about summary refs log tree commit diff stats
path: root/src/local
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-07-02 16:56:33 +0200
committerbptato <nincsnevem662@gmail.com>2024-07-02 17:50:10 +0200
commit02b549ee0c7bda8ad110eba6aac14bafb10d45e3 (patch)
tree4628d8fbba059098f7e6fdc78fd3ab5070cb8820 /src/local
parentc69f9f6c1bc18b718a8c8deb11934cca19929e02 (diff)
downloadchawan-02b549ee0c7bda8ad110eba6aac14bafb10d45e3.tar.gz
pager: PNGify kitty images, clear images on buffer switch
Saves bandwidth; it's especially useful over SSH. Still not sure if this
is the right solution, since it now needs two select cycles instead
of one, and it does yet another copy of the image. (Unnecessarily,
because stbi cannot stream its output, and stbiw cannot stream its
input.)

Also, to save memory, we now discard decoded images of buffers that are
not being viewed.
Diffstat (limited to 'src/local')
-rw-r--r--src/local/container.nim2
-rw-r--r--src/local/pager.nim61
-rw-r--r--src/local/term.nim38
3 files changed, 73 insertions, 28 deletions
diff --git a/src/local/container.nim b/src/local/container.nim
index cc1e75e5..9f8dbd9e 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -22,6 +22,7 @@ import monoucha/javascript
 import monoucha/jsregex
 import monoucha/jstypes
 import server/buffer
+import types/blob
 import types/cell
 import types/color
 import types/cookie
@@ -101,6 +102,7 @@ type
     loaded*: bool
     width*: int
     height*: int
+    data*: Blob
     bmp*: NetworkBitmap
 
   Container* = ref object
diff --git a/src/local/pager.nim b/src/local/pager.nim
index 9f5eb593..f3d4d79a 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -40,6 +40,7 @@ import monoucha/quickjs
 import monoucha/tojs
 import server/buffer
 import server/forkserver
+import types/blob
 import types/cell
 import types/color
 import types/cookie
@@ -208,6 +209,8 @@ proc clearStatus(pager: Pager) =
   )
 
 proc setContainer*(pager: Pager; c: Container) {.jsfunc.} =
+  if pager.term.imageMode != imNone and pager.container != nil:
+    pager.container.cachedImages.setLen(0)
   pager.container = c
   if c != nil:
     c.queueDraw()
@@ -474,8 +477,8 @@ proc redraw(pager: Pager) {.jsfunc.} =
       pager.container.select.redraw = true
 
 proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap) =
-  #TODO we should only cache the final output in memory, not the full bitmap.
-  let bmp = NetworkBitmap(image.bmp)
+  let bmp = NetworkBitmap()
+  bmp[] = NetworkBitmap(image.bmp)[]
   let request = newRequest(newURL("cache:" & $bmp.cacheId).get)
   let cachedImage = CachedImage(
     bmp: bmp,
@@ -484,9 +487,11 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap) =
   )
   pager.loader.shareCachedItem(bmp.cacheId, pager.loader.clientPid,
     container.process)
+  let imageMode = pager.term.imageMode
   pager.loader.fetch(request).then(proc(res: JSResult[Response]):
       Promise[JSResult[Response]] =
     if res.isNone:
+      pager.loader.removeCachedItem(bmp.cacheId)
       return
     let response = res.get
     let headers = newHeaders()
@@ -504,19 +509,46 @@ proc loadCachedImage(pager: Pager; container: Container; image: PosBitmap) =
     response.unregisterFun()
     response.body.sclose()
     return r
-  ).then(proc(res: JSResult[Response]): EmptyPromise =
+  ).then(proc(res: JSResult[Response]) =
     if res.isNone:
       pager.loader.removeCachedItem(bmp.cacheId)
-      return newResolvedPromise()
+      return
     let response = res.get
     # take target sizes
     bmp.width = uint64(image.width)
     bmp.height = uint64(image.height)
-    return response.saveToBitmap(bmp).then(proc() =
-      container.redraw = true
-      cachedImage.loaded = true
-      pager.loader.removeCachedItem(bmp.cacheId)
-    )
+    case imageMode
+    of imSixel:
+      #TODO we should only cache the final output in memory, not the full
+      # bitmap.
+      response.saveToBitmap(bmp).then(proc() =
+        container.redraw = true
+        cachedImage.loaded = true
+        pager.loader.removeCachedItem(bmp.cacheId)
+      )
+    of imKitty:
+      let headers = newHeaders({
+        "Cha-Image-Dimensions": $image.width & 'x' & $image.height
+      })
+      let request = newRequest(
+        newURL("img-codec+png:encode").get,
+        httpMethod = hmPost,
+        headers = headers,
+        body = RequestBody(t: rbtOutput, outputId: response.outputId),
+      )
+      let r = pager.loader.fetch(request)
+      response.resume()
+      response.unregisterFun()
+      response.body.sclose()
+      r.then(proc(res: JSResult[Response]): Promise[JSResult[Blob]] =
+        return res.get.blob()
+      ).then(proc(res: JSResult[Blob]) =
+        container.redraw = true
+        cachedImage.data = res.get
+        cachedImage.loaded = true
+        pager.loader.removeCachedItem(bmp.cacheId)
+      )
+    of imNone: assert false
   )
   container.cachedImages.add(cachedImage)
 
@@ -524,6 +556,8 @@ proc initImages(pager: Pager; container: Container) =
   var newImages: seq[CanvasImage] = @[]
   for image in container.images:
     var imageId = -1
+    var data: Blob = nil
+    var bmp0 = image.bmp
     if image.bmp of NetworkBitmap:
       let bmp = NetworkBitmap(image.bmp)
       let cached = container.findCachedImage(image)
@@ -531,15 +565,16 @@ proc initImages(pager: Pager; container: Container) =
       if cached == nil:
         pager.loadCachedImage(container, image)
         continue
-      image.bmp = cached.bmp
+      bmp0 = cached.bmp
+      data = cached.data
       if not cached.loaded:
         continue # loading
     else:
       imageId = pager.imageId
       inc pager.imageId
-    let canvasImage = pager.term.loadImage(image, container.process, imageId,
-      image.x - container.fromx, image.y - container.fromy, pager.bufWidth,
-      pager.bufHeight)
+    let canvasImage = pager.term.loadImage(bmp0, data, container.process,
+      imageId, image.x - container.fromx, image.y - container.fromy,
+      image.x, image.y, pager.bufWidth, pager.bufHeight)
     if canvasImage != nil:
       newImages.add(canvasImage)
   pager.term.clearImages(pager.bufHeight)
diff --git a/src/local/term.nim b/src/local/term.nim
index f2b48f36..50eac042 100644
--- a/src/local/term.nim
+++ b/src/local/term.nim
@@ -14,7 +14,7 @@ import config/config
 import img/bitmap
 import io/posixstream
 import js/base64
-import layout/renderdocument
+import types/blob
 import types/cell
 import types/color
 import types/opt
@@ -66,7 +66,11 @@ type
     damaged: bool
     marked*: bool
     kittyId: int
-    pbmp: PosBitmap
+    bmp: Bitmap
+    # absolute x, y in container
+    rx: int
+    ry: int
+    data: Blob
 
   Terminal* = ref object
     cs*: Charset
@@ -222,9 +226,6 @@ const ANSIColorMap = [
   rgb(255, 255, 255)
 ]
 
-template bmp(image: CanvasImage): Bitmap =
-  image.pbmp.bmp
-
 proc flush*(term: Terminal) =
   term.outfile.flushFile()
 
@@ -619,12 +620,12 @@ proc outputGrid*(term: Terminal) =
   term.cursorx = -1
   term.cursory = -1
 
-func findImage(term: Terminal; pid, imageId: int; pbmp: PosBitmap):
+func findImage(term: Terminal; pid, imageId: int; bmp: Bitmap; rx, ry: int):
     CanvasImage =
   for it in term.canvasImages:
     if it.pid == pid and it.imageId == imageId and
-        it.pbmp.width == pbmp.width and it.pbmp.height == pbmp.height and
-        it.pbmp.x == pbmp.x and it.pbmp.y == pbmp.y:
+        it.bmp.width == bmp.width and it.bmp.height == bmp.height and
+        it.rx == rx and it.ry == ry:
       return it
   return nil
 
@@ -666,9 +667,9 @@ proc clearImages*(term: Terminal; maxh: int) =
       term.clearImage(image, maxh)
     image.marked = false
 
-proc loadImage*(term: Terminal; pbmp: PosBitmap; pid, imageId, x, y, maxw,
-    maxh: int): CanvasImage =
-  if (let image = term.findImage(pid, imageId, pbmp); image != nil):
+proc loadImage*(term: Terminal; bmp: Bitmap; data: Blob; pid, imageId,
+    x, y, rx, ry, maxw, maxh: int): CanvasImage =
+  if (let image = term.findImage(pid, imageId, bmp, rx, ry); image != nil):
     # reuse image on screen
     if image.x != x or image.y != y:
       # only clear sixels; with kitty we just move the existing image
@@ -690,7 +691,14 @@ proc loadImage*(term: Terminal; pbmp: PosBitmap; pid, imageId, x, y, maxw,
     image.marked = true
     return image
   # new image
-  let image = CanvasImage(pbmp: pbmp, pid: pid, imageId: imageId)
+  let image = CanvasImage(
+    bmp: bmp,
+    pid: pid,
+    imageId: imageId,
+    data: data,
+    rx: rx,
+    ry: ry
+  )
   if term.positionImage(image, x, y, maxw, maxh):
     return image
   # no longer on screen
@@ -802,10 +810,10 @@ proc outputKittyImage(term: Terminal; x, y: int; image: CanvasImage) =
   const MaxBytes = 4096 * 3 div 4
   var i = MaxBytes
   # transcode to RGB
-  let p = cast[ptr UncheckedArray[uint8]](addr image.bmp.px[0])
-  let L = image.bmp.px.len * 4
+  let p = cast[ptr UncheckedArray[uint8]](image.data.buffer)
+  let L = int(image.data.size)
   let m = if i < L: '1' else: '0'
-  outs &= ",a=T,f=32,m=" & m & ';'
+  outs &= ",a=T,f=100,m=" & m & ';'
   outs.btoa(p.toOpenArray(0, min(L, i) - 1))
   outs &= ST
   term.write(outs)