ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
reflow_render.c
Go to the documentation of this file.
1
27
28#include <stddef.h>
29#include <stdint.h>
30#include <string.h>
31
32#include "ra8_attributes.h"
33#include "ra8_err.h"
34#include "ra8_gfx.h"
35#include "ra8_glyph_atlas.h"
36#include "reflow.h"
37#include "reflow_svg.h"
38#include "stb_truetype.h"
39
40/* ===========================================================================
41 * Internal sizing constants (no magic numbers).
42 * ===========================================================================
43 */
44
58
75typedef struct {
76 const stbtt_fontinfo* font;
77 float scale;
78 int w;
79 int h;
81
89
123 const ra8_glyph_key_t* key,
124 uint8_t* cell,
125 uint32_t cell_bytes,
126 uint16_t* out_w,
127 uint16_t* out_h)
128{
129 const priv_glyph_render_ctx_t* rc = (const priv_glyph_render_ctx_t*)ctx;
130 /* w/h were computed and validated (> 0) by internal_blit_glyph before the get. */
131 if (((size_t)rc->w * (size_t)rc->h) > (size_t)cell_bytes) {
132 return k_ra8_err_invalid_size; /* Too big to cache -> caller blits directly. */
133 }
134 stbtt_MakeCodepointBitmap(rc->font,
135 cell,
136 rc->w,
137 rc->h,
138 rc->w,
139 rc->scale,
140 rc->scale,
141 (int)key->glyph_id);
142 *out_w = (uint16_t)rc->w;
143 *out_h = (uint16_t)rc->h;
144 return k_ra8_ok;
145}
146
163static ra8_err_t internal_init_font(const reflow_t* engine, stbtt_fontinfo* out_font)
164{
165 const int32_t offset = stbtt_GetFontOffsetForIndex(engine->font_data, 0);
166 if (offset < 0) {
168 }
169 if (stbtt_InitFont(out_font, engine->font_data, (int)engine->font_len, offset) == 0) {
171 }
172 return k_ra8_ok;
173}
174
197 const unsigned char* bitmap,
198 int w,
199 int h,
200 int xoff,
201 int yoff,
202 int32_t ox,
203 int32_t oy)
204{
205 for (int row = 0; row < h; ++row) {
206 for (int col = 0; col < w; ++col) {
207 const uint8_t alpha = (uint8_t)bitmap[((size_t)row * (size_t)w) + (size_t)col];
208 if (alpha < k_priv_alpha_threshold) {
209 continue;
210 }
211 const int32_t px = g->x + (int32_t)col + (int32_t)xoff + ox;
212 const int32_t py = g->y + (int32_t)row + (int32_t)yoff + oy;
213 (void)ra8_gfx_pixel(px, py, g->color);
214 }
215 }
216}
217
235static void internal_draw_underline(const stbtt_fontinfo* font,
236 const reflow_glyph_t* g,
237 float scale,
238 int32_t ox,
239 int32_t oy)
240{
241 int advance_units = 0;
242 int lsb = 0;
243 stbtt_GetCodepointHMetrics(font, g->cp, &advance_units, &lsb);
244 const int32_t advance = (int32_t)((float)advance_units * scale);
245 const int32_t y_under = g->y + (int32_t)k_priv_underline_offset_px + oy;
246 for (int32_t i = 0; i < advance; ++i) {
247 (void)ra8_gfx_pixel(g->x + i + ox, y_under, g->color);
248 }
249}
250
278static void internal_glyph_render_direct(const stbtt_fontinfo* font,
279 float scale,
280 const reflow_glyph_t* g,
281 int w,
282 int h,
283 int x0,
284 int y0,
285 int32_t ox,
286 int32_t oy)
287{
288 /* Fixed rasterisation storage: rendering is single-threaded and heap-free. */
289 static uint8_t s_glyph_mask[(size_t)k_priv_glyph_dim_max * (size_t)k_priv_glyph_dim_max];
290 /* Skip (do not truncate) any glyph larger than the fixed mask. */
291 const size_t total = (size_t)w * (size_t)h;
292 if (total <= sizeof s_glyph_mask) {
293 stbtt_MakeCodepointBitmap(font, s_glyph_mask, w, h, w, scale, scale, g->cp);
294 internal_blit_alpha_mask(g, s_glyph_mask, w, h, x0, y0, ox, oy);
295 }
296}
297
338 uint8_t face_id,
339 const stbtt_fontinfo* font,
340 float scale,
341 const reflow_glyph_t* g,
342 int w,
343 int h,
344 int x0,
345 int y0,
346 int32_t ox,
347 int32_t oy)
348{
349 /* Hand the render-on-miss callback the font/scale/extent for this glyph. */
350 s_glyph_render_ctx.font = font;
351 s_glyph_render_ctx.scale = scale;
352 s_glyph_render_ctx.w = w;
353 s_glyph_render_ctx.h = h;
354
355 ra8_glyph_key_t key = {};
356 key.glyph_id = (uint32_t)g->cp;
357 key.face_id = (uint16_t)face_id;
358 key.size_px = g->font_px;
359 key.mode = (uint16_t)k_priv_glyph_mode_aa;
360
361 ra8_glyph_t glyph = {};
362 if (ra8_glyph_atlas_get(atlas, &key, &glyph) != k_ra8_ok) {
363 return false; /* Uncacheable (oversized / full) -> caller blits directly. */
364 }
365 internal_blit_alpha_mask(g, glyph.bitmap, (int)glyph.width, (int)glyph.height, x0, y0, ox, oy);
366 (void)ra8_glyph_atlas_put(atlas, glyph.bitmap);
367 return true;
368}
369
396 uint8_t face_id,
397 const stbtt_fontinfo* font,
398 const reflow_glyph_t* g,
399 int32_t ox,
400 int32_t oy)
401{
402 const float scale = stbtt_ScaleForPixelHeight(font, (float)g->font_px);
403 int x0 = 0;
404 int y0 = 0;
405 int x1 = 0;
406 int y1 = 0;
407 stbtt_GetCodepointBitmapBox(font, g->cp, scale, scale, &x0, &y0, &x1, &y1);
408 const int w = x1 - x0;
409 const int h = y1 - y0;
410 /*
411 * w and h are the glyph bitmap-box extents returned by
412 * stbtt_GetCodepointBitmapBox(). A codepoint is either inked -- a non-empty
413 * box with w > 0 AND h > 0 -- or empty -- a zero box with w == 0 AND h == 0
414 * (e.g. U+0020 space). The mixed states (w <= 0, h > 0) and (w > 0, h <= 0)
415 * never occur for a real font glyph, so the vectors that would give w and h
416 * independent MC/DC influence are structurally unreachable.
417 */
418 /* mcdc-deactivated: glyph bbox w,h co-dependent (inked or empty); mixed vectors unreachable. */
419 if ((w > 0) && (h > 0)) {
420 bool drawn = false;
421 if (atlas != nullptr) {
422 drawn = internal_glyph_render_cached(atlas, face_id, font, scale, g, w, h, x0, y0, ox, oy);
423 }
424 if (!drawn) {
425 internal_glyph_render_direct(font, scale, g, w, h, x0, y0, ox, oy);
426 }
427 }
428
429 if ((g->style & k_reflow_style_underline) != 0U) {
430 internal_draw_underline(font, g, scale, ox, oy);
431 }
432}
433
469static void internal_render_svg(const reflow_t* engine,
470 const uint8_t* svg,
471 size_t len,
472 int32_t x,
473 int32_t y,
474 int32_t w,
475 int32_t h)
476{
477 size_t hoff = 0U;
478 size_t hlen = 0U;
479 if (ra8_svg_image_href(svg, len, &hoff, &hlen) != k_ra8_ok) {
480 (void)ra8_svg_render(svg, len, x, y, w, h);
481 return;
482 }
483 char href[k_priv_svg_href_max] = {};
484 if (hlen >= sizeof(href)) {
485 return;
486 }
487 (void)memcpy(href, &svg[hoff], hlen);
488 const uint8_t* rbytes = nullptr;
489 size_t rlen = 0U;
490 if (engine->img_loader(engine->img_loader_ctx, href, (uint32_t)hlen, &rbytes, &rlen) ==
491 k_ra8_ok) {
492 (void)ra8_img_decode_blit(engine->img_arena, rbytes, rlen, x, y, w, h, nullptr, nullptr);
493 }
494}
495
526static void internal_render_one_image(const reflow_t* engine,
527 const reflow_image_box_t* box,
528 int32_t ox,
529 int32_t oy)
530{
531 const char* href = (const char*)&engine->text_pool[box->src_off];
532 const uint8_t* bytes = nullptr;
533 size_t blen = 0U;
534 if (engine->img_loader(engine->img_loader_ctx, href, box->src_len, &bytes, &blen) != k_ra8_ok) {
535 return;
536 }
537 const int32_t bx = box->x + ox;
538 const int32_t by = box->y + oy;
539 if (ra8_svg_is_svg(bytes, blen)) {
540 internal_render_svg(engine, bytes, blen, bx, by, box->w, box->h);
541 return;
542 }
543 (void)
544 ra8_img_decode_blit(engine->img_arena, bytes, blen, bx, by, box->w, box->h, nullptr, nullptr);
545}
546
575static void
576internal_render_images(const reflow_t* engine, uint32_t page_idx, int32_t ox, int32_t oy)
577{
578 if ((engine->img_loader == nullptr) || (engine->img_arena == nullptr)) {
579 return;
580 }
581 for (uint32_t i = 0U; i < engine->image_box_count; ++i) {
582 const reflow_image_box_t* box = &engine->image_boxes[i];
583 if (box->page_index == page_idx) {
584 internal_render_one_image(engine, box, ox, oy);
585 }
586 }
587}
588
589/* ===========================================================================
590 * Public API -- render
591 * ===========================================================================
592 */
593
628static ra8_err_t internal_init_faces(const reflow_t* engine, stbtt_fontinfo* faces, uint8_t* out_n)
629{
630 const ra8_err_t err = internal_init_font(engine, &faces[0]);
631 if (err != k_ra8_ok) {
632 return err;
633 }
634 for (uint8_t k = 0U; k < engine->face_count; ++k) {
635 const uint8_t* blob = engine->faces[k].blob;
636 const int32_t offset = stbtt_GetFontOffsetForIndex(blob, 0);
637 if ((offset < 0) ||
638 (stbtt_InitFont(&faces[k + 1U], blob, (int)engine->faces[k].len, offset) == 0)) {
639 faces[k + 1U] = faces[0]; /* graceful: bad face -> default */
640 }
641 }
642 *out_n = (uint8_t)(engine->face_count + 1U);
643 return k_ra8_ok;
644}
645
687static ra8_err_t
688internal_render_page(const reflow_t* engine, uint32_t page_idx, int32_t ox, int32_t oy)
689{
690 if (engine == nullptr) {
691 return k_ra8_err_null_ptr;
692 }
693 if (engine->in_use == 0U) {
695 }
696 if (page_idx >= engine->page_count) {
698 }
699
700 /* One fontinfo per face (static because render is single-threaded; keeps the
701 * ~1.4 KB off the stack). Default at 0, embedded at 1..N. */
702 static stbtt_fontinfo s_faces[1U + (uint32_t)k_reflow_max_faces];
703 uint8_t nfaces = 0U;
704 ra8_err_t err = internal_init_faces(engine, s_faces, &nfaces);
705 if (err != k_ra8_ok) {
706 return err;
707 }
708
709 const reflow_page_t* page = &engine->pages[page_idx];
710 ra8_glyph_atlas_t* atlas = engine->glyph_atlas;
711 for (uint32_t i = 0U; i < page->glyph_count; ++i) {
712 const reflow_glyph_t* g = &engine->glyphs[page->glyph_first + i];
713 uint8_t fi = (uint8_t)(((uint32_t)g->style >> (uint32_t)k_reflow_face_shift) &
714 (uint32_t)k_reflow_face_mask);
715 if (fi >= nfaces) {
716 fi = 0U; /* defensive: out-of-range face index -> default */
717 }
718 internal_blit_glyph(atlas, fi, &s_faces[fi], g, ox, oy);
719 }
720 internal_render_images(engine, page_idx, ox, oy);
721 return k_ra8_ok;
722}
723
724ra8_err_t reflow_render_page(const reflow_t* engine, uint32_t page_idx, void* framebuffer)
725{
726 (void)framebuffer; /* Reserved hook -- ra8_gfx is bound externally. */
727 return internal_render_page(engine, page_idx, 0, 0);
728}
729
731reflow_render_page_at(const reflow_t* engine, uint32_t page_idx, int32_t origin_x, int32_t origin_y)
732{
733 return internal_render_page(engine, page_idx, origin_x, origin_y);
734}
735
737 ra8_glyph_atlas_t* atlas,
738 const reflow_glyph_atlas_storage_t* storage)
739{
740 if (engine == nullptr) {
741 return k_ra8_err_null_ptr;
742 }
743 if (engine->in_use == 0U) {
745 }
746 if (atlas == nullptr) {
747 engine->glyph_atlas = nullptr; /* Detach -> revert to direct rasterisation. */
748 return k_ra8_ok;
749 }
750 if (storage == nullptr) {
751 return k_ra8_err_null_ptr;
752 }
753 if ((storage->cell_bytes == 0U) || (storage->cell_count == 0U) || (storage->bucket_count == 0U)) {
755 }
756
757 const ra8_glyph_atlas_cfg_t cfg = {
758 .cell_mem = storage->cell_mem,
759 .cell_bytes = storage->cell_bytes,
760 .cell_count = storage->cell_count,
761 .meta = storage->meta,
762 .keys = storage->keys,
763 .dims = storage->dims,
764 .buckets = storage->buckets,
765 .bucket_count = storage->bucket_count,
767 .render_ctx = &s_glyph_render_ctx,
768 };
769 const ra8_err_t err = ra8_glyph_atlas_init(atlas, &cfg);
770 if (err != k_ra8_ok) {
771 return err;
772 }
773 engine->glyph_atlas = atlas;
774 return k_ra8_ok;
775}
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Error Code Definitions for ra8-firmware.
@ k_ra8_err_out_of_range
Sensor or peripheral output out of valid range.
Definition ra8_err.h:337
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
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.
Fixed-RAM-budget glyph cache with LRU eviction (Layer 3, #147).
ra8_err_t ra8_glyph_atlas_put(ra8_glyph_atlas_t *atlas, const uint8_t *bitmap)
Release one pin on a glyph previously returned by ra8_glyph_atlas_get.
ra8_err_t ra8_glyph_atlas_get(ra8_glyph_atlas_t *atlas, const ra8_glyph_key_t *key, ra8_glyph_t *out_glyph)
Get (and pin) the rendered glyph for key.
ra8_err_t ra8_glyph_atlas_init(ra8_glyph_atlas_t *atlas, const ra8_glyph_atlas_cfg_t *cfg)
Initialise a glyph atlas over caller-supplied storage.
HTML / CSS reflow + paginate engine for the ra8d2 ereader.
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)
Decode bytes and blit it, scaled to fit, into the bound framebuffer.
ra8_err_t reflow_render_page(const reflow_t *engine, uint32_t page_idx, void *framebuffer)
Render one page into the active ra8_gfx framebuffer.
static void internal_glyph_render_direct(const stbtt_fontinfo *font, float scale, const reflow_glyph_t *g, int w, int h, int x0, int y0, int32_t ox, int32_t oy)
Rasterise one glyph into a function-local static mask and blit it.
static ra8_err_t internal_init_font(const reflow_t *engine, stbtt_fontinfo *out_font)
Initialise an stbtt_fontinfo from the engine's font blob.
static ra8_err_t internal_atlas_render_glyph(void *ctx, const ra8_glyph_key_t *key, uint8_t *cell, uint32_t cell_bytes, uint16_t *out_w, uint16_t *out_h)
Glyph-atlas render-on-miss callback: rasterise one glyph into a cell.
static void internal_blit_glyph(ra8_glyph_atlas_t *atlas, uint8_t face_id, const stbtt_fontinfo *font, const reflow_glyph_t *g, int32_t ox, int32_t oy)
Rasterise one glyph at its baseline position into the bound framebuffer.
static priv_glyph_render_ctx_t s_glyph_render_ctx
File-static render context shared with the bound glyph atlas.
ra8_err_t reflow_set_glyph_atlas(reflow_t *engine, ra8_glyph_atlas_t *atlas, const reflow_glyph_atlas_storage_t *storage)
Bind a Layer-3 glyph cache to the engine's render path (#164).
static ra8_err_t internal_init_faces(const reflow_t *engine, stbtt_fontinfo *faces, uint8_t *out_n)
Build the per-render stbtt_fontinfo set: default at 0, faces at 1..N.
static void internal_render_one_image(const reflow_t *engine, const reflow_image_box_t *box, int32_t ox, int32_t oy)
Render one image box: SVG-route or raster-decode at the page offset.
static void internal_render_images(const reflow_t *engine, uint32_t page_idx, int32_t ox, int32_t oy)
Decode and blit every laid-out image that belongs to one page.
static void internal_blit_alpha_mask(const reflow_glyph_t *g, const unsigned char *bitmap, int w, int h, int xoff, int yoff, int32_t ox, int32_t oy)
Walk one glyph bitmap and blit any sufficiently-covered pixels into the bound framebuffer.
static bool internal_glyph_render_cached(ra8_glyph_atlas_t *atlas, uint8_t face_id, const stbtt_fontinfo *font, float scale, const reflow_glyph_t *g, int w, int h, int x0, int y0, int32_t ox, int32_t oy)
Draw one glyph through the bound glyph atlas (cache hit or render-miss).
static void internal_draw_underline(const stbtt_fontinfo *font, const reflow_glyph_t *g, float scale, int32_t ox, int32_t oy)
Draw the underline strip for a link glyph.
priv_render_consts_t
Internal sizing knobs for the render pass.
@ k_priv_underline_offset_px
Pixels below baseline for the underline.
@ k_priv_glyph_mode_aa
Glyph-cache render mode: stb coverage AA.
@ k_priv_alpha_threshold
Coverage cutoff for binary blit.
@ k_priv_alpha_max_byte
Upper bound from stb's mask.
@ k_priv_svg_href_max
Max unwrapped SVG cover-image href length.
@ k_priv_underline_thick_px
Thickness of the anchor underline.
@ k_priv_glyph_dim_max
Mask edge bound = 2 * k_reflow_max_font_px.
static ra8_err_t internal_render_page(const reflow_t *engine, uint32_t page_idx, int32_t ox, int32_t oy)
Shared render body for one page, offsetting every element by (ox, oy).
ra8_err_t reflow_render_page_at(const reflow_t *engine, uint32_t page_idx, int32_t origin_x, int32_t origin_y)
Render one page offset by (origin_x, origin_y) into the bound framebuffer.
static void internal_render_svg(const reflow_t *engine, const uint8_t *svg, size_t len, int32_t x, int32_t y, int32_t w, int32_t h)
Render an SVG image box: unwrap a cover <image> href, else draw shapes.
Minimal SVG handling for the reflow ereader (#112).
ra8_err_t ra8_svg_image_href(const uint8_t *svg, size_t len, size_t *out_off, size_t *out_len)
Extract the href of a cover-wrapper <svg><image .../></svg>.
ra8_err_t ra8_svg_render(const uint8_t *svg, size_t len, int32_t x, int32_t y, int32_t w, int32_t h)
Render an SVG's vector shapes into a box.
bool ra8_svg_is_svg(const uint8_t *bytes, size_t len)
Heuristically decide whether bytes are an SVG document.
@ k_reflow_max_faces
Max embedded @font-face typefaces.
@ k_reflow_style_underline
Underlined run.
@ k_reflow_face_shift
Bit position of the face index in style.
@ k_reflow_face_mask
Face-index mask once shifted down.
Per-glyph state handed to the glyph-atlas render-on-miss callback.
const stbtt_fontinfo * font
Font for the in-flight glyph.
float scale
stb pixel-height scale for that glyph.
int h
Glyph bitmap height (from the box query).
int w
Glyph bitmap width (from the box query).
Caller-supplied storage + renderer for ra8_glyph_atlas_init.
Glyph-cache state (caller-owned; treat as private).
Identifies one rendered glyph.
uint16_t face_id
Font face identifier.
uint16_t mode
Render mode (AA / mono / hinting variant).
uint16_t size_px
Pixel size.
uint32_t glyph_id
Font glyph index.
A pinned view of a cached glyph bitmap returned by ra8_glyph_atlas_get.
const uint8_t * bitmap
Glyph coverage bitmap (width*height bytes).
uint16_t height
Glyph height in pixels.
uint16_t width
Glyph width in pixels.
const uint8_t * blob
TTF/OTF bytes; caller-owned, outlives engine.
size_t len
Length of blob, bytes.
Caller-owned backing storage for the render-path glyph cache (#164).
Definition reflow_api.h:191
ra8_glyph_dims_t * dims
cell_count dimension descriptors.
Definition reflow_api.h:197
ra8_glyph_key_t * keys
cell_count key-storage entries.
Definition reflow_api.h:196
uint8_t * cell_mem
cell_count * cell_bytes of bitmap storage.
Definition reflow_api.h:192
int32_t * buckets
bucket_count hash-bucket heads.
Definition reflow_api.h:198
uint32_t cell_count
Number of cells (the glyph-cache budget).
Definition reflow_api.h:194
ra8_keycache_cell_t * meta
cell_count link-metadata entries.
Definition reflow_api.h:195
uint32_t cell_bytes
Bytes per cell (>= largest cached glyph).
Definition reflow_api.h:193
uint32_t bucket_count
Number of hash buckets (>= 1).
Definition reflow_api.h:199
One positioned glyph after layout.
uint8_t style
Font-style bitmask.
int32_t cp
Unicode code point.
uint32_t color
32-bit RGB colour.
int32_t x
Pixel column of glyph baseline-left.
uint16_t font_px
Pixel size used for this glyph.
int32_t y
Pixel row of glyph baseline.
One laid-out <img> rectangle (decoded + blitted at render time).
uint32_t src_len
Href slice length, bytes.
int32_t y
Page-local top edge, pixels.
int32_t x
Page-local left edge, pixels.
uint32_t page_index
Page this image belongs to.
int32_t w
Scaled box width, pixels.
uint32_t src_off
Href slice offset into text pool.
int32_t h
Scaled box height, pixels.
Index range of glyphs that belong to one page.
uint32_t glyph_count
Number of glyphs in this page.
uint32_t glyph_first
Index of first glyph in this page.
Reflow / pagination engine state.
uint32_t page_count
Pages used.
reflow_image_loader_fn img_loader
<img> byte loader (NULL = off).
const uint8_t * font_data
TTF blob; outlives the engine.
ra8_img_arena_t * img_arena
Decode scratch (NULL = off).
reflow_glyph_t glyphs[k_reflow_max_glyphs]
Positioned glyphs.
reflow_page_t pages[k_reflow_max_pages]
Page index ranges.
uint8_t text_pool[k_reflow_text_pool_bytes]
Text bytes.
reflow_face_t faces[k_reflow_max_faces]
Embedded @font-face set.
ra8_glyph_atlas_t * glyph_atlas
Glyph bitmap cache, or NULL for direct raster.
uint8_t in_use
1 = initialized, 0 = closed.
size_t font_len
Length of font_data, bytes.
void * img_loader_ctx
Opaque context for img_loader.
reflow_image_box_t image_boxes[k_reflow_max_images]
Laid-out images.
uint32_t image_box_count
Image boxes used.
uint8_t face_count
Embedded face count (0 = single-face).