diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-29 10:14:05 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-29 10:14:05 +0200 |
| commit | fe118f0dffcd6f80c20a6c69b33232f2207ebf1f (patch) | |
| tree | 82b977e068edad6b4669ebf4e05fbf5735fcc219 | |
| parent | 7a243c8fca38b14d20124da064891b4eea5a5636 (diff) | |
| download | grub-theme-udt-fe118f0dffcd6f80c20a6c69b33232f2207ebf1f.tar.gz grub-theme-udt-fe118f0dffcd6f80c20a6c69b33232f2207ebf1f.zip | |
Add build script with PNG encoder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rwxr-xr-x | build | 70 |
1 files changed, 70 insertions, 0 deletions
@@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# build: assemble the installable GRUB theme into dist/udt/. +# Copyright (C) 2026 Danilo M. <danix@danix.xyz> +# Licensed under the GNU General Public License v2 only. +"""Assemble dist/udt/ from the theme.txt udt-palette rendered. + +Usage: build [--selftest] + +Writes the PNG slices, the .pf2 fonts and a GRUB-readable copy of the +current wallpaper next to theme.txt. Everything is staged in a temporary +directory and swapped in at the end, so a failed run leaves the previous +dist/udt/ untouched. Never touches /boot: root copies dist/udt/ there. +""" +import struct +import sys +import zlib + +PNG_SIG = b"\x89PNG\r\n\x1a\n" + + +def chunk(tag, data): + return (struct.pack(">I", len(data)) + tag + data + + struct.pack(">I", zlib.crc32(tag + data))) + + +def png(w, h, rgba): + """A w x h PNG of one colour: 8-bit RGBA, not interlaced, which GRUB reads.""" + row = b"\0" + bytes(rgba) * w # filter byte 0, then the pixels + return (PNG_SIG + + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0)) + + chunk(b"IDAT", zlib.compress(row * h)) + + chunk(b"IEND", b"")) + + +def unpng(data): + """Decode what png() writes: (width, height, set of RGBA pixels).""" + assert data[:8] == PNG_SIG + pos, idat = 8, b"" + while pos < len(data): + n, tag = struct.unpack(">I4s", data[pos:pos + 8]) + body = data[pos + 8:pos + 8 + n] + assert struct.unpack(">I", data[pos + 8 + n:pos + 12 + n])[0] == zlib.crc32(tag + body) + if tag == b"IHDR": + w, h = struct.unpack(">II", body[:8]) + elif tag == b"IDAT": + idat += body + pos += 12 + n + raw = zlib.decompress(idat) + stride = 1 + 4 * w + px = set() + for y in range(h): + row = raw[y * stride:(y + 1) * stride] + assert row[0] == 0 + px |= {tuple(row[1 + 4 * x:5 + 4 * x]) for x in range(w)} + return w, h, px + + +def selftest(): + w, h, px = unpng(png(3, 2, (0x12, 0x34, 0x56, 0xd9))) + assert (w, h, px) == (3, 2, {(0x12, 0x34, 0x56, 0xd9)}), (w, h, px) + print("selftest ok") + + +def main(): + if sys.argv[1:] == ["--selftest"]: + return selftest() + + +if __name__ == "__main__": + main() |
