## Minimal RGBA → PNG encoder. Optional path; ANSI is the default. ## ## Uses stored (uncompressed) deflate blocks so the encoder stays ## dependency-free — the result is a valid zlib stream every decoder accepts. import ./ansi import ./avatar const crcTable = block: var t: array[256, uint32] for n in 0 ..< 256: var c = uint32(n) for _ in 0 ..< 8: c = if (c and 1) != 0: 0xedb88320'u32 xor (c shr 1) else: c shr 1 t[n] = c t func crc32(bs: openArray[byte]): uint32 = var c = 0xffffffff'u32 for b in bs: c = crcTable[(c xor uint32(b)) and 0xff] xor (c shr 8) c xor 0xffffffff'u32 func adler32(bs: openArray[byte]): uint32 = var a = 1'u32 var b = 0'u32 for x in bs: a = (a + uint32(x)) mod 65521 b = (b + a) mod 65521 (b shl 16) or a func u32(n: uint32): seq[byte] = @[byte((n shr 24) and 255), byte((n shr 16) and 255), byte((n shr 8) and 255), byte(n and 255)] func pngChunk(typ: string, data: openArray[byte]): seq[byte] = var body: seq[byte] for c in typ: body.add byte(c) body.add @data u32(uint32(data.len)) & body & u32(crc32(body)) func deflateStored(bs: openArray[byte]): seq[byte] = ## zlib stream wrapping `bs` in stored deflate blocks (no compression). result = @[0x78'u8, 0x01'u8] # zlib header, no preset dict var i = 0 while true: let len = min(65535, bs.len - i) last = i + len >= bs.len nlen = not uint16(len) result.add byte(if last: 1 else: 0) result.add byte(len and 255) result.add byte((len shr 8) and 255) result.add byte(nlen and 255) result.add byte((nlen shr 8) and 255) for k in 0 ..< len: result.add bs[i + k] i += len if last: break result.add u32(adler32(bs)) func rgbaToPng*(img: Rgba): seq[byte] = ## Encode RGBA into PNG bytes (color type 6, 8-bit). let stride = img.width * 4 var raw: seq[byte] for y in 0 ..< img.height: raw.add 0'u8 # filter None for x in 0 ..< stride: raw.add byte(img.data[y * stride + x]) let ihdr = u32(uint32(img.width)) & u32(uint32(img.height)) & @[8'u8, 6'u8, 0'u8, 0'u8, 0'u8] @[137'u8, 80, 78, 71, 13, 10, 26, 10] & pngChunk("IHDR", ihdr) & pngChunk("IDAT", deflateStored(raw)) & pngChunk("IEND", @[]) proc writePng*(bs: openArray[byte], path: string) = ## Write PNG bytes to `path`. var f = open(path, fmWrite) defer: f.close() if bs.len > 0: discard f.writeBuffer(unsafeAddr bs[0], bs.len) func didToPng*(did: string, scale = 16, facing = fSouth, frame = 0): seq[byte] = ## DID → PNG bytes (no network). rgbaToPng(scaleRgba(spriteToRgba(renderSpritePixels(deriveAvatar(did), facing, frame)), scale))