ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
gen_manga_page_fixture.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# SPDX-License-Identifier: MIT
3# Copyright (c) 2026 Brighton Sikarskie
4"""Bake the ereader_manga demo page fixture.
5
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.
14
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
17firmware.
18
19Regenerate:
20 python3 scripts/gen/gen_manga_page_fixture.py
21"""
22
23from __future__ import annotations
24
25import struct
26import zlib
27from pathlib import Path
28
29PAGE_W = 1536
30PAGE_H = 2048
31TILE = 256
32COLS = PAGE_W // TILE
33ROWS = PAGE_H // TILE
34
35# Tile geometry + emit formatting (named so the arithmetic reads clearly).
36FRAME_PX = 4
37LABEL_SCALE = 8
38BYTES_PER_ROW = 16
39
40# 5x7 blocky font for the tile labels (only the glyphs the labels use).
41FONT = {
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"],
54}
55GLYPH_W = 5
56GLYPH_H = 7
57
58
59def tile_gray(col: int, row: int) -> int:
60 """Distinct, well-separated gray for tile (col, row)."""
61 idx = (col * 3 + row * 5) % 12
62 return 60 + idx * 16 # 60..236
63
64
65class Page:
66 """A flat 8-bit grayscale page the drawing helpers paint into."""
67
68 def __init__(self) -> None:
69 """Allocate the full page, zero-filled -- i.e. black until tiles are drawn.
70
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.
73 """
74 self.px = bytearray(PAGE_W * PAGE_H)
75
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."""
78 rows = FONT.get(ch)
79 if rows is None:
80 return
81 for gy in range(GLYPH_H):
82 for gx in range(GLYPH_W):
83 if rows[gy][gx] != "1":
84 continue
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
91
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)
101
102 def draw_tile(self, col: int, row: int) -> None:
103 """Fill one tile with its solid gray, a black frame, and its label."""
104 tx = col * TILE
105 ty = row * TILE
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)
119
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)
125 return self.px
126
127
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)
133
134
135def encode_png(px: bytes | bytearray) -> bytes:
136 """8-bit grayscale PNG (color type 0), filter type 0 (None) on every row."""
137 raw = bytearray()
138 for y in range(PAGE_H):
139 raw.append(0)
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)
143 return (
144 b"\x89PNG\r\n\x1a\n"
145 + png_chunk(b"IHDR", ihdr)
146 + png_chunk(b"IDAT", idat)
147 + png_chunk(b"IEND", b"")
148 )
149
150
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]
154 dst = (
155 root
156 / "examples"
157 / "ek_ra8d2"
158 / "hw_pending"
159 / "ereader_manga"
160 / "inc"
161 / "mg_page_fixture.h"
162 )
163 lines = [
164 "/**",
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.",
168 " *",
169 " * @details",
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.",
176 " *",
177 " * Regenerate: python3 scripts/gen/gen_manga_page_fixture.py",
178 " *",
179 " * @copyright Copyright (c) 2026 Brighton Sikarskie",
180 " * SPDX-License-Identifier: MIT",
181 " */",
182 "#pragma once",
183 "",
184 "#include <stdint.h>",
185 "",
186 f"/** @brief Baked {len(png)}-byte {PAGE_W}x{PAGE_H} grayscale PNG page. */",
187 "static const uint8_t k_mg_png[] = {",
188 ]
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))
192 lines += [
193 "};",
194 "",
195 "/** @brief Length of ::k_mg_png in bytes. */",
196 f"static const uint32_t k_mg_png_len = {len(png)}U;",
197 ]
198 dst.write_text("\n".join(lines) + "\n", encoding="ascii")
199 return dst, len(png)
200
201
202def main() -> None:
203 """Paint the page, encode it as PNG, and overwrite the baked C header.
204
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.
209
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.
213 """
214 px = Page().build()
215 png = encode_png(px)
216 dst, n = emit_header(png)
217 print(f"wrote {dst} ({n} PNG bytes)")
218
219
220if __name__ == "__main__":
221 main()
void main(void)
The application entry point Reset_Handler hands control to.
Definition main.c:298