package x import "image" import "image/draw" import "golang.org/x/image/math/fixed" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/sashakoshka/stone" func (backend *Backend) Draw () { backend.lock.Lock() defer backend.lock.Unlock() if backend.windowBoundsClean { backend.canvas.XPaintRects ( backend.window.Id, backend.drawCells(false)...) } else { backend.reallocateCanvas() backend.drawCells(true) backend.canvas.XDraw() backend.canvas.XPaint(backend.window.Id) backend.windowBoundsClean = true } } func (backend *Backend) reallocateCanvas () { if backend.canvas != nil { backend.canvas.Destroy() } backend.canvas = xgraphics.New ( backend.connection, image.Rect ( 0, 0, backend.metrics.windowWidth, backend.metrics.windowHeight)) backend.canvas.For (func (x, y int) xgraphics.BGRA { return backend.colors[stone.ColorBackground] }) backend.canvas.XSurfaceSet(backend.window.Id) } func (backend *Backend) drawCells (forceRedraw bool) (areas []image.Rectangle) { width, height := backend.application.Size() for y := 0; y < height; y ++ { for x := 0; x < width; x ++ { if !forceRedraw && backend.application.Clean(x, y) { continue } cell := backend.application.GetForRendering(x, y) content := cell.Rune() if forceRedraw && content < 32 { continue } areas = append(areas, backend.boundsOfCell(x, y)) backend.drawRune(x, y, content, cell.Color(), !forceRedraw) }} if backend.drawBufferBounds && forceRedraw { strokeRectangle ( &image.Uniform { C: backend.config.Color(stone.ColorForeground), }, backend.canvas, image.Rectangle { Min: backend.originOfCell(0, 0), Max: backend.originOfCell(width, height), }) } return } func (backend *Backend) drawRune ( x, y int, character rune, runeColor stone.Color, drawBackground bool, ) { // TODO: cache these draws as non-transparent buffers with the // application background color as the background. that way, we won't // need to redraw the characters *or* composite them. if drawBackground { fillRectangle ( &image.Uniform { C: backend.config.Color(stone.ColorBackground), }, backend.canvas, backend.boundsOfCell(x, y)) } if character < 32 { return } origin := backend.originOfCell(x, y + 1) destinationRectangle, mask, maskPoint, _, ok := backend.font.face.Glyph ( fixed.Point26_6 { X: fixed.I(origin.X), Y: fixed.I(origin.Y), }, character) if !ok { strokeRectangle ( &image.Uniform { C: backend.config.Color(stone.ColorForeground), }, backend.canvas, backend.boundsOfCell(x, y)) return } if backend.drawCellBounds { strokeRectangle ( &image.Uniform { C: backend.config.Color(stone.ColorForeground), }, backend.canvas, backend.boundsOfCell(x, y)) } // cue a series of pointless optimizations alphaMask, isAlpha := mask.(*image.Alpha) if isAlpha { backend.sprayRuneMaskAlpha ( alphaMask, destinationRectangle, maskPoint, backend.colors[runeColor]) } else { backend.sprayRuneMask ( mask, destinationRectangle, maskPoint, backend.colors[runeColor]) } } func (backend *Backend) sprayRuneMask ( mask image.Image, bounds image.Rectangle, maskPoint image.Point, fill xgraphics.BGRA, ) { maxX := bounds.Max.X - bounds.Min.X maxY := bounds.Max.Y - bounds.Min.Y for y := 0; y < maxY; y ++ { for x := 0; x < maxX; x ++ { _, _, _, alpha := mask.At(x + maskPoint.X, y + maskPoint.Y).RGBA() backend.canvas.SetBGRA ( x + bounds.Min.X, y + bounds.Min.Y - backend.metrics.descent, xgraphics.BlendBGRA ( backend.colors[stone.ColorBackground], xgraphics.BGRA { R: fill.R, G: fill.G, B: fill.B, A: uint8(alpha >> 8), })) }} } func (backend *Backend) sprayRuneMaskAlpha ( mask *image.Alpha, bounds image.Rectangle, maskPoint image.Point, fill xgraphics.BGRA, ) { maxX := bounds.Max.X - bounds.Min.X maxY := bounds.Max.Y - bounds.Min.Y for y := 0; y < maxY; y ++ { for x := 0; x < maxX; x ++ { alpha := mask.AlphaAt(x + maskPoint.X, y + maskPoint.Y).A backend.canvas.SetBGRA ( x + bounds.Min.X, y + bounds.Min.Y - backend.metrics.descent, xgraphics.BlendBGRA ( backend.colors[stone.ColorBackground], xgraphics.BGRA { R: fill.R, G: fill.G, B: fill.B, A: alpha, })) }} } func fillRectangle ( source image.Image, destination draw.Image, bounds image.Rectangle, ) { for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { for x := bounds.Min.X; x < bounds.Max.X; x ++ { destination.Set(x, y, source.At(x, y)) }} } func strokeRectangle ( source image.Image, destination draw.Image, bounds image.Rectangle, ) { x := 0 y := bounds.Min.Y for x = bounds.Min.X; x < bounds.Max.X; x ++ { destination.Set(x, y, source.At(x, y)) } y = bounds.Max.Y - 1 for x = bounds.Min.X; x < bounds.Max.X; x ++ { destination.Set(x, y, source.At(x, y)) } x = bounds.Min.X for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { destination.Set(x, y, source.At(x, y)) } x = bounds.Max.X - 1 for y = bounds.Min.Y; y < bounds.Max.Y; y ++ { destination.Set(x, y, source.At(x, y)) } }