ra8-firmware
0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
rabook_gray8_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
"""Generate the ereader_rabook full-resolution gray8 image fixture (#476).
5
6
The ereader_rabook HIL/ra8_emulator gate proves the compiled-book path. This
7
generator bakes a second, tiny `.rabook` blob whose one raster image is stored
8
at ``PIXFMT_GRAY8`` -- the full-resolution, continuous-tone representation the
9
compiler retains for zoomable content (#476), never the panel-quantised 4bpp
10
form. The blob is emitted INFLATED (``BlobBuilder.serialize`` output, no RBKC
11
container) so the gate can ``book_validate`` and walk it with no
12
decompressor, exactly like the sibling ``rabook_fixture.h``.
13
14
The image is a synthetic diagonal grayscale ramp: its pixel values sweep the
15
full 0-255 range, so many land strictly between two 16-level gray4 steps. A
16
gray4 store would round them to the grid; keeping them verbatim is the proof
17
that the compiled blob retains continuous tone at full source resolution. The
18
image itself is grayscale ("L" mode) so the host decode is deterministic across
19
Pillow versions (no colour->luma fold), which keeps the baked bytes -- and the
20
ra8_emulator framebuffer golden derived from them -- reproducible.
21
22
Regenerate with ``just tools::rabook_fixture`` after any format change,
23
then re-pin the ereader_rabook ``img`` framebuffer golden (its hil.conf
24
``HIL_EXPECT``) in the same change.
25
26
@copyright Copyright (c) 2026 Brighton Sikarskie
27
SPDX-License-Identifier: MIT
28
"""
29
30
from
__future__
import
annotations
31
32
import
io
33
import
sys
34
from
pathlib
import
Path
35
36
from
PIL
import
Image
37
38
_REPO_ROOT = Path(__file__).resolve().parents[2]
39
_EPUB_COMPILE = _REPO_ROOT /
"tools"
/
"epub_compile"
/
"src"
40
sys.path.insert(0, str(_EPUB_COMPILE))
41
42
from
rabook_blob
import
BlobBuilder
# noqa: E402 -- imagepack path added above
43
from
rabook_format
import
PIXFMT_GRAY8
# noqa: E402 -- imagepack path added above
44
45
# Image geometry: small enough to render 1:1 inside the gate's 128x160 RGB565
46
# framebuffer, large enough that the diagonal ramp visits many off-grid tones.
47
_IMG_W = 96
48
_IMG_H = 48
49
_GRAY_MAX = 255
# Peak 8-bit gray value.
50
_HALF = 2
# Averaging divisor for the two-axis ramp.
51
_ARRAY_LINE_LIMIT = 96
# Soft wrap width for the emitted C byte rows.
52
53
_OUT = (
54
_REPO_ROOT
55
/
"examples"
56
/
"ek_ra8d2"
57
/
"hw_validated"
58
/
"hil"
59
/
"ereader_rabook"
60
/
"inc"
61
/
"rabook_gray8_fixture.h"
62
)
63
64
65
def
_ramp_png() -> bytes:
66
"""Encode a WxH grayscale diagonal ramp as PNG bytes.
67
68
Returns:
69
PNG-encoded bytes of an "L" (8-bit gray) image whose pixel (x, y) is the
70
average of a horizontal and a vertical 0-255 ramp, so tone varies
71
smoothly on both axes and covers values off the 16-level gray4 grid.
72
"""
73
im = Image.new(
"L"
, (_IMG_W, _IMG_H))
74
px = [
75
((x * _GRAY_MAX) // (_IMG_W - 1) + (y * _GRAY_MAX) // (_IMG_H - 1)) // _HALF
76
for
y
in
range(_IMG_H)
77
for
x
in
range(_IMG_W)
78
]
79
im.putdata(px)
80
buf = io.BytesIO()
81
im.save(buf,
"PNG"
)
82
return
buf.getvalue()
83
84
85
def
_emit_array(name: str, data: bytes) -> str:
86
"""Render bytes as a 4-byte-aligned ``static const uint8_t`` C array.
87
88
``alignas(4)`` matters: the firmware casts into the blob to read 32-bit
89
header fields, and an unaligned load faults on the target. Deterministic for
90
identical input, so the generated header commits and diffs cleanly.
91
92
Args:
93
name: C identifier for the array.
94
data: Bytes to emit.
95
96
Returns:
97
The declaration as a newline-joined string (no trailing newline).
98
"""
99
out = [f
"alignas(4) static const uint8_t {name}[{len(data)}U] = {{"
]
100
line =
" "
101
for
b
in
data:
102
line += f
"0x{b:02X}U,"
103
if
len(line) >= _ARRAY_LINE_LIMIT:
104
out.append(line)
105
line =
" "
106
if
line.strip():
107
out.append(line)
108
out.append(
"};"
)
109
return
"\n"
.join(out)
110
111
112
def
_build_blob() -> bytes:
113
"""Compile the one-image gray8 book to an inflated flat `.rabook` blob."""
114
bb = BlobBuilder()
115
idx = bb.add_raster_image(
"ramp.png"
, _ramp_png(), pixel_format=PIXFMT_GRAY8)
116
bb.cover_index = idx
117
meta = {
118
"title"
:
"gray8 ramp"
,
119
"author"
:
""
,
120
"language"
:
"en"
,
121
"identifier"
:
"urn:ra8:rabook-gray8-fixture"
,
122
}
123
return
bb.serialize(meta)
124
125
126
def
main
() -> int:
127
"""Emit the gray8 fixture header; return a process exit code."""
128
blob = _build_blob()
129
header =
"\n"
.join(
130
[
131
"/**"
,
132
" * @file rabook_gray8_fixture.h"
,
133
" * @brief Baked, INFLATED .rabook flat blob carrying one full-resolution"
,
134
" * gray8 image (#476)."
,
135
" * @generated by scripts/gen/rabook_gray8_fixture.py -- do not edit by hand."
,
136
" *"
,
137
" * @details"
,
138
" * The compiled-book path retains zoomable rasters at full source resolution"
,
139
" * in continuous-tone gray8 (1 byte/pixel), never the panel-quantised 4bpp"
,
140
" * form. This one-image book is the ra8_emulator proof: ereader_rabook validates"
,
141
" * it, confirms the image is 8bpp and holds more than the 16 distinct tones a"
,
142
" * 4bpp store could reproduce, then blits it 1:1 at full resolution. The image"
,
143
" * is a diagonal grayscale ramp whose tones sweep the full 0-255 range, so its"
,
144
" * survival proves no quantisation occurred. Do not edit by hand; see the"
,
145
" * generator."
,
146
" *"
,
147
" * @copyright Copyright (c) 2026 Brighton Sikarskie"
,
148
" * SPDX-License-Identifier: MIT"
,
149
" * @since Version 0.1.0"
,
150
" */"
,
151
"#pragma once"
,
152
""
,
153
"#include <stdint.h>"
,
154
""
,
155
"/**"
,
156
" * @enum rabook_gray8_fixture_size_t"
,
157
" * @brief Byte length of @ref k_rabook_gray8_fixture."
,
158
" * @since Version 0.1.0"
,
159
" */"
,
160
"typedef enum : uint32_t {"
,
161
f
" k_rabook_gray8_fixture_len = {len(blob)}U, /**< Inflated flat-blob length. */"
,
162
"} rabook_gray8_fixture_size_t;"
,
163
""
,
164
"/** @brief Baked, inflated gray8-image .rabook blob (validate + walk). */"
,
165
"// NOLINTBEGIN(readability-magic-numbers) -- generated byte-table fixture."
,
166
]
167
)
168
body = _emit_array(
"k_rabook_gray8_fixture"
, blob)
169
text = header +
"\n"
+ body +
"\n// NOLINTEND(readability-magic-numbers)\n"
170
_OUT.write_text(text, encoding=
"ascii"
)
171
sys.stdout.write(f
"rabook_gray8_fixture: wrote {_OUT} ({len(blob)} blob bytes)\n"
)
172
return
0
173
174
175
if
__name__ ==
"__main__"
:
176
raise
SystemExit(
main
())
main
void main(void)
The application entry point Reset_Handler hands control to.
Definition
main.c:298
scripts
gen
rabook_gray8_fixture.py
Generated by
1.16.1