about summary refs log tree commit diff stats
path: root/adapter/img
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-08-28 23:57:34 +0200
committerbptato <nincsnevem662@gmail.com>2024-08-29 01:26:47 +0200
commit4ba535852edb9c8ce566cf4e9063e51da3575edd (patch)
tree76384923ec2d4bd25bde52e4abcd7763349f287d /adapter/img
parentc434776fea51c915b235881c8d2fa14567582018 (diff)
downloadchawan-4ba535852edb9c8ce566cf4e9063e51da3575edd.tar.gz
sixel: fix borked approximation scheme
ok, of course if you search for the nearest entry after setting blue
as the lowest index bits the search will be biased towards blue...

let's just skip the trouble of reconstructing closest color relations
by storing hashes in the octree - this way, I can ensure that the final
merged colors are present in the buckets that getColor then looks at

(while we're at it, also fix the color run merging code in quantize.)
Diffstat (limited to 'adapter/img')
-rw-r--r--adapter/img/sixel.nim70
1 files changed, 29 insertions, 41 deletions
diff --git a/adapter/img/sixel.nim b/adapter/img/sixel.nim
index 8f215261..ffab1e2a 100644
--- a/adapter/img/sixel.nim
+++ b/adapter/img/sixel.nim
@@ -97,6 +97,7 @@ type Node {.acyclic.} = ref object
   r: uint32
   g: uint32
   b: uint32
+  hashes: seq[int]
   children: array[8, Node]
 
 proc getIdx(c: RGBColor; level: int): uint8 {.inline.} =
@@ -106,6 +107,13 @@ proc getIdx(c: RGBColor; level: int): uint8 {.inline.} =
     (c.b shr sl) and 1
   return idx
 
+func quantHash(c: RGBColor): int =
+  # take top 3 bits of each component - note this means bits 5..7,
+  # the 8th bit is always 0 (as 100 is the highest color component).
+  return ((int(c.r shr 4) and 7) shl 6) or
+    ((int(c.g shr 4) and 7) shl 3) or
+    (int(c.b shr 4) and 7)
+
 type TrimMap = array[7, seq[Node]]
 
 # Insert a node into the octree.
@@ -125,7 +133,8 @@ proc insert(parent: Node; c: RGBColor; trimMap: var TrimMap; level = 0;
         n: n,
         r: uint32(c.r) * n,
         g: uint32(c.g) * n,
-        b: uint32(c.b) * n
+        b: uint32(c.b) * n,
+        hashes: @[quantHash(c)]
       )
       return true
     else:
@@ -169,10 +178,16 @@ proc trim(trimMap: var TrimMap; K: var int) =
       g += child.g
       b += child.b
       n += child.n
+      for h in child.hashes:
+        if h notin node.hashes:
+          node.hashes.add(h)
       child = nil
       dec k
   node.leaf = true
   node.c = rgb(uint8(r div n), uint8(g div n), uint8(b div n))
+  let h = quantHash(node.c)
+  if h notin node.hashes:
+    node.hashes.add(h)
   node.r = r
   node.g = g
   node.b = b
@@ -203,10 +218,12 @@ proc quantize(s: string; bgcolor: ARGBColor; palette: int): Node =
   for i in 0 ..< s.len div 4:
     let m = i * 4
     let c0 = s.getPixel(m, bgcolor)
-    inc pcs
     if pc0 != c0:
-      K += int(root.insert(c0, trimMap, n = pcs))
+      if pcs > 0:
+        K += int(root.insert(pc0, trimMap, n = pcs))
       pcs = 0
+      pc0 = c0
+    inc pcs
     while K > palette:
       # trim the tree.
       trimMap.trim(K)
@@ -217,23 +234,11 @@ proc quantize(s: string; bgcolor: ARGBColor; palette: int): Node =
       trimMap.trim(K)
   return root
 
-type
-  QuantMap = object
-    map: array[4096, seq[tuple[idx: int; c: RGBColor]]]
-    imap: array[4096, int]
+type QuantMap = array[512, seq[tuple[idx: int; c: RGBColor]]]
 
-  ColorPair = tuple[c: RGBColor; n: uint32]
-
-func quantHash(c: RGBColor): int =
-  # take top 4 bits of each component - note this means bits 4..7,
-  # the 8th bit is always 0 (as 100 is the highest color component).
-  return ((int(c.r shr 3) and 0xF) shl 8) or
-    ((int(c.g shr 3) and 0xF) shl 4) or
-    (int(c.b shr 3) and 0xF)
-
-proc flatten(node: Node; map: var QuantMap; cols: var seq[ColorPair]) =
+proc flatten(node: Node; map: var QuantMap; cols: var seq[Node]) =
   if node.leaf:
-    cols.add((node.c, node.n))
+    cols.add(node)
   else:
     for child in node.children:
       if child != nil:
@@ -241,41 +246,24 @@ proc flatten(node: Node; map: var QuantMap; cols: var seq[ColorPair]) =
 
 proc flatten(node: Node; outs: var string; palette: int): QuantMap =
   var map: QuantMap
-  var cols = newSeqOfCap[ColorPair](palette)
+  var cols = newSeqOfCap[Node](palette)
   node.flatten(map, cols)
   # try to set the most common colors as the smallest numbers (so we write less)
-  cols.sort(proc(a, b: ColorPair): int = cmp(a.n, b.n), order = Descending)
+  cols.sort(proc(a, b: Node): int = cmp(a.n, b.n), order = Descending)
   for n, it in cols:
-    let n = n + 1
+    let n = n + 1 # skip 0 - that's transparent
     let c = it.c
     # 2 is RGB
     outs &= '#' & $n & ";2;" & $c.r & ';' & $c.g & ';' & $c.b
-    let i = quantHash(c)
-    map.map[i].add((n, c))
-  # for empty buckets in the hash map: copy over the closest match
-  var todo: seq[int] = @[]
-  var pi = -9999 # make sure this gets overridden in imap
-  for i, it in map.map.mpairs:
-    if it.len == 0:
-      if pi >= 0:
-        map.imap[i] = pi
-      todo.add(i)
-    else:
-      for j in todo:
-        if abs(j - pi) > abs(j - i):
-          map.imap[j] = i
-      todo.setLen(0)
-      pi = i
+    for i in it.hashes:
+      map[i].add((n, c))
   return map
 
 proc getColor(map: QuantMap; c: RGBColor): int =
   let i = quantHash(c)
   var minDist = uint32.high
   var resIdx = -1
-  var j = i
-  if map.map[j].len == 0:
-    j = map.imap[i]
-  for (idx, ic) in map.map[j]:
+  for (idx, ic) in map[i]:
     let d = uint32(abs(int32(c.r) - int32(ic.r))) +
       uint32(abs(int32(c.g) - int32(ic.g))) +
       uint32(abs(int32(c.b) - int32(ic.b)))