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() style := cell.Style() if forceRedraw && content < 32 && style & ( stone.StyleHighlight | stone.StyleUnderline) == 0 { continue } areas = append(areas, backend.boundsOfCell(x, y)) backend.drawRune ( x, y, content, cell.Color(), cell.Style(), !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, runeStyle stone.Style, 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. face := backend.font.normal highlight := runeStyle & stone.StyleHighlight > 0 bold := runeStyle & stone.StyleBold > 0 italic := runeStyle & stone.StyleItalic > 0 boldTransform := false italicTransform := false switch { case bold && italic: if backend.font.boldItalic == nil { switch { case backend.font.bold == nil && backend.font.italic != nil, backend.font.bold != nil && backend.font.italic != nil: boldTransform = true face = backend.font.italic case backend.font.italic == nil && backend.font.bold != nil: italicTransform = true face = backend.font.bold default: boldTransform = true italicTransform = true } } else { face = backend.font.boldItalic } case bold: if backend.font.bold == nil { boldTransform = true } else { face = backend.font.bold } case italic: if backend.font.italic == nil { italicTransform = true } else { face = backend.font.italic } } var background xgraphics.BGRA var foreground xgraphics.BGRA if highlight { background = backend.colors[runeColor] foreground = backend.colors[stone.ColorBackground] } else { background = backend.colors[stone.ColorBackground] foreground = backend.colors[runeColor] } if drawBackground || highlight { fillRectangle ( &image.Uniform { C: background }, backend.canvas, backend.boundsOfCell(x, y)) } origin := backend.originOfCell(x, y + 1) if character >= 32 { destinationRectangle, mask, maskPoint, _, ok := face.Glyph ( fixed.Point26_6 { X: fixed.I(origin.X), Y: fixed.I(origin.Y), }, character) if !ok { strokeRectangle ( &image.Uniform { C: foreground }, backend.canvas, backend.boundsOfCell(x, y)) return } if backend.drawCellBounds { strokeRectangle ( &image.Uniform { C: foreground }, backend.canvas, backend.boundsOfCell(x, y)) } // alphaMask, isAlpha := mask.(*image.Alpha) // if isAlpha { // backend.sprayRuneMaskAlpha ( // alphaMask, destinationRectangle, // maskPoint, foreground, background) // } else { backend.sprayRuneMask ( mask, destinationRectangle, maskPoint, foreground, background, italicTransform, boldTransform) // } } // underline if runeStyle & stone.StyleUnderline > 0 { maxX := origin.X + backend.metrics.cellWidth y := origin.Y - backend.metrics.descent for x := origin.X; x < maxX; x ++ { backend.canvas.SetBGRA(x, y, foreground) } } } func (backend *Backend) sprayRuneMask ( mask image.Image, bounds image.Rectangle, maskPoint image.Point, fill xgraphics.BGRA, background xgraphics.BGRA, italic bool, bold bool, ) { maxX := bounds.Max.X - bounds.Min.X maxY := bounds.Max.Y - bounds.Min.Y for y := 0; y < maxY; y ++ { var previousAlpha uint32 offset := 0 if italic { offset = (maxY - y) / 4 } for x := 0; x < maxX; x ++ { _, _, _, alpha := mask.At(x + maskPoint.X, y + maskPoint.Y).RGBA() currentAlpha := alpha if bold && previousAlpha > alpha { alpha = previousAlpha } backend.canvas.SetBGRA ( x + bounds.Min.X + offset, y + bounds.Min.Y - backend.metrics.descent, xgraphics.BlendBGRA ( background, xgraphics.BGRA { R: fill.R, G: fill.G, B: fill.B, A: uint8(alpha >> 8), })) previousAlpha = currentAlpha } if bold { backend.canvas.SetBGRA ( bounds.Max.X + offset, y + bounds.Min.Y - backend.metrics.descent, xgraphics.BlendBGRA ( background, xgraphics.BGRA { R: fill.R, G: fill.G, B: fill.B, A: uint8(previousAlpha >> 8), })) } } } // func (backend *Backend) sprayRuneMaskAlpha ( // mask *image.Alpha, // bounds image.Rectangle, // maskPoint image.Point, // fill xgraphics.BGRA, // background 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 ( // background, // 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)) } }