diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-21 18:33:34 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-01-21 19:06:00 +0100 |
commit | 9159f6a76e14225d7ceeee55defbde2e335c5d3e (patch) | |
tree | e3660a99d6fab73660c1de4ef72f74c29822ac4c | |
parent | 68add5b33e5d9f101306aaf84c373dc695b9ab81 (diff) | |
download | chawan-9159f6a76e14225d7ceeee55defbde2e335c5d3e.tar.gz |
render: blend in paintBackground
It's an improvement, but the painting order still isn't quite right... Also fixes a bug where paintBackground would unnecessarily append an extra line to the document's end.
-rw-r--r-- | src/css/render.nim | 82 | ||||
-rw-r--r-- | src/types/color.nim | 24 | ||||
-rw-r--r-- | test/layout/inline-backgrounds.color.expected | 1 | ||||
-rw-r--r-- | test/layout/inline-split-sizes.color.expected | 1 | ||||
-rw-r--r-- | test/layout/inline-table-missing-parents.color.expected | 1 | ||||
-rw-r--r-- | test/layout/position-absolute-block-in-inline.color.expected | 1 | ||||
-rw-r--r-- | test/layout/table-col-width.color.expected | 1 |
7 files changed, 68 insertions, 43 deletions
diff --git a/src/css/render.nim b/src/css/render.nim index 3eaa619c..0da28fe4 100644 --- a/src/css/render.nim +++ b/src/css/render.nim @@ -84,10 +84,6 @@ proc insertFormat(line: var FlexibleLine; pos, i: int; format: Format; node: Element = nil) = line.insertFormat(i, FormatCell(format: format, node: node, pos: pos)) -proc addFormat(line: var FlexibleLine; pos: int; format: Format; - node: Element = nil) = - line.formats.add(FormatCell(format: format, node: node, pos: pos)) - func toFormat(computed: CSSValues): Format = if computed == nil: return Format() @@ -278,7 +274,7 @@ proc setText(grid: var FlexibleGrid; state: var RenderState; s: string; proc paintBackground(grid: var FlexibleGrid; state: var RenderState; color: CellColor; startx, starty, endx, endy: int; node: Element; - noPaint = false) = + alpha: uint8) = let clipBox = addr state.clipBox var startx = startx var starty = starty @@ -294,69 +290,77 @@ proc paintBackground(grid: var FlexibleGrid; state: var RenderState; endx = min(endx, clipBox.send.x.toInt) div state.attrs.ppc if starty >= endy or startx >= endx: return - if grid.high < endy: # make sure we have line y + if grid.len < endy: # make sure we have line y - 1 grid.addLines(endy - grid.high) var format = Format(bgcolor: color) - for y in starty ..< endy: + for line in grid.toOpenArray(starty, endy - 1).mitems: # Make sure line.width() >= endx - if noPaint: - for i in grid[y].str.width() ..< endx: - grid[y].str &= ' ' + if alpha < 255: + # If the background is not fully opaque, then text under it is + # preserved. + for i in line.str.width() ..< endx: + line.str &= ' ' else: + # Otherwise, background overpaints old text. let w = endx - startx while state.spaces.len < w: state.spaces &= ' ' var cx: int var hadStr: bool - grid[y].setText0(state.spaces.toOpenArray(0, w - 1), startx, endx, + line.setText0(state.spaces.toOpenArray(0, w - 1), startx, endx, cx, hadStr) # Process formatting around startx - if grid[y].formats.len == 0: + if line.formats.len == 0: # No formats - grid[y].addFormat(startx, Format()) + line.formats.add(FormatCell(pos: startx)) else: - let fi = grid[y].findFormatN(startx) - 1 + let fi = line.findFormatN(startx) - 1 if fi == -1: # No format <= startx - grid[y].insertFormat(startx, 0, Format()) - elif grid[y].formats[fi].pos == startx: + line.insertFormat(startx, 0, Format()) + elif line.formats[fi].pos == startx: # Last format equals startx => next comes after, nothing to be done discard else: # Last format lower than startx => separate format from startx - let copy = grid[y].formats[fi] - grid[y].formats[fi].pos = startx - grid[y].insertFormat(fi, copy) + let copy = line.formats[fi] + line.formats[fi].pos = startx + line.insertFormat(fi, copy) # Process formatting around endx - assert grid[y].formats.len > 0 - let fi = grid[y].findFormatN(endx) - 1 + assert line.formats.len > 0 + let fi = line.findFormatN(endx) - 1 if fi == -1: # Last format > endx -> nothing to be done discard - elif grid[y].formats[fi].pos != endx: - let copy = grid[y].formats[fi] - grid[y].formats[fi].pos = endx - grid[y].insertFormat(fi, copy) + elif line.formats[fi].pos != endx: + let copy = line.formats[fi] + line.formats[fi].pos = endx + line.insertFormat(fi, copy) # Paint format backgrounds between startx and endx - for fi in 0 ..< grid[y].formats.len: - if grid[y].formats[fi].pos >= endx: + for it in line.formats.mitems: + if it.pos >= endx: break - if grid[y].formats[fi].pos >= startx: - if not noPaint: - grid[y].formats[fi].format = format - grid[y].formats[fi].node = node + if it.pos >= startx: + if alpha == 0: + discard + elif alpha == 255: + it.format = format + else: + it.format.bgcolor = it.format.bgcolor.blend(color, alpha) + it.node = node proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; box: BlockBox; offset: Offset; pass2 = false) proc paintInlineBox(grid: var FlexibleGrid; state: var RenderState; - box: InlineBox; offset: Offset; bgcolor: CellColor) = + box: InlineBox; offset: Offset; bgcolor: CellColor; alpha: uint8) = for area in box.state.areas: let x1 = toInt(offset.x + area.offset.x) let y1 = toInt(offset.y + area.offset.y) let x2 = toInt(offset.x + area.offset.x + area.size.w) let y2 = toInt(offset.y + area.offset.y + area.size.h) - grid.paintBackground(state, bgcolor, x1, y1, x2, y2, box.node) + grid.paintBackground(state, bgcolor, x1, y1, x2, y2, box.node, + alpha) proc renderInlineBox(grid: var FlexibleGrid; state: var RenderState; box: InlineBox; offset: Offset; bgcolor0: ARGBColor; @@ -368,12 +372,12 @@ proc renderInlineBox(grid: var FlexibleGrid; state: var RenderState; if bgcolor.isCell: let bgcolor = bgcolor.cellColor() if bgcolor.t != ctNone: - grid.paintInlineBox(state, box, offset, bgcolor) + grid.paintInlineBox(state, box, offset, bgcolor, 255) else: bgcolor0 = bgcolor0.blend(bgcolor.argb) if bgcolor0.a > 0: grid.paintInlineBox(state, box, offset, - bgcolor0.rgb.cellColor()) + bgcolor0.rgb.cellColor(), bgcolor0.a) let startOffset = offset + box.state.startOffset box.render.offset = startOffset if position != PositionStatic and stSplitStart in box.splitType: @@ -405,7 +409,7 @@ proc renderInlineBox(grid: var FlexibleGrid; state: var RenderState; let y2 = y2p.toInt # add Element to background (but don't actually color it) grid.paintBackground(state, defaultColor, x1, y1, x2, y2, - box.node, noPaint = true) + box.node, 0) let x = (offset.x div state.attrs.ppc).toInt let y = (offset.y div state.attrs.ppl).toInt let offx = (offset.x - x.toLUnit * state.attrs.ppc).toInt @@ -464,7 +468,8 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; let opacity = box.computed{"opacity"} if box.computed{"visibility"} == VisibilityVisible and opacity != 0: #TODO maybe blend with the terminal background? - let bgcolor = box.computed{"background-color"}.cellColor() + let bgcolor0 = box.computed{"background-color"} + let bgcolor = bgcolor0.cellColor() if bgcolor != defaultColor: if box.computed{"-cha-bgcolor-is-canvas"} and state.bgcolor == defaultColor: @@ -476,7 +481,8 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState; let e = offset + box.state.size let iex = toInt(e.x) let iey = toInt(e.y) - grid.paintBackground(state, bgcolor, ix, iy, iex, iey, box.node) + grid.paintBackground(state, bgcolor, ix, iy, iex, iey, box.node, + bgcolor0.a) if box.computed{"background-image"} != nil: # ugly hack for background-image display... TODO actually display images const s = "[img]" diff --git a/src/types/color.nim b/src/types/color.nim index 0d5786da..719a0fad 100644 --- a/src/types/color.nim +++ b/src/types/color.nim @@ -61,6 +61,9 @@ func a*(c: ARGBColor): uint8 = func rgb*(c: ARGBColor): RGBColor = return RGBColor(uint32(c) and 0xFFFFFFu32) +func argb*(c: RGBColor; a: uint8): ARGBColor = + return ARGBColor((uint32(c) and 0x00FFFFFFu32) or (uint32(a) shl 24)) + func argb*(c: RGBColor): ARGBColor = return ARGBColor(uint32(c) or 0xFF000000u32) @@ -110,6 +113,13 @@ func cssColor*(c: ANSIColor): CSSColor = func argb*(c: CSSColor): ARGBColor = return ARGBColor(c.n) +func a*(c: CSSColor): uint8 = + if c.isCell: + if CellColor(c.n).t == ctNone: + return 0 + return 255 + return ARGBColor(c.n).a + func cellColor*(c: CSSColor): CellColor = if c.isCell: return CellColor(c.n) @@ -329,6 +339,8 @@ proc straight(c: ARGBColor): ARGBColor = let b = ((uint32(c.b) * 0xFF00 div a + 0x80) shr 8) and 0xFF return ARGBColor((a shl 24) or (r shl 16) or (g shl 8) or b) +# Note: this is a very poor approximation, as the premultiplication +# already discards fractions... func blend*(c0, c1: ARGBColor): ARGBColor = let pc0 = c0.premul() let pc1 = c1.premul() @@ -342,6 +354,18 @@ func blend*(c0, c1: ARGBColor): ARGBColor = let res = straight(pres) return res +# Blending operation for cell colors. +# Normally, this should only happen with RGB color, so if either color +# is not one, we can just return fg. +# (This does mean that blending over -cha-ansi is arguably broken. +# Luckily, we get to define how it works because it's our extension :) +func blend*(bg, fg: CellColor; a: uint8): CellColor = + if bg.t != ctRGB or fg.t != ctRGB: + return fg + let bg = bg.rgb.argb + let fg = fg.rgb.argb(a) + return bg.blend(fg).rgb.cellColor() + func rgb*(r, g, b: uint8): RGBColor = return RGBColor((uint32(r) shl 16) or (uint32(g) shl 8) or uint32(b)) diff --git a/test/layout/inline-backgrounds.color.expected b/test/layout/inline-backgrounds.color.expected index 4fdd2664..3f140fda 100644 --- a/test/layout/inline-backgrounds.color.expected +++ b/test/layout/inline-backgrounds.color.expected @@ -28,4 +28,3 @@ final stage test1 [48;2;0;128;0m test2[49m [48;2;0;0;255m test3 [49m [48;2;255;0;0m test4[49m - diff --git a/test/layout/inline-split-sizes.color.expected b/test/layout/inline-split-sizes.color.expected index 39ddcf28..24eaf5a4 100644 --- a/test/layout/inline-split-sizes.color.expected +++ b/test/layout/inline-split-sizes.color.expected @@ -2,4 +2,3 @@ before [48;2;255;0;0mt[49m carthago delenda [48;2;255;0;0m est[49m [48;2;255;0;0mhello[49m after - diff --git a/test/layout/inline-table-missing-parents.color.expected b/test/layout/inline-table-missing-parents.color.expected index 28542efd..eb8dc764 100644 --- a/test/layout/inline-table-missing-parents.color.expected +++ b/test/layout/inline-table-missing-parents.color.expected @@ -8,4 +8,3 @@ test 9.5 [48;2;255;0;0mthing2.5 thing3 [49m [48;2;255;0;0mthing4 [49m [48;2;0;0;255mtest [49m - diff --git a/test/layout/position-absolute-block-in-inline.color.expected b/test/layout/position-absolute-block-in-inline.color.expected index b25e275c..839256f8 100644 --- a/test/layout/position-absolute-block-in-inline.color.expected +++ b/test/layout/position-absolute-block-in-inline.color.expected @@ -5,4 +5,3 @@ line four [38;2;109;109;109m[48;2;255;255;0mabsolute 4[39m[49m [48;2;0;0;255mabsolute 5[49m line six[38;2;109;109;109m[48;2;255;255;0mabsolute 6[39m[49m - diff --git a/test/layout/table-col-width.color.expected b/test/layout/table-col-width.color.expected index 7e556aa8..64847c83 100644 --- a/test/layout/table-col-width.color.expected +++ b/test/layout/table-col-width.color.expected @@ -6,4 +6,3 @@ [48;2;255;0;0ma [49m [48;2;255;0;0mtest [49m [48;2;255;0;0ma [49m - |