package xcanvas import "image" import "image/color" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/tomo/tomo/canvas" // Canvas satisfies the canvas.Canvas interface. It draws to an xgraphics.Image. // It must be closed after use. type Canvas struct { *xgraphics.Image } // NewCanvas creates a new canvas from a bounding rectangle. func NewCanvas (x *xgbutil.XUtil, bounds image.Rectangle) *Canvas { return NewCanvasFrom(xgraphics.New(x, bounds)) } // NewCanvasFrom creates a new canvas from an existing xgraphics.Image. Note // that calling Close() on the resulting canvas will destroy this image. func NewCanvasFrom (image *xgraphics.Image) *Canvas { if image == nil { return nil } return &Canvas { image } } // Pen returns a new drawing context. func (this *Canvas) Pen () canvas.Pen { return &pen { image: this.Image, } } // SubCanvas returns a subset of this canvas that points to the same data. func (this *Canvas) SubCanvas (bounds image.Rectangle) canvas.Canvas { this.assert() subImage := this.Image.SubImage(bounds) if subImage == nil { return nil } xImage := subImage.(*xgraphics.Image) return &Canvas { xImage } } // Push pushes this canvas to the screen. func (this *Canvas) Push (window xproto.Window) { this.assert() this.XDraw() this.XExpPaint(window, this.Bounds().Min.X, this.Bounds().Min.Y) } // Close frees this canvas from the X server. func (this *Canvas) Close () { this.assert() this.Image.Destroy() } func (this *Canvas) assert () { if this == nil { panic("nil canvas") } } // TODO: we need to implement: // - cap // - joint // - align type pen struct { image *xgraphics.Image closed bool endCap canvas.Cap joint canvas.Joint weight int align canvas.StrokeAlign stroke xgraphics.BGRA fill xgraphics.BGRA texture *Texture } func (this *pen) Rectangle (bounds image.Rectangle) { bounds = bounds.Canon() if this.weight == 0 { if this.fill.A > 0 && !this.textureObscures() { this.fillRectangle(this.fill, bounds) } if this.texture != nil { this.textureRectangle(bounds) } } else { if this.stroke.A > 0 { this.strokeRectangle(this.stroke, bounds) } } } func (this *pen) Path (points ...image.Point) { if this.weight == 0 { if this.fill.A > 0 { this.fillPolygon(this.fill, points...) } } else if this.closed && len(points) > 2 { if this.stroke.A > 0 { this.strokePolygon(this.stroke, points...) } } else { if this.stroke.A > 0 { this.polyLine(this.stroke, points...) } } } func (this *pen) Closed (closed bool) { this.closed = closed } func (this *pen) Cap (endCap canvas.Cap) { this.endCap = endCap } func (this *pen) Joint (joint canvas.Joint) { this.joint = joint } func (this *pen) StrokeWeight (weight int) { this.weight = weight } func (this *pen) StrokeAlign (align canvas.StrokeAlign) { this.align = align } func (this *pen) Stroke (stroke color.Color) { this.stroke = convertColor(stroke) } func (this *pen) Fill (fill color.Color) { this.fill = convertColor(fill) } func (this *pen) Texture (texture canvas.Texture) { this.texture = AssertTexture(texture) } func (this *pen) textureObscures () bool { return this.texture != nil && this.texture.Opaque() } func convertColor (c color.Color) xgraphics.BGRA { r, g, b, a := c.RGBA() return xgraphics.BGRA { B: uint8(b >> 8), G: uint8(g >> 8), R: uint8(r >> 8), A: uint8(a >> 8), } }