4"""Bake the ereader_manga demo page fixture.
6Emits examples/ek_ra8d2/hw_pending/ereader_manga/inc/mg_page_fixture.h: one large
78-bit grayscale PNG (bigger than the 1024x600 panel) whose content is a
8deterministic tile grid -- each 256x256 tile is a distinct solid gray with a
9black inner frame and a big blocky "CcRr" label -- so that panning the
10viewport across the page visibly changes which labels are on screen. The page
11is the source jof_produce() transcodes into a JOF atlas at boot;
12it decodes to the same pixels on host, ra8_emulator and silicon, so the render
13hash in the app banner is identical everywhere.
15Solid tile blocks + sparse labels compress to a few KiB of PNG, which keeps
16the baked .rodata blob small enough to sit in the 1 MB code MRAM alongside the
20 python3 scripts/gen/gen_manga_page_fixture.py
23from __future__
import annotations
27from pathlib
import Path
42 "0": [
"01110",
"10001",
"10011",
"10101",
"11001",
"10001",
"01110"],
43 "1": [
"00100",
"01100",
"00100",
"00100",
"00100",
"00100",
"01110"],
44 "2": [
"01110",
"10001",
"00001",
"00010",
"00100",
"01000",
"11111"],
45 "3": [
"11111",
"00010",
"00100",
"00010",
"00001",
"10001",
"01110"],
46 "4": [
"00010",
"00110",
"01010",
"10010",
"11111",
"00010",
"00010"],
47 "5": [
"11111",
"10000",
"11110",
"00001",
"00001",
"10001",
"01110"],
48 "6": [
"00110",
"01000",
"10000",
"11110",
"10001",
"10001",
"01110"],
49 "7": [
"11111",
"00001",
"00010",
"00100",
"01000",
"01000",
"01000"],
50 "8": [
"01110",
"10001",
"10001",
"01110",
"10001",
"10001",
"01110"],
51 "9": [
"01110",
"10001",
"10001",
"01111",
"00001",
"00010",
"01100"],
52 "C": [
"01110",
"10001",
"10000",
"10000",
"10000",
"10001",
"01110"],
53 "R": [
"11110",
"10001",
"10001",
"11110",
"10100",
"10010",
"10001"],
59def tile_gray(col: int, row: int) -> int:
60 """Distinct, well-separated gray for tile (col, row)."""
61 idx = (col * 3 + row * 5) % 12
66 """A flat 8-bit grayscale page the drawing helpers paint into."""
68 def __init__(self) -> None:
69 """Allocate the full page, zero-filled -- i.e. black until tiles are drawn.
71 Every pixel is overwritten by ``build``, so the initial value is not
72 load-bearing; it matters only if a caller draws tiles selectively.
74 self.px = bytearray(PAGE_W * PAGE_H)
76 def draw_glyph(self, x0: int, y0: int, ch: str, scale: int, val: int) ->
None:
77 """Blit one scaled blocky glyph at (x0, y0) into the page."""
81 for gy
in range(GLYPH_H):
82 for gx
in range(GLYPH_W):
83 if rows[gy][gx] !=
"1":
85 for sy
in range(scale):
86 for sx
in range(scale):
87 x = x0 + gx * scale + sx
88 y = y0 + gy * scale + sy
89 if 0 <= x < PAGE_W
and 0 <= y < PAGE_H:
90 self.px[y * PAGE_W + x] = val
92 def draw_label(self, tx: int, ty: int, col: int, row: int) ->
None:
93 """Draw 'C<col>R<row>' centred in tile (tx,ty), black on the tile fill."""
94 label = f
"C{col}R{row}"
95 text_w = len(label) * (GLYPH_W + 1) * LABEL_SCALE
96 x0 = tx + (TILE - text_w) // 2
97 y0 = ty + (TILE - GLYPH_H * LABEL_SCALE) // 2
98 for i, ch
in enumerate(label):
99 gx0 = x0 + i * (GLYPH_W + 1) * LABEL_SCALE
100 self.draw_glyph(gx0, y0, ch, LABEL_SCALE, 0)
102 def draw_tile(self, col: int, row: int) ->
None:
103 """Fill one tile with its solid gray, a black frame, and its label."""
106 g = tile_gray(col, row)
107 for y
in range(ty, ty + TILE):
108 base = y * PAGE_W + tx
109 for x
in range(TILE):
110 self.px[base + x] = g
111 for t
in range(FRAME_PX):
112 for x
in range(tx, tx + TILE):
113 self.px[(ty + t) * PAGE_W + x] = 0
114 self.px[(ty + TILE - 1 - t) * PAGE_W + x] = 0
115 for y
in range(ty, ty + TILE):
116 self.px[y * PAGE_W + tx + t] = 0
117 self.px[y * PAGE_W + tx + TILE - 1 - t] = 0
118 self.draw_label(tx, ty, col, row)
120 def build(self) -> bytearray:
121 """Paint every tile and return the raw grayscale pixel buffer."""
122 for row
in range(ROWS):
123 for col
in range(COLS):
124 self.draw_tile(col, row)
128def png_chunk(tag: bytes, data: bytes) -> bytes:
129 """Wrap one PNG chunk (length + tag + data + CRC-32)."""
130 out = struct.pack(
">I", len(data)) + tag + data
131 crc = zlib.crc32(tag + data) & 0xFFFFFFFF
132 return out + struct.pack(
">I", crc)
135def encode_png(px: bytes | bytearray) -> bytes:
136 """8-bit grayscale PNG (color type 0), filter type 0 (None) on every row."""
138 for y
in range(PAGE_H):
140 raw.extend(px[y * PAGE_W : (y + 1) * PAGE_W])
141 ihdr = struct.pack(
">IIBBBBB", PAGE_W, PAGE_H, 8, 0, 0, 0, 0)
142 idat = zlib.compress(bytes(raw), 9)
145 + png_chunk(b
"IHDR", ihdr)
146 + png_chunk(b
"IDAT", idat)
147 + png_chunk(b
"IEND", b
"")
151def emit_header(png: bytes) -> tuple[Path, int]:
152 """Write the generated pure-ASCII C header holding the baked PNG bytes."""
153 root = Path(__file__).resolve().parents[2]
161 /
"mg_page_fixture.h"
165 " * @file mg_page_fixture.h",
166 " * @brief Baked demo manga page for the ereader_manga viewer.",
167 " * @generated by scripts/gen/gen_manga_page_fixture.py -- do not edit by hand.",
170 f
" * One {PAGE_W}x{PAGE_H} 8-bit grayscale PNG, larger than the 1024x600 panel, laid out",
171 f
" * as a {COLS}x{ROWS} grid of {TILE}px tiles. Each tile is a distinct solid gray with a",
172 ' * black inner frame and a big blocky "C<col>R<row>" label, so panning the',
173 " * viewport across the page visibly changes which labels are on screen. The",
174 " * page is the source jof_produce() transcodes into a JOF atlas",
175 " * at boot; it decodes to identical pixels on host, ra8_emulator and silicon.",
177 " * Regenerate: python3 scripts/gen/gen_manga_page_fixture.py",
179 " * @copyright Copyright (c) 2026 Brighton Sikarskie",
180 " * SPDX-License-Identifier: MIT",
184 "#include <stdint.h>",
186 f
"/** @brief Baked {len(png)}-byte {PAGE_W}x{PAGE_H} grayscale PNG page. */",
187 "static const uint8_t k_mg_png[] = {",
189 for start
in range(0, len(png), BYTES_PER_ROW):
190 chunk = png[start : start + BYTES_PER_ROW]
191 lines.append(
" " +
" ".join(f
"0x{byte:02X}," for byte
in chunk))
195 "/** @brief Length of ::k_mg_png in bytes. */",
196 f
"static const uint32_t k_mg_png_len = {len(png)}U;",
198 dst.write_text(
"\n".join(lines) +
"\n", encoding=
"ascii")
203 """Paint the page, encode it as PNG, and overwrite the baked C header.
205 Takes no arguments and writes to a path derived from this file's location,
206 so it always targets the ereader_manga app in the same checkout -- running
207 it from anywhere regenerates the same fixture rather than one relative to
208 the working directory.
210 The write is unconditional; the fixture being deterministic is what makes
211 that safe, since regenerating an unchanged page leaves the file
212 byte-identical and git sees no diff.
216 dst, n = emit_header(png)
217 print(f
"wrote {dst} ({n} PNG bytes)")
220if __name__ ==
"__main__":
void main(void)
The application entry point Reset_Handler hands control to.