about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-09-24 23:25:07 +0200
committerbptato <nincsnevem662@gmail.com>2024-09-24 23:55:43 +0200
commit1c9277343140effcc9eee8845757afd1eed4f4bd (patch)
tree7a902dc2aebce67ee5749442129176b0683cf60f /src
parent0c738c94e14c213562f69ff6e376c19fb0487201 (diff)
downloadchawan-1c9277343140effcc9eee8845757afd1eed4f4bd.tar.gz
sixel: support transparency
Sixel can only represent transparency for fully transparent (alpha
= 0) and fully opaque (alpha = 255) pixels, i.e. we would have to
do blending ourselves to do this "properly". But what do you even
blend? Background color? Images? Clearly you can't do text...

So instead of going down the blending route, we now just approximate
the 8-bit channel with Sixel's 1-bit channel and then patch it up with
dither. It does look a bit weird, but it's not *that* bad, especially
compared to the previous strategy of "blend with some color which
hopefully happens to be the background color" (it rarely was).

Note that this requires us to handle transparent images specially
in term. That is, for opaque ones, we can leave out the "clear cells
affected by image" part, but for transparent ones, we must clear the
entire image every time.
Diffstat (limited to 'src')
-rw-r--r--src/layout/renderdocument.nim4
-rw-r--r--src/local/container.nim2
-rw-r--r--src/local/pager.nim8
-rw-r--r--src/local/term.nim38
-rw-r--r--src/server/buffer.nim4
-rw-r--r--src/types/color.nim2
6 files changed, 34 insertions, 24 deletions
diff --git a/src/layout/renderdocument.nim b/src/layout/renderdocument.nim
index 76042ae2..9622d305 100644
--- a/src/layout/renderdocument.nim
+++ b/src/layout/renderdocument.nim
@@ -231,7 +231,6 @@ type
     width*: int
     height*: int
     bmp*: NetworkBitmap
-    bgcolor*: RGBColor
 
   AbsolutePos = object
     offset: Offset
@@ -380,8 +379,7 @@ proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState;
           y: (offset.y div state.attrs.ppl).toInt,
           width: atom.size.w.toInt,
           height: atom.size.h.toInt,
-          bmp: atom.bmp,
-          bgcolor: bgcolor0.toRGBColor()
+          bmp: atom.bmp
         ))
   if fragment.computed{"position"} != PositionStatic:
     if fragment.splitType != {stSplitStart, stSplitEnd}:
diff --git a/src/local/container.nim b/src/local/container.nim
index 6bdfe64b..5632bd88 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -122,6 +122,8 @@ type
     offx*: int # same as CanvasImage.offx
     dispw*: int # same as CanvasImage.dispw
     erry*: int # same as CanvasImage.offy % 6
+    # whether the image has transparency, *disregarding the last row*
+    transparent*: bool
 
   Container* = ref object of RootObj
     # note: this is not the same as source.request.url (but should be synced
diff --git a/src/local/pager.nim b/src/local/pager.nim
index ec242472..e8034af7 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -586,7 +586,6 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap;
       url = newURL("img-codec+x-sixel:encode").get
       headers.add("Cha-Image-Sixel-Halfdump", "1")
       headers.add("Cha-Image-Sixel-Palette", $pager.term.sixelRegisterNum)
-      headers.add("Cha-Image-Background-Color", $image.bgcolor)
       headers.add("Cha-Image-Offset", $offx & 'x' & $erry)
       headers.add("Cha-Image-Crop-Width", $dispw)
     of imKitty:
@@ -630,6 +629,11 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap;
       cachedImage.data = blob
       cachedImage.state = cisLoaded
       cachedImage.cacheId = cacheId
+      if imageMode == imSixel and 4 < blob.size:
+        #TODO this should be a response header, but loader can't send us
+        # those yet...
+        let u = cast[ptr UncheckedArray[uint8]](blob.buffer)[4]
+        cachedImage.transparent = u == 1
     )
   )
   container.cachedImages.add(cachedImage)
@@ -660,7 +664,7 @@ proc initImages(pager: Pager; container: Container) =
     let canvasImage = pager.term.loadImage(cached.data, container.process,
       imageId, image.x - container.fromx, image.y - container.fromy,
       image.width, image.height, image.x, image.y, pager.bufWidth,
-      pager.bufHeight, erry, offx, dispw)
+      pager.bufHeight, erry, offx, dispw, cached.transparent)
     if canvasImage != nil:
       newImages.add(canvasImage)
   pager.term.clearImages(pager.bufHeight)
diff --git a/src/local/term.nim b/src/local/term.nim
index 0e245256..3f38193b 100644
--- a/src/local/term.nim
+++ b/src/local/term.nim
@@ -73,6 +73,7 @@ type
     damaged: bool
     marked*: bool
     dead: bool
+    transparent: bool # note: this is only set in outputSixelImage
     kittyId: int
     # 0 if kitty
     erry: int
@@ -735,7 +736,12 @@ proc checkImageDamage*(term: Terminal; maxh: int) =
       let mx = image.x + (image.dispw - image.offx) div term.attrs.ppc
       for y in max(image.y, 0) ..< ey0:
         let od = term.lineDamage[y]
-        if od < mx:
+        if image.transparent and od > image.x:
+          image.damaged = true
+          if od < mx:
+            # damage starts inside this image; move it to its beginning.
+            term.lineDamage[y] = image.x
+        elif not image.transparent and od < mx:
           image.damaged = true
           if y >= ey1:
             break
@@ -752,7 +758,8 @@ proc checkImageDamage*(term: Terminal; maxh: int) =
               term.lineDamage[y] = mx
 
 proc loadImage*(term: Terminal; data: Blob; pid, imageId, x, y, width, height,
-    rx, ry, maxw, maxh, erry, offx, dispw: int): CanvasImage =
+    rx, ry, maxw, maxh, erry, offx, dispw: int; transparent: bool):
+    CanvasImage =
   if (let image = term.findImage(pid, imageId, rx, ry, width, height, erry,
         offx, dispw); image != nil):
     # reuse image on screen
@@ -777,7 +784,8 @@ proc loadImage*(term: Terminal; data: Blob; pid, imageId, x, y, width, height,
     ry: ry,
     width: width,
     height: height,
-    erry: erry
+    erry: erry,
+    transparent: transparent
   )
   if term.positionImage(image, x, y, maxw, maxh):
     return image
@@ -796,26 +804,22 @@ proc outputSixelImage(term: Terminal; x, y: int; image: CanvasImage;
   let offy = image.offy
   let dispw = image.dispw
   let disph = image.disph
-  var outs = term.cursorGoto(x, y)
   let realw = dispw - offx
   let realh = disph - offy
-  # set transparency if we want to draw a non-6-divisible number
-  # of rows; omit it otherwise, for then some terminals (e.g. foot)
-  # handle the image more efficiently
-  let trans = realh mod 6 != 0
-  outs &= DCS & "0;" & $int(trans) & 'q'
-  # set raster attributes
-  outs &= "\"1;1;" & $realw & ';' & $realh
-  if data.len < 4: # bounds check
-    outs &= ST
-    term.write(outs)
+  if data.len < 5: # bounds check
     return
   let sraLen = int(data.getU32BE(0))
-  let preludeLen = sraLen + 4
+  let preludeLen = sraLen + 5
   if preludeLen > data.len:
-    outs &= ST
-    term.write(outs)
     return
+  var outs = term.cursorGoto(x, y)
+  # set transparency if we want to draw a non-6-divisible number
+  # of rows *or* the image is transparent; omit it otherwise, for then
+  # some terminals (e.g. foot) handle the image more efficiently
+  let trans = realh mod 6 != 0 or image.transparent
+  outs &= DCS & "0;" & $int(trans) & "q"
+  # set raster attributes
+  outs &= "\"1;1;" & $realw & ';' & $realh
   term.write(outs)
   term.write(data.toOpenArray(4, preludeLen - 1))
   let lookupTableLen = int(data.getU32BE(data.len - 4))
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index c5d00655..71eef321 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -1615,8 +1615,10 @@ proc getLines*(buffer: Buffer; w: Slice[int]): GetLinesResult {.proxy.} =
   result.numLines = buffer.lines.len
   result.bgcolor = buffer.bgcolor
   if buffer.config.images:
+    let ppl = buffer.attrs.ppl
     for image in buffer.images:
-      if image.y <= w.b and image.y + image.height >= w.a:
+      let ey = image.y + (image.height + ppl - 1) div ppl # ceil
+      if image.y <= w.b and ey >= w.a:
         result.images.add(image)
 
 proc markURL*(buffer: Buffer; schemes: seq[string]) {.proxy.} =
diff --git a/src/types/color.nim b/src/types/color.nim
index ab8d4984..20de1fcc 100644
--- a/src/types/color.nim
+++ b/src/types/color.nim
@@ -293,7 +293,7 @@ func fastmul*(c, ca: uint32): uint32 =
   return ga or (rb shr 8)
 
 # fastmul, but preserves alpha
-func fastmul1(c, ca: uint32): uint32 =
+func fastmul1*(c, ca: uint32): uint32 =
   let u = c
   var rb = u and 0x00FF00FFu32
   rb *= ca