ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
reflow_image.c
Go to the documentation of this file.
1
19
20#include "reflow_image.h"
21
22#include <stddef.h>
23#include <stdint.h>
24#include <string.h>
25
26#include "ra8_attributes.h"
27#include "ra8_check.h"
28#include "ra8_err.h"
29#include "ra8_gfx.h"
30#include "ra8_img_arena.h"
31#include "ra8_log.h"
32#include "reflow_svg.h"
33#include "stb_image.h"
34
36static const char* const s_tag_img = "ra8_img";
37
49
54typedef enum : uint8_t {
58
59ra8_err_t ra8_img_probe_size(const uint8_t* bytes, size_t len, int32_t* out_w, int32_t* out_h)
60{
61 RA8_CHECK_NULL_PTR(bytes, s_tag_img, "probe: null bytes");
62 RA8_CHECK_NULL_PTR(out_w, s_tag_img, "probe: null out_w");
63 RA8_CHECK_NULL_PTR(out_h, s_tag_img, "probe: null out_h");
64
65 /* SVG is not a raster: take its intrinsic size from the document (#112). */
66 if (ra8_svg_is_svg(bytes, len)) {
67 return ra8_svg_size(bytes, len, out_w, out_h);
68 }
69
70 int x = 0;
71 int y = 0;
72 int comp = 0;
73 if (stbi_info_from_memory(bytes, (int)len, &x, &y, &comp) == 0) {
74 ra8_log_error(s_tag_img, "probe: stbi_info rejected the header");
76 }
77 *out_w = (int32_t)x;
78 *out_h = (int32_t)y;
79 return k_ra8_ok;
80}
81
113static void internal_fit_box(int32_t src_w,
114 int32_t src_h,
115 int32_t box_w,
116 int32_t box_h,
117 int32_t* fit_w,
118 int32_t* fit_h)
119{
120 int32_t sw = 0;
121 int32_t sh = 0;
122 if (((int64_t)box_w * (int64_t)src_h) <= ((int64_t)box_h * (int64_t)src_w)) {
123 /* Width is the tighter constraint: fill the box width. */
124 sw = box_w;
125 sh = (int32_t)(((int64_t)src_h * (int64_t)box_w) / (int64_t)src_w);
126 } else {
127 /* Height is the tighter constraint: fill the box height. */
128 sh = box_h;
129 sw = (int32_t)(((int64_t)src_w * (int64_t)box_h) / (int64_t)src_h);
130 }
131 *fit_w = (sw < k_ra8_img_min_edge) ? (int32_t)k_ra8_img_min_edge : sw;
132 *fit_h = (sh < k_ra8_img_min_edge) ? (int32_t)k_ra8_img_min_edge : sh;
133}
134
164{
165 const char* const reason = stbi_failure_reason();
166 /*
167 * internal_decode_fail() is called only from ra8_img_decode_blit() after
168 * stbi_load_from_memory() has already reported a decode failure. stb_image
169 * sets its global failure string on every failure path, so stbi_failure_reason()
170 * is non-null on every call that reaches here; the (reason == nullptr) arm is
171 * unreachable defensive code and cannot give the first condition independent
172 * influence.
173 */
174 /* mcdc-deactivated: stbi sets a reason on every failure, so (reason != nullptr) is always true here. */
175 if ((reason != nullptr) && (strstr(reason, "outofmem") != nullptr)) {
176 return k_ra8_err_no_mem;
177 }
179}
180
217static void internal_blit_scaled(const uint8_t* pixels,
218 int32_t src_w,
219 int32_t src_h,
220 int32_t fit_w,
221 int32_t fit_h,
222 int32_t dst_x,
223 int32_t dst_y)
224{
225 for (int32_t dy = 0; dy < fit_h; dy++) {
226 const int32_t map_y = (int32_t)(((int64_t)dy * (int64_t)src_h) / (int64_t)fit_h);
227 for (int32_t dx = 0; dx < fit_w; dx++) {
228 const int32_t map_x = (int32_t)(((int64_t)dx * (int64_t)src_w) / (int64_t)fit_w);
229 const size_t idx =
230 (((size_t)map_y * (size_t)src_w) + (size_t)map_x) * (size_t)k_ra8_img_req_rgb;
231 const uint32_t color = ((uint32_t)pixels[idx + (size_t)k_ra8_img_ch_r] << k_ra8_img_shift_r) |
232 ((uint32_t)pixels[idx + (size_t)k_ra8_img_ch_g] << k_ra8_img_shift_g) |
233 (uint32_t)pixels[idx + (size_t)k_ra8_img_ch_b];
234 (void)ra8_gfx_pixel(dst_x + dx, dst_y + dy, color);
235 }
236 }
237}
238
270{
272 arena->offset = 0U;
273 arena->live = 0U;
274}
275
278 const uint8_t* bytes,
279 size_t len,
280 int32_t dst_x,
281 int32_t dst_y,
282 int32_t box_w,
283 int32_t box_h,
284 int32_t* out_w,
285 int32_t* out_h)
286{
287 RA8_CHECK_NULL_PTR(arena, s_tag_img, "blit: null arena");
288 RA8_CHECK_NULL_PTR(bytes, s_tag_img, "blit: null bytes");
289 if ((len == 0U) || (box_w < (int32_t)k_ra8_img_min_edge) ||
290 (box_h < (int32_t)k_ra8_img_min_edge)) {
291 ra8_log_error(s_tag_img, "blit: empty input or box");
293 }
294
295 ra8_img_arena_bind(arena); /* resets the arena to empty */
296 int sx = 0;
297 int sy = 0;
298 int comp = 0;
299 /* The stb call stays on one line so the no-alloc audit
300 (scripts/checks/check_no_dynamic_alloc.py) finds its opt-out on the flagged
301 call line; clang-format would otherwise wrap it across many lines. */
302 // clang-format off: the allocation opt-out comment must stay on the flagged call line.
303 uint8_t* const pixels = stbi_load_from_memory(bytes, (int)len, &sx, &sy, &comp, (int)k_ra8_img_req_rgb); /* alloc-allow: stb is backed by the fixed ra8_img_arena (zero-heap), not malloc */
304 // clang-format on
305 /*
306 * stbi_load_from_memory() returns a non-null pixel pointer only when it
307 * decoded a bitmap of at least 1x1 (it rejects zero-dimension images with a
308 * null return). So whenever pixels != nullptr, sx >= 1 and sy >= 1: the
309 * (sx <= 0) and (sy <= 0) guards are defensive belt-and-suspenders that can
310 * only be true when pixels == nullptr already made the decision true. Neither
311 * can be flipped independently, so full MC/DC of this decision is unreachable.
312 */
313 /* mcdc-deactivated: stbi guarantees sx,sy >= 1 when pixels != nullptr; sx/sy guards are unreachable. */
314 if ((pixels == nullptr) || (sx <= 0) || (sy <= 0)) {
315 const ra8_err_t err = internal_decode_fail();
317 ra8_log_error(s_tag_img, "blit: decode failed");
318 return err;
319 }
320
321 int32_t fit_w = 0;
322 int32_t fit_h = 0;
323 internal_fit_box((int32_t)sx, (int32_t)sy, box_w, box_h, &fit_w, &fit_h);
324 internal_blit_scaled(pixels, (int32_t)sx, (int32_t)sy, fit_w, fit_h, dst_x, dst_y);
325 // clang-format off: the allocation opt-out comment must stay on the flagged call line.
326 stbi_image_free(pixels); /* alloc-allow: ra8_img_arena-backed (zero-heap), not malloc */
327 // clang-format on
329
330 if (out_w != nullptr) {
331 *out_w = fit_w;
332 }
333 if (out_h != nullptr) {
334 *out_h = fit_h;
335 }
336 return k_ra8_ok;
337}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Validation and Error-Checking Macros for ra8-firmware.
#define RA8_CHECK_NULL_PTR(ptr, tag, message)
Reject nullptr pointer, returning k_ra8_err_null_ptr.
Definition ra8_check.h:243
Error Code Definitions for ra8-firmware.
@ k_ra8_err_not_supported
Requested feature not compiled in, not wired, or not supported by this MCU variant.
Definition ra8_err.h:180
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
char * strstr(const char *haystack, const char *needle)
Locate substring in string.
Software 2D graphics primitives layered on top of a caller-ownedframebuffer (DRW / D/AVE 2D / GLCDC r...
ra8_err_t ra8_gfx_pixel(int32_t x, int32_t y, uint32_t color)
Set a single pixel.
Heap-free scratch allocator hooks for the stb_image single-TU build.
void ra8_img_arena_bind(ra8_img_arena_t *arena)
Bind arena as the active scratch for subsequent decode allocations.
void ra8_img_arena_unbind(void)
Unbind the active arena; subsequent allocation hooks fail with nullptr.
Lightweight Logging Interface for ra8-firmware.
#define ra8_log_error(tag, message)
RA8 log error.
Definition ra8_log.h:335
static void internal_blit_scaled(const uint8_t *pixels, int32_t src_w, int32_t src_h, int32_t fit_w, int32_t fit_h, int32_t dst_x, int32_t dst_y)
Nearest-neighbour blit a decoded RGB image into the bound framebuffer.
ra8_img_shift_t
Channel shifts to assemble a 0x00RRGGBB colour for ra8_gfx (no magics).
@ k_ra8_img_shift_r
Red channel shift into 0x00RRGGBB.
@ k_ra8_img_shift_g
Green channel shift into 0x00RRGGBB.
ra8_img_pack_t
Pixel-packing and channel constants (no magic numbers).
@ k_ra8_img_min_edge
Minimum scaled / box edge length, pixels.
@ k_ra8_img_ch_b
Blue byte offset within an RGB triple.
@ k_ra8_img_ch_r
Red byte offset within an RGB triple.
@ k_ra8_img_req_rgb
Desired channel count requested from stb_image.
@ k_ra8_img_ch_g
Green byte offset within an RGB triple.
ra8_err_t ra8_img_decode_blit(ra8_img_arena_t *arena, const uint8_t *bytes, size_t len, int32_t dst_x, int32_t dst_y, int32_t box_w, int32_t box_h, int32_t *out_w, int32_t *out_h)
Implementation of ra8_img_decode_blit() – nearest-neighbour scale.
static void internal_fit_box(int32_t src_w, int32_t src_h, int32_t box_w, int32_t box_h, int32_t *fit_w, int32_t *fit_h)
Compute the aspect-preserving fit rectangle for an image in a box.
static void internal_arena_release(ra8_img_arena_t *arena)
Unbind the decode arena and force it back to the fully-drained state.
ra8_err_t ra8_img_probe_size(const uint8_t *bytes, size_t len, int32_t *out_w, int32_t *out_h)
Probe an image's intrinsic dimensions without a full decode.
static ra8_err_t internal_decode_fail(void)
Map a decode failure to the closest ra8_err_t via stbi_failure_reason.
static const char *const s_tag_img
Log tag for the image decode/blit module.
Zero-heap raster image decode + scale + blit for reflow (#106).
Minimal SVG handling for the reflow ereader (#112).
bool ra8_svg_is_svg(const uint8_t *bytes, size_t len)
Heuristically decide whether bytes are an SVG document.
ra8_err_t ra8_svg_size(const uint8_t *svg, size_t len, int32_t *out_w, int32_t *out_h)
Report an SVG's intrinsic size (width/height, else viewBox dimensions).
Caller-owned bump arena backing a single image decode.
size_t offset
Current bump offset.
size_t live
Live (allocated, not freed) blocks.