Skip to content

Instantly share code, notes, and snippets.

@rosmo
Created December 25, 2023 22:22
Show Gist options
  • Save rosmo/884eed0226c15ee2f18deddf110deeb6 to your computer and use it in GitHub Desktop.
Save rosmo/884eed0226c15ee2f18deddf110deeb6 to your computer and use it in GitHub Desktop.
Generate a PNG dynamically using Starlark (eg. for Tidbyt/Pixlet)
load("render.star", "render")
load("encoding/base64.star", "base64")
load("hash.star", "hash")
# Author: Taneli Leppä <rosmo@rosmo.fi>
# PNG encoder from: https://github.com/miloyip/svpng
# Base64 encoder from: https://gist.github.com/caseyscarborough/8467877
def generate_png(w, h):
t = [ 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c ] # CRC32 table
c = 0xffffffff
a = 1
b = 0
output = [0x89, 80, 78, 71, 13, 10, 0x1a, 10] # Start with magic header
alpha = False
b64tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def png_u32(u):
output.append(u >> 24)
output.append((u >> 16) & 255)
output.append((u >> 8) & 255)
output.append(u & 255)
def png_u32c(c, u):
c = png_u8c(c, (u >> 24))
c = png_u8c(c, (u >> 16) & 255)
c = png_u8c(c, (u >> 8) & 255)
c = png_u8c(c, u & 255)
return c
def png_u16lc(c, u):
c = png_u8c(c, (u & 255))
c = png_u8c(c, (u >> 8) & 255)
return c
def png_u8c(c, u):
output.append(u)
c ^= u
c = (c >> 4) ^ t[c & 15]
c = (c >> 4) ^ t[c & 15]
return c
def png_u8adler(c, a, b, u):
c = png_u8c(c, u)
a = (a + u) % 65521
b = (b + a) % 65521
return c, a, b
png_u32(13) # Length of header
c = png_u8c(c, 73) # I
c = png_u8c(c, 72) # H
c = png_u8c(c, 68) # D
c = png_u8c(c, 82) # R
c = png_u32c(c, w) # Width of image
c = png_u32c(c, h) # Height of image
c = png_u8c(c, 8) # Color depth 8
c = png_u8c(c, 6 if alpha else 2) # No alpha (use 6 for alpha)
c = png_u8c(c, 0) # Compression=Deflate, Filter=No, Interlace=No (3 bytes)
c = png_u8c(c, 0) # Compression=Deflate, Filter=No, Interlace=No (3 bytes)
c = png_u8c(c, 0) # Compression=Deflate, Filter=No, Interlace=No (3 bytes)
png_u32(~c) # Add CRC for end
c = 0xffffffff
p = w * (4 if alpha else 3) + 1
png_u32(2 + h * (5 + p) + 4) # Length of header
c = png_u8c(c, 73) # I
c = png_u8c(c, 68) # D
c = png_u8c(c, 65) # A
c = png_u8c(c, 84) # T
c = png_u8c(c, 0x78) # Deflate block
c = png_u8c(c, 0x01)
for y in range(h):
c = png_u8c(c, 1 if y == (h - 1) else 0) # 1 for last block, 0 otherwise
c = png_u16lc(c, p) # Block size
c = png_u16lc(c, ~p) # And complement
c, a, b = png_u8adler(c, a, b, 0)
for x in range(w):
c, a, b = png_u8adler(c, a, b, 255) # r
c, a, b = png_u8adler(c, a, b, 0) # g
c, a, b = png_u8adler(c, a, b, 0) # b
c = png_u32c(c, (b << 16) | a) # Deflate block end with adler
png_u32(~c) # Add CRC for end
c = 0xffffffff
png_u32(0) # Length of header
c = png_u8c(c, 73) # I
c = png_u8c(c, 69) # E
c = png_u8c(c, 78) # N
c = png_u8c(c, 68) # D
png_u32(~c) # Add CRC for end
if len(output) % 3 != 0: # Hack to pad for base64
for _ in range(len(output) % 3):
c = png_u8c(c, 0)
z = ""
i = 0
while i < len(output):
z += b64tbl[output[i] >> 2];
z += b64tbl[((output[i] & 0x03) << 4) | ((output[i + 1] & 0xf0) >> 4)]
z += b64tbl[((output[i + 1] & 0x0f) << 2) | ((output[i + 2] & 0xc0) >> 6)]
z += b64tbl[(output[i + 2] & 0x3f)]
i += 3
return z
def main(config):
img = generate_png(32, 32)
print(hash.md5(img))
return render.Root(
child = render.Image(src=base64.decode(img))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment