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
 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))