about summary refs log tree commit diff stats
path: root/adapter/img
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-09-01 01:03:50 +0200
committerbptato <nincsnevem662@gmail.com>2024-09-01 01:46:38 +0200
commit55cfd29e961488a8c1ed9eb7801d237d27bc86c7 (patch)
treec74569e15ca72d777eadcfd19a0203cbb76c3e3f /adapter/img
parente9466c4c436f964b53034e28356aa3f5c957a068 (diff)
downloadchawan-55cfd29e961488a8c1ed9eb7801d237d27bc86c7.tar.gz
canvas: move to separate CGI script
* stream: and passFd is now client-based, and accessible for buffers
* Bitmap's width & height is now int, not uint64
* no more non-network Bitmap special case in the pager for canvas

I just shoehorned it into the static image model, so it still doesn't
render changes after page load. But at least now it doesn't crash the
browser.
Diffstat (limited to 'adapter/img')
-rw-r--r--adapter/img/canvas.c5
-rw-r--r--adapter/img/canvas.nim141
-rw-r--r--adapter/img/jebp.nim5
3 files changed, 148 insertions, 3 deletions
diff --git a/adapter/img/canvas.c b/adapter/img/canvas.c
new file mode 100644
index 00000000..9e386ca1
--- /dev/null
+++ b/adapter/img/canvas.c
@@ -0,0 +1,5 @@
+#define STBI_ONLY_PNG
+#define STBI_NO_STDIO
+#define STBI_NO_LINEAR
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
diff --git a/adapter/img/canvas.nim b/adapter/img/canvas.nim
new file mode 100644
index 00000000..2182e1d9
--- /dev/null
+++ b/adapter/img/canvas.nim
@@ -0,0 +1,141 @@
+# Very simple canvas renderer. At the moment, it uses an undocumented binary
+# protocol for reading commands, and renders it whenever stdin is closed.
+# So for now, it can only really render a single frame.
+#
+# It uses unifont for rendering text - currently I just store it as PNG
+# and read it with stbi. (TODO: try switching to a more efficient format
+# like qemacs fbf.)
+
+import std/os
+import std/posix
+import std/strutils
+
+import css/cssvalues
+import img/bitmap
+import img/painter
+import img/path
+import io/bufreader
+import io/dynstream
+import types/color
+import types/line
+import utils/sandbox
+
+{.compile: "canvas.c".}
+
+{.passc: "-I" & currentSourcePath().parentDir().}
+
+{.push header: "stb_image.h".}
+proc stbi_load_from_memory(buffer: ptr uint8; len: cint; x, y, comp: ptr cint;
+  req_comp: cint): ptr uint8
+proc stbi_image_free(retval_from_stbi_load: pointer)
+{.pop.}
+
+const unifont = readFile"res/unifont_jp-15.0.05.png"
+proc loadUnifont(unifont: string): ImageBitmap =
+  var width, height, comp: cint
+  let p = stbi_load_from_memory(cast[ptr uint8](unsafeAddr unifont[0]),
+    cint(unifont.len), addr width, addr height, addr comp, 4)
+  let len = width * height
+  let bitmap = ImageBitmap(
+    px: cast[seq[RGBAColorBE]](newSeqUninitialized[uint32](len)),
+    width: int(width),
+    height: int(height)
+  )
+  copyMem(addr bitmap.px[0], p, len)
+  stbi_image_free(p)
+  return bitmap
+
+proc main() =
+  enterNetworkSandbox()
+  let os = newPosixStream(STDOUT_FILENO)
+  let ps = newPosixStream(STDIN_FILENO)
+  if getEnv("MAPPED_URI_SCHEME") != "img-codec+x-cha-canvas":
+    os.write("Cha-Control: ConnectionError 1 wrong scheme\n")
+    quit(1)
+  case getEnv("MAPPED_URI_PATH")
+  of "decode":
+    let headers = getEnv("REQUEST_HEADERS")
+    for hdr in headers.split('\n'):
+      if hdr.strip() == "Cha-Image-Info-Only: 1":
+        #TODO this is a hack...
+        # basically, we eat & discard all data from the buffer so it gets saved
+        # to a cache file. then, actually render when the pager asks us to
+        # do so.
+        # obviously this is highly sub-optimal; a better solution would be to
+        # leave stdin open & pass down the stream id from the buffer. (but then
+        # you have to save canvas output too, so it doesn't have to be
+        # re-coded, and handle that case in encoders... or implement on-demand
+        # multi-frame output.)
+        os.write("\n")
+        discard ps.recvAll()
+        quit(0)
+    var cmd: PaintCommand
+    var width: int
+    var height: int
+    ps.withPacketReader r:
+      r.sread(cmd)
+      if cmd != pcSetDimensions:
+        os.write("Cha-Control: ConnectionError 1 wrong dimensions\n")
+        quit(1)
+      r.sread(width)
+      r.sread(height)
+    os.write("Cha-Image-Dimensions: " & $width & "x" & $height & "\n\n")
+    let bmp = newBitmap(width, height)
+    var alive = true
+    while alive:
+      try:
+        ps.withPacketReader r:
+          r.sread(cmd)
+          case cmd
+          of pcSetDimensions:
+            alive = false
+          of pcFillRect, pcStrokeRect:
+            var x1, y1, x2, y2: int
+            var color: ARGBColor
+            r.sread(x1)
+            r.sread(y1)
+            r.sread(x2)
+            r.sread(y2)
+            r.sread(color)
+            if cmd == pcFillRect:
+              bmp.fillRect(x1, y1, x2, y2, color)
+            else:
+              bmp.strokeRect(x1, y1, x2, y2, color)
+          of pcFillPath:
+            var lines: PathLines
+            var color: ARGBColor
+            var fillRule: CanvasFillRule
+            r.sread(lines)
+            r.sread(color)
+            r.sread(fillRule)
+            bmp.fillPath(lines, color, fillRule)
+          of pcStrokePath:
+            var lines: seq[Line]
+            var color: ARGBColor
+            r.sread(lines)
+            r.sread(color)
+            bmp.strokePath(lines, color)
+          of pcFillText, pcStrokeText:
+            if unifontBitmap == nil:
+              unifontBitmap = loadUnifont(unifont)
+            var text: string
+            var x, y: float64
+            var color: ARGBColor
+            var align: CSSTextAlign
+            r.sread(text)
+            r.sread(x)
+            r.sread(y)
+            r.sread(color)
+            r.sread(align)
+            if cmd == pcFillText:
+              bmp.fillText(text, x, y, color, align)
+            else:
+              bmp.strokeText(text, x, y, color, align)
+      except EOFError, ErrorConnectionReset, ErrorBrokenPipe:
+        break
+    os.sendDataLoop(addr bmp.px[0], bmp.px.len * sizeof(bmp.px[0]))
+  of "encode":
+    os.write("Cha-Control: ConnectionError 1 not supported\n")
+    quit(1)
+
+main()
diff --git a/adapter/img/jebp.nim b/adapter/img/jebp.nim
index 03d72d59..afeb283e 100644
--- a/adapter/img/jebp.nim
+++ b/adapter/img/jebp.nim
@@ -6,9 +6,6 @@ import std/strutils
 import utils/sandbox
 import utils/twtstr
 
-{.passc: "-fno-strict-aliasing".}
-{.passl: "-fno-strict-aliasing".}
-
 {.compile: "jebp.c".}
 
 when sizeof(cint) < 4:
@@ -61,10 +58,12 @@ proc myRead(data: pointer; size: csize_t; user: pointer): csize_t {.cdecl.} =
     n += csize_t(i)
   return n
 
+{.push header: "stb_image_resize.h".}
 proc stbir_resize_uint8(input_pixels: ptr uint8;
   input_w, input_h, input_stride_in_bytes: cint; output_pixels: ptr uint8;
   output_w, output_h, output_stride_in_bytes, num_channels: cint): cint
   {.importc.}
+{.pop.}
 
 proc writeAll(data: pointer; size: int) =
   var n = 0