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 () error { this.assert() this.Image.Destroy() return nil } 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), } } // For some reason, xgraphics.BGRA does not specify whether or not it uses // premultiplied alpha, and information regarding this is contradictory. // Basically: // - BGRAModel just takes the result of c.RGBA and bit shifts it, without // un-doing the aplha premultiplication that is required by Color.RGBA, // suggesting that xgraphics.BGRA stores alpha-premultiplied color. // - xgraphics.BlendBGRA lerps between dest and src using only the alpha of // src (temporarily converting the colors to fucking floats for some reason) // which seems to suggest that xgraphics.BGRA *does not* store alpha- // premultiplied color. // There is no issues page on xgbutil so we may never get an answer to this // question. However, in this package we just use xgraphics.BGRA to store alpha- // premultiplied color anyway because its way faster, and I would sooner eat // spaghetti with a spoon than convert to and from float64 to blend pixels. func blendPremultipliedBGRA (dst, src xgraphics.BGRA) xgraphics.BGRA { // https://en.wikipedia.org/wiki/Alpha_compositing return xgraphics.BGRA { B: blendPremultipliedChannel(dst.B, src.B, src.A), G: blendPremultipliedChannel(dst.G, src.G, src.A), R: blendPremultipliedChannel(dst.R, src.R, src.A), A: blendPremultipliedChannel(dst.A, src.A, src.A), } } func blendPremultipliedChannel (dst, src, a uint8) uint8 { dst16, src16, a16 := uint16(dst), uint16(src), uint16(a) return uint8(src16 + ((dst16 * (255 - a16)) >> 8)) }