ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
jof_png.c
Go to the documentation of this file.
1
26
27#include <stddef.h>
28#include <stdint.h>
29#include <string.h>
30
31#include "jof_internal.h"
32#include "jof_png_internal.h"
33#include "miniz.h"
34#include "ra8_attributes.h"
35#include "ra8_check.h"
36#include "ra8_err.h"
37#include "ra8_log.h"
38
39/* ---------------------------------------------------------------------------
40 * Byte-source helpers.
41 * ---------------------------------------------------------------------------
42 */
43
44/* ---------------------------------------------------------------------------
45 * Scanline reconstruction.
46 * ---------------------------------------------------------------------------
47 */
48
65RA8_INTERNAL static uint8_t internal_png_paeth(uint8_t a, uint8_t b, uint8_t c)
66{
67 const int32_t p = (int32_t)a + (int32_t)b - (int32_t)c;
68 const int32_t pa = (p > (int32_t)a) ? (p - (int32_t)a) : ((int32_t)a - p);
69 const int32_t pb = (p > (int32_t)b) ? (p - (int32_t)b) : ((int32_t)b - p);
70 const int32_t pc = (p > (int32_t)c) ? (p - (int32_t)c) : ((int32_t)c - p);
71 if ((pa <= pb) && (pa <= pc)) {
72 return a;
73 }
74 if (pb <= pc) {
75 return b;
76 }
77 return c;
78}
79
102 uint8_t* row,
103 const uint8_t* prev,
104 uint32_t nbytes,
105 uint8_t bpp)
106{
107 switch (filter) {
109 return k_ra8_ok;
111 for (uint32_t i = (uint32_t)bpp; i < nbytes; i++) {
112 row[i] = (uint8_t)(row[i] + row[i - bpp]);
113 }
114 return k_ra8_ok;
116 for (uint32_t i = 0U; i < nbytes; i++) {
117 row[i] = (uint8_t)(row[i] + prev[i]);
118 }
119 return k_ra8_ok;
121 for (uint32_t i = 0U; i < nbytes; i++) {
122 const uint32_t left = (i >= (uint32_t)bpp) ? (uint32_t)row[i - bpp] : 0U;
123 row[i] = (uint8_t)(row[i] + (uint8_t)((left + (uint32_t)prev[i]) / 2U));
124 }
125 return k_ra8_ok;
127 for (uint32_t i = 0U; i < nbytes; i++) {
128 const uint8_t a = (i >= (uint32_t)bpp) ? row[i - bpp] : 0U;
129 const uint8_t c = (i >= (uint32_t)bpp) ? prev[i - bpp] : 0U;
130 row[i] = (uint8_t)(row[i] + internal_png_paeth(a, prev[i], c));
131 }
132 return k_ra8_ok;
133 default:
135 }
136}
137
158{
159 const uint32_t w = (uint32_t)st->w;
160 if (st->color_type == (uint8_t)k_ra8_png_color_pal) {
161 for (uint32_t x = 0U; x < w; x++) {
162 const uint8_t idx = row[x];
163 if ((uint16_t)idx >= st->plte_count) {
165 }
166 const uint32_t src = (uint32_t)idx * (uint32_t)k_ra8_png_ch_3;
167 const uint32_t dst = x * (uint32_t)st->dst_ch;
168 st->xlat[dst] = st->palette[src];
169 st->xlat[dst + 1U] = st->palette[src + 1U];
170 st->xlat[dst + 2U] = st->palette[src + 2U];
171 if (st->dst_ch == (uint8_t)k_ra8_png_ch_4) {
172 st->xlat[dst + 3U] =
173 ((uint16_t)idx < st->trns_count) ? st->trns[idx] : (uint8_t)k_ra8_png_opaque;
174 }
175 }
176 return k_ra8_ok;
177 }
178 if (st->color_type == (uint8_t)k_ra8_png_color_ga) {
179 for (uint32_t x = 0U; x < w; x++) {
180 const uint8_t g = row[(size_t)x * (size_t)k_ra8_png_ch_2];
181 const uint8_t a = row[((size_t)x * (size_t)k_ra8_png_ch_2) + 1U];
182 const uint32_t dst = x * (uint32_t)k_ra8_png_ch_4;
183 st->xlat[dst] = g;
184 st->xlat[dst + 1U] = g;
185 st->xlat[dst + 2U] = g;
186 st->xlat[dst + 3U] = a;
187 }
188 return k_ra8_ok;
189 }
190 /* gray, RGB, RGBA: source layout == output layout. */
191 (void)memcpy(st->xlat, row, (size_t)w * (size_t)st->dst_ch);
192 return k_ra8_ok;
193}
194
217internal_png_consume_rows(ra8_png_state_t* st, const uint8_t* src, uint32_t avail)
218{
219 uint32_t pos = 0U;
220 while (pos < avail) {
221 const uint32_t need = st->rowlen - st->rowfill;
222 const uint32_t left = avail - pos;
223 const uint32_t take = (left < need) ? left : need;
224 (void)memcpy(&st->rowbuf[st->rowfill], &src[pos], (size_t)take);
225 st->rowfill += take;
226 pos += take;
227 if (st->rowfill < st->rowlen) {
228 break; /* partial scanline; wait for more inflate output */
229 }
230 if (st->rows_done >= st->h) {
231 return k_ra8_err_validation_failed; /* more scanlines than IHDR said */
232 }
233 const uint32_t nbytes = st->rowlen - 1U;
234 ra8_err_t err =
235 internal_png_unfilter(st->rowbuf[0], &st->rowbuf[1], st->prevrow, nbytes, st->src_ch);
236 if (err != k_ra8_ok) {
237 return err;
238 }
239 (void)memcpy(st->prevrow, &st->rowbuf[1], (size_t)nbytes);
240 err = internal_png_translate(st, &st->rowbuf[1]);
241 if (err != k_ra8_ok) {
242 return err;
243 }
244 err = st->on_rows(st->cb_ctx, st->xlat, st->w, st->rows_done, 1U, st->dst_ch);
245 if (err != k_ra8_ok) {
246 return err;
247 }
248 st->rows_done++;
249 st->rowfill = 0U;
250 }
251 return k_ra8_ok;
252}
253
254/* ---------------------------------------------------------------------------
255 * IDAT inflate phase.
256 * ---------------------------------------------------------------------------
257 */
258
280{
281 if (st->color_type == (uint8_t)k_ra8_png_color_pal) {
282 if (st->has_plte == 0U) {
284 }
285 st->dst_ch = (st->has_trns != 0U) ? (uint8_t)k_ra8_png_ch_4 : (uint8_t)k_ra8_png_ch_3;
286 } else if (st->color_type == (uint8_t)k_ra8_png_color_gray) {
287 st->dst_ch = (uint8_t)k_ra8_png_ch_1;
288 } else if (st->color_type == (uint8_t)k_ra8_png_color_ga) {
289 st->dst_ch = (uint8_t)k_ra8_png_ch_4;
290 } else {
291 st->dst_ch = st->src_ch; /* RGB -> 3, RGBA -> 4 */
292 }
293 const ra8_err_t err = st->on_geom(st->cb_ctx, st->w, st->h, st->dst_ch);
294 if (err != k_ra8_ok) {
295 return err;
296 }
297 st->rowlen = 1U + ((uint32_t)st->w * (uint32_t)st->src_ch);
298 /* The bump allocator returns 8-byte-aligned void* carves, which satisfies
299 * the inflate context's alignment. */
300 st->inflator = (tinfl_decompressor*)priv_jof_bump_take(bump, sizeof(tinfl_decompressor));
301 st->ring = priv_jof_bump_take(bump, (size_t)k_ra8_png_ring_bytes);
303 st->rowbuf = priv_jof_bump_take(bump, (size_t)st->rowlen);
304 st->prevrow = priv_jof_bump_take(bump, (size_t)(st->rowlen - 1U));
305 st->xlat = priv_jof_bump_take(bump, (size_t)st->w * (size_t)st->dst_ch);
306 if ((st->inflator == nullptr) || (st->ring == nullptr) || (st->inbuf == nullptr) ||
307 (st->rowbuf == nullptr) || (st->prevrow == nullptr) || (st->xlat == nullptr)) {
309 }
310 (void)memset(st->prevrow, 0, (size_t)(st->rowlen - 1U)); /* PNG sec 9: prior row = zeroes */
311 tinfl_init(st->inflator);
312 return k_ra8_ok;
313}
314
336{
337 *out_avail = 0U;
338 for (uint32_t guard = 0U; guard < (uint32_t)k_ra8_png_max_chunks; guard++) {
339 if (st->idat_rem > 0U) {
340 const uint32_t take = (st->idat_rem < (uint32_t)k_ra8_png_inbuf_bytes)
341 ? st->idat_rem
342 : (uint32_t)k_ra8_png_inbuf_bytes;
343 const ra8_err_t err = priv_jof_png_pull_exact(st, st->inbuf, take);
344 if (err != k_ra8_ok) {
345 return err;
346 }
347 st->idat_rem -= take;
348 *out_avail = take;
349 return k_ra8_ok;
350 }
351 /* Current IDAT exhausted: skip its CRC, look at the next chunk. */
353 if (err != k_ra8_ok) {
354 return err;
355 }
356 uint32_t len = 0U;
357 uint32_t type = 0U;
358 err = priv_jof_png_chunk_hdr(st, &len, &type);
359 if (err != k_ra8_ok) {
360 return err;
361 }
362 if (type == (uint32_t)k_ra8_png_type_idat) {
363 st->idat_rem = len;
364 continue;
365 }
366 st->pending_len = len;
367 st->pending_type = type;
368 st->pending_valid = 1U;
369 st->source_done = 1U;
370 return k_ra8_ok; /* clean end of the compressed stream */
371 }
372 return k_ra8_err_protocol_error; /* chunk budget exhausted (hostile) */
373}
374
381typedef struct {
382 uint32_t in_avail;
383 uint32_t in_pos;
384 uint8_t stalls;
385 uint8_t done;
387
411{
412 // mcdc-deactivated: refill-first loop structure; entering with a drained window after the source ended requires the source-ending iteration to return >= TINFL_STATUS_DONE without finishing, i.e. tinfl parked output mid-flush at the exact call the source ended -- for every constructible stream that call either completes (DONE exits the loop) or fails closed at the status check, so the (drained, source-done) entry cannot be flipped independently.
413 if ((it->in_pos == it->in_avail) && (st->source_done == 0U)) {
414 const ra8_err_t err = internal_png_refill_input(st, &it->in_avail);
415 if (err != k_ra8_ok) {
416 return err;
417 }
418 it->in_pos = 0U;
419 }
420 size_t in_sz = (size_t)(it->in_avail - it->in_pos);
421 size_t out_sz = (size_t)((uint32_t)k_ra8_png_ring_bytes - st->ring_wr);
422 const mz_uint flags = (mz_uint)TINFL_FLAG_PARSE_ZLIB_HEADER |
423 ((st->source_done == 0U) ? (mz_uint)TINFL_FLAG_HAS_MORE_INPUT : 0U);
424 const tinfl_status status = tinfl_decompress(st->inflator,
425 &st->inbuf[it->in_pos],
426 &in_sz,
427 st->ring,
428 &st->ring[st->ring_wr],
429 &out_sz,
430 flags);
431 if (status < TINFL_STATUS_DONE) {
432 return k_ra8_err_protocol_error; /* corrupt deflate / bad zlib header */
433 }
434 // mcdc-deactivated: zero-progress stall guard, defense-in-depth against a decompressor that spins without failing; tinfl never returns >= TINFL_STATUS_DONE with neither input consumed nor output produced (a starved mid-stream call without HAS_MORE_INPUT fails closed at the status check above, and the refill-first structure guarantees input is present otherwise), so the stall arm is not constructible from any source stream.
435 if ((in_sz == 0U) && (out_sz == 0U)) {
436 it->stalls++;
437 // mcdc-deactivated: inner arm of the non-constructible stall guard above (same rationale); kept so a hypothetical spinning decompressor aborts after one repeat instead of looping.
438 if ((it->stalls > 1U) || (st->source_done != 0U)) {
439 return k_ra8_err_protocol_error; /* truncated stream / no progress */
440 }
441 } else {
442 it->stalls = 0U;
443 }
444 if (out_sz > 0U) {
445 const ra8_err_t err = internal_png_consume_rows(st, &st->ring[st->ring_wr], (uint32_t)out_sz);
446 if (err != k_ra8_ok) {
447 return err;
448 }
449 st->ring_wr = (st->ring_wr + (uint32_t)out_sz) & ((uint32_t)k_ra8_png_ring_bytes - 1U);
450 }
451 it->in_pos += (uint32_t)in_sz;
452 it->done = (status == TINFL_STATUS_DONE) ? 1U : 0U;
453 return k_ra8_ok;
454}
455
478{
479 st->idat_rem = first_len;
480 ra8_png_iter_t it = {};
481 while (it.done == 0U) {
482 const ra8_err_t err = internal_png_inflate_step(st, &it);
483 if (err != k_ra8_ok) {
484 return err;
485 }
486 }
487 /* Strict termination: every declared row arrived, nothing extra. */
488 if ((st->rows_done != st->h) || (st->rowfill != 0U)) {
490 }
491 if ((it.in_pos != it.in_avail) || (st->idat_rem != 0U)) {
492 return k_ra8_err_validation_failed; /* trailing garbage inside IDAT */
493 }
494 return k_ra8_ok;
495}
496
497/* ---------------------------------------------------------------------------
498 * Entry point.
499 * ---------------------------------------------------------------------------
500 */
501
522{
524 if (err != k_ra8_ok) {
525 return err;
526 }
527 err = internal_png_inflate_idat(st, len);
528 if (err != k_ra8_ok) {
529 return err;
530 }
531 return priv_jof_png_finish(st);
532}
533
555internal_png_walk_chunks(ra8_png_state_t* st, jof_bump_t* bump, uint16_t max_w, uint16_t max_h)
556{
557 ra8_err_t err = priv_jof_png_prologue(st, max_w, max_h);
558 if (err != k_ra8_ok) {
559 return err;
560 }
561 for (uint32_t guard = 0U; guard < (uint32_t)k_ra8_png_max_chunks; guard++) {
562 uint32_t len = 0U;
563 uint32_t type = 0U;
564 err = priv_jof_png_chunk_hdr(st, &len, &type);
565 if (err != k_ra8_ok) {
566 return err;
567 }
568 if (type == (uint32_t)k_ra8_png_type_idat) {
569 return internal_png_run_idat(st, bump, len);
570 }
571 err = priv_jof_png_pre_idat(st, len, type);
572 if (err != k_ra8_ok) {
573 return err;
574 }
575 }
576 return k_ra8_err_protocol_error; /* chunk budget exhausted (hostile) */
577}
578
580 void* pull_ctx,
581 jof_bump_t* bump,
582 uint16_t max_w,
583 uint16_t max_h,
584 jof_geom_fn on_geom,
585 jof_rows_fn on_rows,
586 void* cb_ctx)
587{
588 static const char* const s_tag = "jof_png";
589 static ra8_png_state_t s_png;
590 RA8_CHECK_NULL_PTR(pull, s_tag, "pull must not be nullptr");
591 RA8_CHECK_NULL_PTR(bump, s_tag, "bump must not be nullptr");
592 RA8_CHECK_NULL_PTR(on_geom, s_tag, "on_geom must not be nullptr");
593 RA8_CHECK_NULL_PTR(on_rows, s_tag, "on_rows must not be nullptr");
594 ra8_png_state_t* st = &s_png;
595 (void)memset(st, 0, sizeof(*st));
596 st->pull = pull;
597 st->pull_ctx = pull_ctx;
598 st->on_geom = on_geom;
599 st->on_rows = on_rows;
600 st->cb_ctx = cb_ctx;
601 return internal_png_walk_chunks(st, bump, max_w, max_h);
602}
Module-private seams shared by the producer translation units.
void * priv_jof_bump_take(jof_bump_t *bump, size_t len)
Carve len 8-byte-aligned bytes from the bump arena.
Definition jof_produce.c:89
ra8_err_t(* jof_geom_fn)(void *ctx, uint16_t width, uint16_t height, uint8_t channels)
Producer geometry hook: fires once when the source dimensions and output channel count are known,...
ra8_err_t(* jof_rows_fn)(void *ctx, const uint8_t *px, uint16_t width, uint16_t y0, uint16_t nrows, uint8_t channels)
Producer row sink: receives decoded pixel rows strictly in order.
static uint8_t internal_png_paeth(uint8_t a, uint8_t b, uint8_t c)
Paeth predictor (PNG sec 9.4 "Filter type 4: Paeth").
Definition jof_png.c:65
static ra8_err_t internal_png_run_idat(ra8_png_state_t *st, jof_bump_t *bump, uint32_t len)
The IDAT arm: bind geometry, inflate the stream, finish the walk.
Definition jof_png.c:521
static ra8_err_t internal_png_unfilter(uint8_t filter, uint8_t *row, const uint8_t *prev, uint32_t nbytes, uint8_t bpp)
Reconstruct one filtered scanline in place (PNG sec 9 filters).
Definition jof_png.c:101
static ra8_err_t internal_png_walk_chunks(ra8_png_state_t *st, jof_bump_t *bump, uint16_t max_w, uint16_t max_h)
Walk the chunk stream: pre-IDAT chunks, then the IDAT arm.
Definition jof_png.c:555
ra8_err_t priv_jof_png_rows(jof_pull_fn pull, void *pull_ctx, jof_bump_t *bump, uint16_t max_w, uint16_t max_h, jof_geom_fn on_geom, jof_rows_fn on_rows, void *cb_ctx)
Streaming PNG scanline decode: pull bytes in, emit rows in order.
Definition jof_png.c:579
static ra8_err_t internal_png_inflate_idat(ra8_png_state_t *st, uint32_t first_len)
Inflate the whole IDAT stream, emitting every scanline.
Definition jof_png.c:477
static ra8_err_t internal_png_consume_rows(ra8_png_state_t *st, const uint8_t *src, uint32_t avail)
Assemble, reconstruct and emit rows from freshly inflated bytes.
Definition jof_png.c:217
static ra8_err_t internal_png_inflate_step(ra8_png_state_t *st, ra8_png_iter_t *it)
One inflate iteration: refill, tinfl, progress guard, drain rows.
Definition jof_png.c:410
static ra8_err_t internal_png_bind_geometry(ra8_png_state_t *st, jof_bump_t *bump)
Fire the geometry hook and carve the pixel-path buffers.
Definition jof_png.c:279
static ra8_err_t internal_png_refill_input(ra8_png_state_t *st, uint32_t *out_avail)
Refill the compressed-input buffer from the IDAT chunk stream.
Definition jof_png.c:335
static ra8_err_t internal_png_translate(ra8_png_state_t *st, const uint8_t *row)
Translate one reconstructed source row into the output layout.
Definition jof_png.c:157
ra8_err_t priv_jof_png_chunk_hdr(ra8_png_state_t *st, uint32_t *out_len, uint32_t *out_type)
Read one 8-byte chunk header (length + type, both big-endian).
ra8_err_t priv_jof_png_pre_idat(ra8_png_state_t *st, uint32_t len, uint32_t type)
Dispatch one pre-IDAT chunk (PLTE / tRNS / ancillary / stray IEND).
ra8_err_t priv_jof_png_prologue(ra8_png_state_t *st, uint16_t max_w, uint16_t max_h)
Verify the 8-byte signature, then parse + validate the IHDR.
ra8_err_t priv_jof_png_pull_exact(ra8_png_state_t *st, uint8_t *buf, uint32_t len)
Pull exactly len bytes; EOF mid-read is a protocol error.
ra8_err_t priv_jof_png_skip(ra8_png_state_t *st, uint32_t len)
Discard exactly len source bytes (unknown / ancillary chunks).
ra8_err_t priv_jof_png_finish(ra8_png_state_t *st)
Walk the post-IDAT chunks until IEND (ancillary chunks skipped).
Module-private declarations shared by the two PNG decoder units.
@ k_ra8_png_opaque
Fully-opaque alpha value.
@ k_ra8_png_filter_none
Row filter: none.
@ k_ra8_png_filter_avg
Row filter: average.
@ k_ra8_png_ch_3
3 bytes per pixel.
@ k_ra8_png_filter_sub
Row filter: sub.
@ k_ra8_png_color_gray
Grayscale.
@ k_ra8_png_filter_paeth
Row filter: Paeth.
@ k_ra8_png_color_ga
Grayscale + alpha.
@ k_ra8_png_color_pal
Indexed-colour.
@ k_ra8_png_ch_2
2 bytes per pixel.
@ k_ra8_png_filter_up
Row filter: up.
@ k_ra8_png_ch_1
1 byte per pixel.
@ k_ra8_png_ch_4
4 bytes per pixel.
@ k_ra8_png_max_chunks
Chunk-walk budget (hostile cap).
@ k_ra8_png_type_idat
"IDAT" big-endian.
@ k_ra8_png_inbuf_bytes
Compressed-input chunk buffer.
@ k_ra8_png_ring_bytes
Inflate ring (power of 2).
@ k_ra8_png_crc_bytes
Trailing chunk CRC field.
ra8_err_t(* jof_pull_fn)(void *ctx, uint8_t *buf, size_t cap, size_t *got)
Forward byte source for the producer (DIP seam).
static const char * s_tag
Logging / check tag.
Definition ra8_app.c:17
Annotation-attribute framework macros for ra8-firmware.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#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_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_protocol_error
Protocol-level error (e.g.
Definition ra8_err.h:429
@ 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 * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
Lightweight Logging Interface for ra8-firmware.
Linear (bump) allocator over the caller's work arena.
Inflate-loop cursor: compressed-input window + progress guard.
Definition jof_png.c:381
uint8_t stalls
Consecutive zero-progress iterations.
Definition jof_png.c:384
uint8_t done
1 once tinfl reported stream end.
Definition jof_png.c:385
uint32_t in_pos
Consumed compressed bytes.
Definition jof_png.c:383
uint32_t in_avail
Valid compressed bytes in inbuf.
Definition jof_png.c:382
Whole streaming PNG decode state (module-static instance).
uint16_t trns_count
tRNS entries present.
uint16_t rows_done
Rows emitted so far.
uint32_t pending_type
Pending chunk type (big-endian value).
uint8_t * prevrow
Carved previous unfiltered row.
tinfl_decompressor * inflator
Carved inflate context.
jof_geom_fn on_geom
Producer geometry hook.
uint8_t pending_valid
1 when a pending chunk header is held.
uint8_t src_ch
Source bytes per pixel (filter bpp).
void * pull_ctx
Context for pull.
uint8_t * xlat
Carved translated output row.
uint32_t idat_rem
Bytes left in the current IDAT chunk.
uint8_t source_done
1 once a non-IDAT chunk ended the data.
uint32_t pending_len
Pending chunk payload length.
uint8_t * inbuf
Carved compressed-input buffer.
void * cb_ctx
Producer context.
jof_pull_fn pull
Sequential byte source.
uint8_t * ring
Carved 64 KiB inflate ring.
uint8_t trns[k_ra8_png_trns_max]
Palette alpha (tRNS).
uint8_t has_trns
1 once tRNS parsed.
uint16_t h
Image height, pixels.
jof_rows_fn on_rows
Producer row sink.
uint8_t * rowbuf
Carved scanline (filter + pixels).
uint32_t rowfill
Scanline bytes assembled so far.
uint16_t plte_count
Palette entries present.
uint32_t ring_wr
Ring write offset.
uint8_t palette[k_ra8_png_plte_max]
PLTE payload (RGB triples).
uint8_t has_plte
1 once PLTE parsed.
uint32_t rowlen
Scanline bytes (1 + w * src_ch).
uint8_t color_type
IHDR colour type.
uint8_t dst_ch
Output bytes per pixel (1, 3 or 4).
uint16_t w
Image width, pixels.