ra8-firmware
0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
make_imgfmt_fixtures.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 imgfmt_fixtures.h: deterministic BMP and GIF images for the gate.
5
6
These close a coverage hole. The firmware links four stb_image formats, and
7
STBI_ONLY_BMP and STBI_ONLY_GIF were the two with no example exercising them --
8
PNG and JPEG already have ereader_image and ereader_jpeg. A decoder nobody
9
calls is a decoder nobody knows is broken.
10
11
The on-device ereader_imgfmt gate decodes both through `ra8_img_decode_blit`
12
(zero-heap) into a 160x120 framebuffer and CRC-gates each independently, so a
13
regression in one format cannot be masked by the other passing.
14
15
Determinism is the requirement that shapes everything here: the CRCs are pinned
16
in the gate, so a fixture that varied run to run would break the gate rather
17
than detect anything. Output is pure 7-bit ASCII at 16 bytes per row inside a
18
clang-format-off guard, so the formatter leaves it byte-identical.
19
20
Usage:
21
python3 examples/ek_ra8d2/hw_validated/hil/ereader_imgfmt/scripts/make_imgfmt_fixtures.py
22
"""
23
24
from
__future__
import
annotations
25
26
import
io
27
from
pathlib
import
Path
28
29
from
PIL
import
Image
30
31
W, H = 40, 30
32
QUAD = [(0xC0, 0x10, 0x20), (0x18, 0x90, 0x30), (0x20, 0x40, 0xC0), (0xD0, 0xA0, 0x18)]
33
34
35
def
make_image(layout: str) -> Image.Image:
36
"""Render a 40x30 RGB test pattern in one of two layouts.
37
38
The two layouts exist so BMP and GIF decode to DIFFERENT pixels and
39
therefore pin two different CRCs. Identical images would let a gate pass
40
while decoding the wrong buffer -- a copy-paste bug in the gate would be
41
invisible.
42
43
Colors come from QUAD, chosen to be far apart so GIF's 4-color adaptive
44
palette reproduces them exactly rather than approximating.
45
46
Args:
47
layout: "quad" for four quadrants, anything else for horizontal bands.
48
49
Returns:
50
A 40x30 RGB Pillow Image.
51
"""
52
# Two distinct patterns so the BMP and GIF decode to different pixels and
53
# therefore pin two different CRCs (each path independently gated).
54
img = Image.new(
"RGB"
, (W, H))
55
px = img.load()
56
for
y
in
range(H):
57
for
x
in
range(W):
58
if
layout ==
"quad"
:
59
idx = (1
if
x >= W // 2
else
0) + (2
if
y >= H // 2
else
0)
60
else
:
# horizontal bands
61
idx =
min
(y // (H // len(QUAD)), len(QUAD) - 1)
62
px[x, y] = QUAD[idx]
63
return
img
64
65
66
def
encode(fmt: str) -> bytes:
67
"""Render and encode the test image for one format.
68
69
Pairs each format with its own layout -- BMP gets quadrants, anything else
70
gets bands -- so the two fixtures never collide. GIF is additionally
71
converted to a 4-color adaptive palette: GIF is palette-only anyway, and
72
pinning the color count keeps the encoder from choosing a different palette
73
size and shifting the bytes.
74
75
Args:
76
fmt: A Pillow format name, in practice "BMP" or "GIF". Any other value
77
takes the bands layout and is passed to Pillow unchanged.
78
79
Returns:
80
The encoded image bytes; identical across runs for a given format.
81
82
Raises:
83
KeyError: Pillow does not know `fmt`.
84
"""
85
# BMP gets the quadrant layout; GIF gets horizontal bands.
86
img = make_image(
"quad"
if
fmt ==
"BMP"
else
"bands"
)
87
out = io.BytesIO()
88
if
fmt ==
"GIF"
:
89
# Palette mode keeps the GIF tiny and deterministic.
90
img = img.convert(
"P"
, palette=Image.ADAPTIVE, colors=4)
91
img.save(out, format=fmt)
92
return
out.getvalue()
93
94
95
def
bake_array(name: str, data: bytes) -> str:
96
"""Render bytes as a C length enum plus a `static const uint8_t` array.
97
98
The length is emitted as `enum : size_t` and used to size the array, so the
99
declared size and the data can never disagree.
100
101
The table is wrapped in `clang-format off`/`on` and laid out 16 bytes per
102
row. That keeps the formatter from reflowing it, which is what makes a
103
regenerated fixture diff empty when nothing actually changed.
104
105
Args:
106
name: Lowercase stem for the identifiers; produces `k_<name>` and
107
`k_<name>_len`, and is upper-cased for the doc comments. Used
108
unquoted, so it must already be a valid C identifier fragment.
109
data: Bytes to bake.
110
111
Returns:
112
The declarations as C source text.
113
"""
114
rows = []
115
for
i
in
range(0, len(data), 16):
116
chunk = data[i : i + 16]
117
rows.append(
" "
+
", "
.join(f
"0x{b:02X}"
for
b
in
chunk) +
","
)
118
body =
"\n"
.join(rows)
119
return
(
120
f
"/** @brief Length of the baked {name.upper()} image, bytes. */\n"
121
f
"enum : size_t {{ k_{name}_len = {len(data)}U "
122
f
"/**< {name.capitalize()} length. */ }};\n"
123
"\n"
124
f
"/** @brief Baked 40x30 four-quadrant {name.upper()} image. */\n"
125
"/* Suppression rationale: generated bytes stay at 16 per row for stable diffs. */\n"
126
"/* clang-format off */\n"
127
f
"static const uint8_t k_{name}[k_{name}_len] = {{\n"
128
f
"{body}\n"
129
"};\n"
130
"/* clang-format on */\n"
131
)
132
133
134
def
main
() -> int:
135
"""Regenerate the owning component's inc/imgfmt_fixtures.h.
136
137
Regenerating changes the baked bytes only if Pillow's encoders change. If it
138
does, the CRCs pinned in the on-device gate no longer match and must be
139
re-pinned in the same commit -- otherwise the gate fails on a fixture that
140
is actually correct.
141
"""
142
bmp = encode(
"BMP"
)
143
gif = encode(
"GIF"
)
144
header = (
145
"/**\n"
146
" * @file imgfmt_fixtures.h\n"
147
" * @brief Baked BMP + GIF test images for the ereader_imgfmt gate.\n"
148
" *\n"
149
" * @details The two stb_image formats (STBI_ONLY_BMP / STBI_ONLY_GIF) that\n"
150
" * had no example. Each is a 40x30 four-quadrant image, byte-identical run to\n"
151
" * run. Generated by examples/ek_ra8d2/hw_validated/hil/ereader_imgfmt/scripts/\n"
152
" * make_imgfmt_fixtures.py. Pure ASCII, 16 bytes per row.\n"
153
" *\n"
154
" * @copyright Copyright (c) 2026 Brighton Sikarskie\n"
155
" * SPDX-License-Identifier: MIT\n"
156
" */\n"
157
"#pragma once\n"
158
"\n"
159
"#include <stddef.h>\n"
160
"#include <stdint.h>\n"
161
"\n"
162
f
"{bake_array('bmp', bmp)}"
163
"\n"
164
f
"{bake_array('gif', gif)}"
165
)
166
output = Path(__file__).resolve().parent.parent /
"inc"
/
"imgfmt_fixtures.h"
167
with
output.open(
"w"
, encoding=
"ascii"
)
as
f:
168
f.write(header)
169
print(f
"wrote {output} (bmp {len(bmp)} B, gif {len(gif)} B)"
)
170
171
172
if
__name__ ==
"__main__"
:
173
main
()
main
void main(void)
The application entry point Reset_Handler hands control to.
Definition
main.c:298
min
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.
Definition
xz_config.h:157
examples
ek_ra8d2
hw_validated
hil
ereader_imgfmt
scripts
make_imgfmt_fixtures.py
Generated by
1.16.1