From 81a6c22e5e90466b2a21ebc1590f1903b86319a9 Mon Sep 17 00:00:00 2001 From: nic <139033898+dicnunz@users.noreply.github.com> Date: Thu, 21 May 2026 00:04:59 -0400 Subject: [PATCH] Add original audit lantern pixel art --- assets/pixel-art/audit-lantern-grid.png | Bin 0 -> 1484 bytes demos/pixel-art-demo.mp4 | Bin 0 -> 21224 bytes scripts/generate_pixel_art.py | 186 ++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 assets/pixel-art/audit-lantern-grid.png create mode 100644 demos/pixel-art-demo.mp4 create mode 100644 scripts/generate_pixel_art.py diff --git a/assets/pixel-art/audit-lantern-grid.png b/assets/pixel-art/audit-lantern-grid.png new file mode 100644 index 0000000000000000000000000000000000000000..1014d20a61747a7238a718b4b62a565782312982 GIT binary patch literal 1484 zcmW-heKeGL6vyu~Gt6TcGkWn(Gev2bh%N8TILX_=taq{*#U`iKBtnXZwPCYSEL+~@ zwAn04+u9^qBPy>$<85~`QU*mxDw}4rkNxA``#bmCbMNnd&pn@9o{t-qqDuh)s)zeN zKRjaVhAQK}-6^sMK&jAUA2%RbI$z#WQ@mU4ak*04bL@O!nY|$XNnh7_jWU^j%O-PC z=zZdnaySjm{B+T+dPtJr;4NwRNP8|U`m?`OF>d5t>O?biPC1p{)$V4!_4X&l^&TQj z=z|FY91RfVd%1Pu6M+c?VAug83V+|S0^f+1g)8*Q63r?-7slB(mhIiK0?2uZE!2pnfJ_NEoQNk4JShXBv zgI_HGKS3dzv6f}h1$0<(cFMUxhHL`=@2Z*mL5m7A$h+qY7BtcOGHwUNc)a48b-fS2 zG#QM#F0z=66-Fl_f9&r7z*FW}Me*Wc5BT&UK|mkf&LjV%y@jaburct(lJx1|m8zrG z(2Gu`986oaVK=?~0{%kbrletdJc|NHTMS@`8qkB>Au^10!G@(Yb4b_ihS~;m$KdWI zc-OH1=xN9YvruI*FfQYZ&z!}F+^v`*z*!`7vT1`I`$RcOd`=Anzpq%*!G;XX3F<7a zT_TJWEX~iKPk*cUFmkPJM$BCvO>FVHVdqvmKtugQTW{e#V zbGQQN+BkxR(H@MXBAX@`Nt{|k3znlJ+KEu0V}E`mMi!7b;*Bw2aUhHgrK$L_02pQ9 zvL-9>8|^r{mIS!T%CKtAFlA+wHD}m@=Voph+2XbqBgK%BRHY9BHO!XAs}1ajfz<65 zNro6f^g0ETE@!9MUK07K4_G}z=eiwcD@4VH{DwU@5?IN~ScQLEU2kO%acCBClf!kwfV5}P@z4=XEKt@~ zB^HK4bvf5h)&~5-anAe)ffn%M(4C2IJhfXJ?L+*W;~+++5j9M7Z%hY zQki3q@|nVMx%Y+6M+=QW-*CbRstx%m2AIER{r)+PS}oVuxpw(M zOwJO@ksF!E;@heSb@|4rh*hGZaGZH8a0@FHk=k;;QOwc@ zPL4fQL_Y~C$=J5+wIe~3it|o%IT5?KwB8+)HGZ2^S($o9l?M}mt?g)j>Qk^NLK6%R!!GD^87-d4n0 zrPnAO3h_FCQQ)lOP&948E$CY%;J9Isv(@knKOhTo4tyk*(Kq!mOI07^De!Rh*>}^0 GpZOoNAxrT9 literal 0 HcmV?d00001 diff --git a/demos/pixel-art-demo.mp4 b/demos/pixel-art-demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..c9b25c31b3d3bf7feb2380afbce391d2fcf1f9c8 GIT binary patch literal 21224 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 literal 0 HcmV?d00001 diff --git a/scripts/generate_pixel_art.py b/scripts/generate_pixel_art.py new file mode 100644 index 000000000..99547f68e --- /dev/null +++ b/scripts/generate_pixel_art.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +"""Generate the original pixel-art bounty asset for issue #80.""" + +from __future__ import annotations + +import struct +import zlib +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +OUT = ROOT / "assets" / "pixel-art" / "audit-lantern-grid.png" + +WIDTH = 128 +HEIGHT = 128 + +PALETTE = { + "void": (10, 13, 20, 255), + "deep": (16, 23, 34, 255), + "grid": (28, 63, 70, 255), + "trace": (62, 172, 140, 255), + "trace_hi": (125, 238, 194, 255), + "gold": (235, 184, 76, 255), + "gold_hi": (255, 226, 132, 255), + "amber": (211, 112, 57, 255), + "shadow": (51, 36, 36, 255), + "glass": (115, 196, 213, 255), + "white": (235, 244, 241, 255), +} + + +def blank() -> list[list[tuple[int, int, int, int]]]: + return [[PALETTE["void"] for _ in range(WIDTH)] for _ in range(HEIGHT)] + + +def dot(img: list[list[tuple[int, int, int, int]]], x: int, y: int, color: str) -> None: + if 0 <= x < WIDTH and 0 <= y < HEIGHT: + img[y][x] = PALETTE[color] + + +def rect( + img: list[list[tuple[int, int, int, int]]], + x: int, + y: int, + w: int, + h: int, + color: str, +) -> None: + for yy in range(y, y + h): + for xx in range(x, x + w): + dot(img, xx, yy, color) + + +def line( + img: list[list[tuple[int, int, int, int]]], + x0: int, + y0: int, + x1: int, + y1: int, + color: str, +) -> None: + dx = abs(x1 - x0) + sx = 1 if x0 < x1 else -1 + dy = -abs(y1 - y0) + sy = 1 if y0 < y1 else -1 + err = dx + dy + while True: + dot(img, x0, y0, color) + if x0 == x1 and y0 == y1: + return + twice = 2 * err + if twice >= dy: + err += dy + x0 += sx + if twice <= dx: + err += dx + y0 += sy + + +def fill_circle( + img: list[list[tuple[int, int, int, int]]], cx: int, cy: int, radius: int, color: str +) -> None: + r2 = radius * radius + for y in range(cy - radius, cy + radius + 1): + for x in range(cx - radius, cx + radius + 1): + if (x - cx) ** 2 + (y - cy) ** 2 <= r2: + dot(img, x, y, color) + + +def pixelate_shadow(img: list[list[tuple[int, int, int, int]]]) -> None: + for y in range(0, HEIGHT, 4): + for x in range(0, WIDTH, 4): + if ((x * 17 + y * 23) % 37) in {0, 1, 2}: + rect(img, x, y, 2, 2, "deep") + + +def draw_grid(img: list[list[tuple[int, int, int, int]]]) -> None: + for x in range(8, WIDTH, 12): + line(img, x, 10, x, 116, "grid") + for y in range(14, HEIGHT, 12): + line(img, 8, y, 119, y, "grid") + + traces = [ + (14, 97, 45, 97, 45, 76), + (82, 97, 111, 97, 111, 70), + (25, 35, 48, 35, 48, 55), + (101, 31, 82, 31, 82, 54), + (21, 74, 37, 74, 37, 62), + (92, 74, 109, 74, 109, 58), + ] + for x0, y0, x1, y1, x2, y2 in traces: + line(img, x0, y0, x1, y1, "trace") + line(img, x1, y1, x2, y2, "trace") + fill_circle(img, x0, y0, 2, "trace_hi") + fill_circle(img, x2, y2, 2, "trace_hi") + + +def draw_lantern(img: list[list[tuple[int, int, int, int]]]) -> None: + fill_circle(img, 64, 62, 29, "amber") + fill_circle(img, 64, 62, 23, "gold") + fill_circle(img, 64, 62, 15, "gold_hi") + + rect(img, 45, 42, 38, 48, "shadow") + rect(img, 49, 46, 30, 40, "glass") + rect(img, 53, 50, 22, 32, "deep") + rect(img, 57, 54, 14, 24, "gold_hi") + rect(img, 60, 58, 8, 16, "white") + + rect(img, 42, 39, 44, 5, "gold") + rect(img, 47, 35, 34, 4, "gold_hi") + rect(img, 55, 30, 18, 5, "gold") + line(img, 58, 30, 58, 22, "gold_hi") + line(img, 70, 30, 70, 22, "gold_hi") + line(img, 58, 22, 70, 22, "gold") + + rect(img, 42, 90, 44, 6, "gold") + rect(img, 49, 96, 30, 4, "gold_hi") + rect(img, 55, 100, 18, 4, "gold") + + line(img, 50, 46, 78, 82, "trace_hi") + line(img, 78, 46, 50, 82, "trace_hi") + for x, y in [(52, 49), (75, 49), (52, 79), (75, 79)]: + fill_circle(img, x, y, 2, "white") + + +def draw_foreground_marks(img: list[list[tuple[int, int, int, int]]]) -> None: + for x, y in [(20, 106), (33, 111), (94, 110), (108, 104), (17, 25), (111, 24)]: + rect(img, x, y, 5, 5, "gold") + rect(img, x + 1, y + 1, 3, 3, "gold_hi") + for x, y in [(30, 84), (98, 84), (64, 111), (64, 18)]: + fill_circle(img, x, y, 3, "trace_hi") + fill_circle(img, x, y, 1, "white") + + +def write_png(path: Path, img: list[list[tuple[int, int, int, int]]]) -> None: + raw = b"".join(b"\x00" + b"".join(bytes(pixel) for pixel in row) for row in img) + + def chunk(kind: bytes, data: bytes) -> bytes: + return ( + struct.pack(">I", len(data)) + + kind + + data + + struct.pack(">I", zlib.crc32(kind + data) & 0xFFFFFFFF) + ) + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes( + b"\x89PNG\r\n\x1a\n" + + chunk(b"IHDR", struct.pack(">IIBBBBB", WIDTH, HEIGHT, 8, 6, 0, 0, 0)) + + chunk(b"IDAT", zlib.compress(raw, 9)) + + chunk(b"IEND", b"") + ) + + +def main() -> None: + img = blank() + pixelate_shadow(img) + draw_grid(img) + draw_lantern(img) + draw_foreground_marks(img) + write_png(OUT, img) + print(OUT) + + +if __name__ == "__main__": + main()