1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
## 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))
|