nandi/freeqsay-nimpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/nandi/freeqsay-nim.git
git clone ssh://git@rickub.com/nandi/freeqsay-nim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

png.nim · 86 lines · 2.6 KBNim Blame HistoryRaw
freeqsay: Nim port of the Clojure implementation fa53aa2 nandi 10h ago1## Minimal RGBA → PNG encoder. Optional path; ANSI is the default.
2##
3## Uses stored (uncompressed) deflate blocks so the encoder stays
4## dependency-free — the result is a valid zlib stream every decoder accepts.
5
6import ./ansi
7import ./avatar
8
9const crcTable = block:
10 var t: array[256, uint32]
11 for n in 0 ..< 256:
12 var c = uint32(n)
13 for _ in 0 ..< 8:
14 c = if (c and 1) != 0: 0xedb88320'u32 xor (c shr 1) else: c shr 1
15 t[n] = c
16 t
17
18func crc32(bs: openArray[byte]): uint32 =
19 var c = 0xffffffff'u32
20 for b in bs:
21 c = crcTable[(c xor uint32(b)) and 0xff] xor (c shr 8)
22 c xor 0xffffffff'u32
23
24func adler32(bs: openArray[byte]): uint32 =
25 var a = 1'u32
26 var b = 0'u32
27 for x in bs:
28 a = (a + uint32(x)) mod 65521
29 b = (b + a) mod 65521
30 (b shl 16) or a
31
32func u32(n: uint32): seq[byte] =
33 @[byte((n shr 24) and 255), byte((n shr 16) and 255),
34 byte((n shr 8) and 255), byte(n and 255)]
35
36func pngChunk(typ: string, data: openArray[byte]): seq[byte] =
37 var body: seq[byte]
38 for c in typ: body.add byte(c)
39 body.add @data
40 u32(uint32(data.len)) & body & u32(crc32(body))
41
42func deflateStored(bs: openArray[byte]): seq[byte] =
43 ## zlib stream wrapping `bs` in stored deflate blocks (no compression).
44 result = @[0x78'u8, 0x01'u8] # zlib header, no preset dict
45 var i = 0
46 while true:
47 let
48 len = min(65535, bs.len - i)
49 last = i + len >= bs.len
50 nlen = not uint16(len)
51 result.add byte(if last: 1 else: 0)
52 result.add byte(len and 255)
53 result.add byte((len shr 8) and 255)
54 result.add byte(nlen and 255)
55 result.add byte((nlen shr 8) and 255)
56 for k in 0 ..< len:
57 result.add bs[i + k]
58 i += len
59 if last: break
60 result.add u32(adler32(bs))
61
62func rgbaToPng*(img: Rgba): seq[byte] =
63 ## Encode RGBA into PNG bytes (color type 6, 8-bit).
64 let stride = img.width * 4
65 var raw: seq[byte]
66 for y in 0 ..< img.height:
67 raw.add 0'u8 # filter None
68 for x in 0 ..< stride:
69 raw.add byte(img.data[y * stride + x])
70 let ihdr = u32(uint32(img.width)) & u32(uint32(img.height)) &
71 @[8'u8, 6'u8, 0'u8, 0'u8, 0'u8]
72 @[137'u8, 80, 78, 71, 13, 10, 26, 10] &
73 pngChunk("IHDR", ihdr) &
74 pngChunk("IDAT", deflateStored(raw)) &
75 pngChunk("IEND", @[])
76
77proc writePng*(bs: openArray[byte], path: string) =
78 ## Write PNG bytes to `path`.
79 var f = open(path, fmWrite)
80 defer: f.close()
81 if bs.len > 0:
82 discard f.writeBuffer(unsafeAddr bs[0], bs.len)
83
84func didToPng*(did: string, scale = 16, facing = fSouth, frame = 0): seq[byte] =
85 ## DID → PNG bytes (no network).
86 rgbaToPng(scaleRgba(spriteToRgba(renderSpritePixels(deriveAvatar(did), facing, frame)), scale))