|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
Void-and-cluster blue-noise dither: gray8 -> 16-level e-ink panel (#477). More...
#include "ra8_gfx_dither.h"#include <stddef.h>#include <stdint.h>#include "ra8_attributes.h"#include "ra8_check.h"#include "ra8_err.h"#include "ra8_gfx_dither_mask_internal.h"#include "ra8_gfx_internal.h"#include "ra8_log.h"Go to the source code of this file.
Functions | |
| static uint32_t | internal_mask_index (int32_t x, int32_t y) |
Toroidal blue-noise mask index for absolute panel coordinate (x, y). | |
| static uint8_t | internal_quantise (uint8_t gray8, uint8_t thr) |
| Quantise a gray8 sample to a 4-bit level given its blue-noise threshold. | |
| static uint32_t | internal_level_to_color (uint8_t level) |
| Expand a 4-bit panel level to a 0x00RRGGBB gray colour. | |
| static void | internal_pack_tile (const uint8_t *src, int32_t w, int32_t h, int32_t ox, int32_t oy, uint8_t *out) |
Dither a gray8 tile to packed 4-bpp nibbles, mask-phased at (ox, oy). | |
| 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 dither: gray8 -> 16-level e-ink panel (#477).
Implements the three ra8_gfx_dither.h entry points over one shared pair of primitives: internal_mask_index maps absolute panel coordinates onto the committed blue-noise mask (s_ra8_gfx_dither_mask, baked by scripts/gen/gen_bluenoise_mask.py), and internal_quantise turns a gray8 sample plus its threshold into a 4-bit level. Because the threshold depends on no neighbour state, a large image split into tiles dithers byte-for-byte the same as the whole image, and the transform is pure integer arithmetic over a const table – host, ra8_emulator, and silicon emit identical bytes (the EIL==HIL rule). The single-pixel (ra8_gfx_dither_gray4_level), bulk-pack (internal_pack_tile) and render-blit (ra8_gfx_blit_gray8_dither) paths each compose those primitives directly, so the hot loops carry no per-pixel call through the public API. Scalar-first; a Helium/MVE lane-wise pass can replace the inner loops later without moving the mask or the quantise rule.
Definition in file ra8_gfx_dither.c.
|
static |
Expand a 4-bit panel level to a 0x00RRGGBB gray colour.
Replicates the nibble into an 8-bit gray ((n << 4) | n == n * 17) and broadcasts it across R, G and B – byte-identical to the gray level ra8_gfx_blit_gray8 and ra8_gfx_blit_gray4_zoom produce for the same level, so the dithered blit down-converts the same way. The nibble is widened to uint32_t before shifting so no cast is applied to a composite expression.
| [in] | level | 4-bit panel level, 0 .. k_ra8_gfx_dither_max_level. |
| 0x00000000 | level is 0 (black). |
| 0x00FFFFFF | level is 15 (white). |
level <= k_ra8_gfx_dither_max_level (caller guarantees). Definition at line 133 of file ra8_gfx_dither.c.
References k_ra8_gfx_dither_nib_shift, k_ra8_gfx_dither_rgb_g_shift, and k_ra8_gfx_dither_rgb_r_shift.
Referenced by ra8_gfx_blit_gray8_dither().
|
static |
Toroidal blue-noise mask index for absolute panel coordinate (x, y).
Reduces the coordinates onto the mask edge with a bitmask (the edge is a power of two): (y & (dim - 1)) * dim + (x & (dim - 1)). AND with dim - 1 yields the mathematically-correct non-negative modulo for negative coordinates too, so a lens window drawn at a negative offset still lands on the same continuous mask phase (seamless tiling).
| [in] | x | Absolute column (any int32). |
| [in] | y | Absolute row (any int32). |
| 0 | Both coordinates land on the mask origin column and row. |
Definition at line 66 of file ra8_gfx_dither.c.
References k_ra8_gfx_dither_mask_dim, and k_ra8_gfx_dither_mask_index_mask.
Referenced by internal_pack_tile(), ra8_gfx_blit_gray8_dither(), and ra8_gfx_dither_gray4_level().
|
static |
Dither a gray8 tile to packed 4-bpp nibbles, mask-phased at (ox, oy).
The bulk packer behind ra8_gfx_dither_gray8_to_gray4: for every pixel it thresholds the sample against the blue-noise mask at absolute coordinates (ox + col, oy + row) and packs the level two per byte (high nibble even, low nibble odd). Even indices assign the byte (clearing the low nibble) and odd indices OR into it, so no pre-zeroing is required even for an odd pixel count.
| [in] | src | Row-major gray8 tile of w * h bytes. |
| [in] | w | Tile width in pixels (> 0; caller-checked). |
| [in] | h | Tile height in pixels (> 0; caller-checked). |
| [in] | ox | Absolute panel column of the tile's left edge (mask phase). |
| [in] | oy | Absolute panel row of the tile's top edge (mask phase). |
| [out] | out | Packed-gray4 output; >= (w * h + 1) / 2 writable bytes. |
src and out are non-NULL and do not overlap (caller-checked). w > 0 and h > 0 (caller-checked). out[0 .. (w*h+1)/2) holds the dithered, packed tile. out; holds no shared state. Definition at line 166 of file ra8_gfx_dither.c.
References internal_mask_index(), internal_quantise(), k_ra8_gfx_dither_nib_shift, k_ra8_gfx_dither_ppb, and s_ra8_gfx_dither_mask.
Referenced by ra8_gfx_dither_gray8_to_gray4().
|
static |
Quantise a gray8 sample to a 4-bit level given its blue-noise threshold.
The base level is gray8 / step and the fractional distance to the next level is (gray8 % step) / step; the pixel rounds up when the threshold falls below that fraction. Written as the exact integer test thr * step < rem * byte_levels, the round-up probability is exactly rem / step over a uniform mask – unbiased, so flat regions reproduce their tone with no banding. No clamp is needed and none is added (it would be unreachable dead code): the base equals the maximum level only when gray8 == 255, which forces rem == 0 and hence no round-up, so the result is in [0, max_level] by construction.
| [in] | gray8 | Source luminance sample, 0 (black) .. 255 (white). |
| [in] | thr | Blue-noise threshold for the pixel, 0 .. 255. |
| 0 | The pixel quantised to black. |
| 15 | The pixel quantised to white. |
thr is a mask byte in [0, 255]. Definition at line 99 of file ra8_gfx_dither.c.
References k_ra8_gfx_dither_byte_levels, and k_ra8_gfx_dither_step.
Referenced by internal_pack_tile(), ra8_gfx_blit_gray8_dither(), and ra8_gfx_dither_gray4_level().
|
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().