| freeqsay: Nim port of the Clojure implementation fa53aa2 nandi 23h ago | 1 | ## 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 | |
| 6 | import ./ansi |
| 7 | import ./avatar |
| 8 | |
| 9 | const 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 | |
| 18 | func 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 | |
| 24 | func 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 | |
| 32 | func 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 | |
| 36 | func 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 | |
| 42 | func 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 | |
| 62 | func 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 | |
| 77 | proc 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 | |
| 84 | func 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)) |