Created
December 25, 2023 22:22
-
-
Save rosmo/884eed0226c15ee2f18deddf110deeb6 to your computer and use it in GitHub Desktop.
Generate a PNG dynamically using Starlark (eg. for Tidbyt/Pixlet)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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