|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
Void-and-cluster blue-noise dithering: continuous-tone gray8 -> 16-level panel (#477). More...
Go to the source code of this file.
Enumerations | |
| enum | ra8_gfx_dither_const_t : uint8_t { k_ra8_gfx_dither_levels = 16 , k_ra8_gfx_dither_step = 17 , k_ra8_gfx_dither_max_level = 15 , k_ra8_gfx_dither_nib_shift = 4 , k_ra8_gfx_dither_ppb = 2 , k_ra8_gfx_dither_mask_dim = 64 , k_ra8_gfx_dither_mask_index_mask = 63 , k_ra8_gfx_dither_rgb_g_shift = 8 , k_ra8_gfx_dither_rgb_r_shift = 16 } |
| 16-level palette, packing, and blue-noise mask geometry constants. More... | |
| enum | ra8_gfx_dither_scale_t : uint16_t { k_ra8_gfx_dither_byte_levels = 256 , k_ra8_gfx_dither_mask_len = 4096 } |
| Fixed-point scales for the unbiased threshold comparison. More... | |
Functions | |
| uint8_t | ra8_gfx_dither_gray4_level (uint8_t gray8, int32_t x, int32_t y) |
| Quantise one gray8 sample to a 4-bit panel level via the blue-noise mask. | |
| ra8_err_t | ra8_gfx_dither_gray8_to_gray4 (const uint8_t *src, int32_t w, int32_t h, int32_t origin_x, int32_t origin_y, uint8_t *out, uint32_t out_cap, uint32_t *out_size) |
| Dither a gray8 tile to packed 4-bpp nibbles (panel-native), seamlessly. | |
| ra8_err_t | ra8_gfx_blit_gray8_dither (const uint8_t *src, int32_t w, int32_t h, int32_t dst_x, int32_t dst_y) |
| Blit a gray8 image into the bound framebuffer, blue-noise dithered. | |
Void-and-cluster blue-noise dithering: continuous-tone gray8 -> 16-level panel (#477).
The shipping e-reader panel (Waveshare 6inch HD, IT8951) shows 16 gray levels (4 bpp); the retained source figures (#476) carry 256. Hard-quantising 256 -> 16 bands smooth gradients into visible steps. This module removes the banding by thresholding every pixel against a precomputed blue-noise mask (ra8_gfx_dither_mask_internal.h, generated by scripts/gen/gen_bluenoise_mask.py) indexed purely by the pixel's absolute (x, y) position plus its own value – Ulichney's void-and-cluster method (1993), NOT error diffusion.
Why a position-indexed mask and not Floyd-Steinberg: the reader is tiled, zoomable, must be deterministic (ra8_emulator byte == silicon, the EIL==HIL rule), and wants Helium/MVE. A mask threshold depends on no neighbour state, so it is:
Definition in file ra8_gfx_dither.h.
| enum ra8_gfx_dither_const_t : uint8_t |
16-level palette, packing, and blue-noise mask geometry constants.
The panel palette is 16 evenly-spaced levels at n * k_ra8_gfx_dither_step for n in [0, k_ra8_gfx_dither_max_level]. The mask edge is a power of two so the toroidal index is a bitmask (coord & k_ra8_gfx_dither_mask_index_mask) rather than a divide, and negative coordinates wrap correctly under the same two's-complement mask.
Definition at line 68 of file ra8_gfx_dither.h.
| enum ra8_gfx_dither_scale_t : uint16_t |
Fixed-point scales for the unbiased threshold comparison.
The rounding decision mask * step < rem * byte_levels keeps the per-pixel round-up probability exactly rem / step when the mask value is uniform over [0, k_ra8_gfx_dither_byte_levels).
| Enumerator | |
|---|---|
| k_ra8_gfx_dither_byte_levels | Threshold texture depth (mask range +1). |
| k_ra8_gfx_dither_mask_len | Mask entry count (mask_dim * mask_dim). |
Definition at line 89 of file ra8_gfx_dither.h.
|
nodiscard |
Blit a gray8 image into the bound framebuffer, blue-noise dithered.
The render entry point: for each pixel of the w x h row-major gray8 source it computes the dithered level with ra8_gfx_dither_gray4_level, expands that level to the 8-bit gray (n << 4) | n, broadcasts it to 0x00RRGGBB, and writes it through the shared clipped plotter at framebuffer coordinate (dst_x + col, dst_y + row) – so the visible output is the panel's 16 levels arranged as blue-noise grain, down-converted to the bound pixel format exactly as ra8_gfx_blit_gray8 down-converts a hard tone. The mask is indexed at absolute framebuffer coordinates, so repainting a damaged sub-rectangle (via ra8_gfx_set_clip) reproduces the same grain that a full-frame paint would – dirty-region updates never seam.
| [in] | src | Row-major gray8 source of at least w * h bytes. |
| [in] | w | Source width in pixels (> 0; also the row stride). |
| [in] | h | Source height in pixels (> 0). |
| [in] | dst_x | Destination column of the source top-left in the framebuffer. |
| [in] | dst_y | Destination row of the source top-left in the framebuffer. |
| k_ra8_ok | Visible pixels written (or fully clipped out). |
| k_ra8_err_not_initialized | ra8_gfx_init() was not called. |
| k_ra8_err_invalid_arg | src is NULL, or w / h <= 0. |
src holds at least w * h readable bytes. Definition at line 220 of file ra8_gfx_dither.c.
References g_gfx_text_state, internal_level_to_color(), internal_mask_index(), internal_quantise(), k_ra8_err_invalid_arg, k_ra8_err_not_initialized, k_ra8_ok, priv_gfx_text_plot(), and s_ra8_gfx_dither_mask.
| uint8_t ra8_gfx_dither_gray4_level | ( | uint8_t | gray8, |
| int32_t | x, | ||
| int32_t | y ) |
Quantise one gray8 sample to a 4-bit panel level via the blue-noise mask.
The atomic dither operation and the seam that pins the quantisation maths. Looks up the blue-noise threshold for the pixel at absolute coordinates (x, y) – s_ra8_gfx_dither_mask[(y & 63) * 64 + (x & 63)] – and returns v / 17 rounded up to v / 17 + 1 when that threshold is below the pixel's fractional distance to the next level. Indexing at absolute coordinates (not tile-local) is what makes tiled rendering seamless: the same pixel gets the same threshold regardless of which tile drew it. Pure: reads only its arguments and the const mask, mutates nothing.
| [in] | gray8 | Source luminance sample, 0 (black) .. 255 (white). |
| [in] | x | Absolute panel/framebuffer column (any int32; wraps mod 64). |
| [in] | y | Absolute panel/framebuffer row (any int32; wraps mod 64). |
| 0 | The pixel quantised to black. |
| 15 | The pixel quantised to white (the maximum level). |
Definition at line 183 of file ra8_gfx_dither.c.
References internal_mask_index(), internal_quantise(), and s_ra8_gfx_dither_mask.
|
nodiscard |
Dither a gray8 tile to packed 4-bpp nibbles (panel-native), seamlessly.
Applies ra8_gfx_dither_gray4_level to every pixel of the w x h row-major gray8 tile and packs the resulting levels two per byte: the pixel at flat index i occupies the high nibble when i is even and the low nibble when odd (byte = (level[2k] << 4) | level[2k+1]), the same layout ra8_gfx_blit_gray4_zoom unpacks and ra8_rabook_gray4_encode produces. The mask is indexed at absolute coordinates (origin_x + col, origin_y + row), so a large image split into tiles dithers identically to the whole image – no tile-boundary seams. Output size is (w * h + 1) / 2 bytes; out must be at least that large.
| [in] | src | Row-major gray8 tile of at least w * h bytes. |
| [in] | w | Tile width in pixels (> 0; also the source row stride). |
| [in] | h | Tile height in pixels (> 0). |
| [in] | origin_x | Absolute panel column of the tile's left edge (mask phase). |
| [in] | origin_y | Absolute panel row of the tile's top edge (mask phase). |
| [out] | out | Packed-gray4 output; >= (w * h + 1) / 2 writable bytes. |
| [in] | out_cap | Capacity of out in bytes. |
| [out] | out_size | On success, the byte count written ((w * h + 1) / 2). |
| k_ra8_ok | Tile dithered and packed. |
| k_ra8_err_null_ptr | src, out, or out_size is NULL. |
| k_ra8_err_invalid_arg | w <= 0 or h <= 0. |
| k_ra8_err_no_mem | out_cap < (w * h + 1) / 2. |
src holds at least w * h readable bytes. out and out_size are non-NULL and out does not overlap src. out_size == (w * h + 1) / 2 and every packed nibble is in [0, k_ra8_gfx_dither_max_level]. out and *out_size are unmodified past the point of the failing check.out; holds no shared state. Definition at line 188 of file ra8_gfx_dither.c.
References internal_pack_tile(), k_ra8_err_invalid_arg, k_ra8_err_no_mem, k_ra8_gfx_dither_ppb, k_ra8_ok, RA8_CHECK_NULL_PTR, and ra8_log_error.
Referenced by internal_emit_strip().