ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ra8_gfx_dither.c File Reference

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"
Include dependency graph for ra8_gfx_dither.c:

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.

Detailed Description

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.

Since
0.1.0

Definition in file ra8_gfx_dither.c.

Function Documentation

◆ internal_level_to_color()

uint32_t internal_level_to_color ( uint8_t level)
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.

Parameters
[in]level4-bit panel level, 0 .. k_ra8_gfx_dither_max_level.
Returns
0x00RRGGBB gray colour with R == G == B.
Return values
0x00000000level is 0 (black).
0x00FFFFFFlevel is 15 (white).
Precondition
level <= k_ra8_gfx_dither_max_level (caller guarantees).
The shift constants are valid compile-time literals.
Postcondition
The returned colour has equal R, G and B channels.
No memory is modified (pure function).
Note
Thread-safe; reads only its argument and compile-time constants.
Since
0.1.0

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().

◆ internal_mask_index()

uint32_t internal_mask_index ( int32_t x,
int32_t y )
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).

Parameters
[in]xAbsolute column (any int32).
[in]yAbsolute row (any int32).
Returns
Flat mask index in [0, k_ra8_gfx_dither_mask_len).
Return values
0Both coordinates land on the mask origin column and row.
Precondition
k_ra8_gfx_dither_mask_dim is a power of two (asserted above).
The mask index bitmask equals dim - 1 (asserted above).
Postcondition
The result is a valid subscript into s_ra8_gfx_dither_mask.
No memory is modified (pure function).
Note
Thread-safe; reads only its arguments and compile-time constants.
Since
0.1.0

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().

◆ internal_pack_tile()

void internal_pack_tile ( const uint8_t * src,
int32_t w,
int32_t h,
int32_t ox,
int32_t oy,
uint8_t * out )
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.

Parameters
[in]srcRow-major gray8 tile of w * h bytes.
[in]wTile width in pixels (> 0; caller-checked).
[in]hTile height in pixels (> 0; caller-checked).
[in]oxAbsolute panel column of the tile's left edge (mask phase).
[in]oyAbsolute panel row of the tile's top edge (mask phase).
[out]outPacked-gray4 output; >= (w * h + 1) / 2 writable bytes.
Precondition
src and out are non-NULL and do not overlap (caller-checked).
w > 0 and h > 0 (caller-checked).
Postcondition
Every packed nibble is in [0, k_ra8_gfx_dither_max_level].
out[0 .. (w*h+1)/2) holds the dithered, packed tile.
Note
Not thread-safe only in that it writes out; holds no shared state.
Since
0.1.0

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().

◆ internal_quantise()

uint8_t internal_quantise ( uint8_t gray8,
uint8_t thr )
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.

Parameters
[in]gray8Source luminance sample, 0 (black) .. 255 (white).
[in]thrBlue-noise threshold for the pixel, 0 .. 255.
Returns
The dithered 4-bit level, 0 .. k_ra8_gfx_dither_max_level.
Return values
0The pixel quantised to black.
15The pixel quantised to white.
Precondition
thr is a mask byte in [0, 255].
The palette step and byte-level scale are valid compile-time constants.
Postcondition
The result is in [0, k_ra8_gfx_dither_max_level].
No memory is modified (pure function).
Note
Thread-safe; reads only its arguments and compile-time constants.
Since
0.1.0

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().

◆ ra8_gfx_blit_gray8_dither()

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 )
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.

Parameters
[in]srcRow-major gray8 source of at least w * h bytes.
[in]wSource width in pixels (> 0; also the row stride).
[in]hSource height in pixels (> 0).
[in]dst_xDestination column of the source top-left in the framebuffer.
[in]dst_yDestination row of the source top-left in the framebuffer.
Returns
Error code.
Return values
k_ra8_okVisible pixels written (or fully clipped out).
k_ra8_err_not_initializedra8_gfx_init() was not called.
k_ra8_err_invalid_argsrc is NULL, or w / h <= 0.
Precondition
ra8_gfx_init() returned k_ra8_ok.
src holds at least w * h readable bytes.
Postcondition
Each in-clip destination pixel equals its dithered, down-converted level.
Pixels outside the clip rectangle are left unchanged.
Note
Not thread-safe; shares the single ra8_gfx bind state.
See also
ra8_gfx_blit_gray8 Same geometry, hard tone (no dither).
Since
0.1.0

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.

◆ ra8_gfx_dither_gray4_level()

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.

Parameters
[in]gray8Source luminance sample, 0 (black) .. 255 (white).
[in]xAbsolute panel/framebuffer column (any int32; wraps mod 64).
[in]yAbsolute panel/framebuffer row (any int32; wraps mod 64).
Returns
The dithered 4-bit level.
Return values
0The pixel quantised to black.
15The pixel quantised to white (the maximum level).
Precondition
None – every uint8 value and every int32 coordinate is in range.
The blue-noise mask table is linked (compile-time const).
Postcondition
The result is in [0, k_ra8_gfx_dither_max_level].
No memory is modified (pure function).
Note
Thread-safe: reads only its arguments and immutable data; ISR-safe.
Example:
uint8_t level = ra8_gfx_dither_gray4_level(src[y * w + x], tile_x + x, tile_y + y);
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.
See also
ra8_gfx_dither_gray8_to_gray4 Bulk tile -> packed 4 bpp using this rule.
Since
0.1.0

Definition at line 183 of file ra8_gfx_dither.c.

References internal_mask_index(), internal_quantise(), and s_ra8_gfx_dither_mask.

◆ ra8_gfx_dither_gray8_to_gray4()

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 )
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.

Parameters
[in]srcRow-major gray8 tile of at least w * h bytes.
[in]wTile width in pixels (> 0; also the source row stride).
[in]hTile height in pixels (> 0).
[in]origin_xAbsolute panel column of the tile's left edge (mask phase).
[in]origin_yAbsolute panel row of the tile's top edge (mask phase).
[out]outPacked-gray4 output; >= (w * h + 1) / 2 writable bytes.
[in]out_capCapacity of out in bytes.
[out]out_sizeOn success, the byte count written ((w * h + 1) / 2).
Returns
Error code.
Return values
k_ra8_okTile dithered and packed.
k_ra8_err_null_ptrsrc, out, or out_size is NULL.
k_ra8_err_invalid_argw <= 0 or h <= 0.
k_ra8_err_no_memout_cap < (w * h + 1) / 2.
Precondition
src holds at least w * h readable bytes.
out and out_size are non-NULL and out does not overlap src.
Postcondition
On k_ra8_ok, *out_size == (w * h + 1) / 2 and every packed nibble is in [0, k_ra8_gfx_dither_max_level].
On any error return, out and *out_size are unmodified past the point of the failing check.
Note
Not thread-safe only in that it writes out; holds no shared state.
See also
ra8_gfx_blit_gray8_dither Dither straight into the bound framebuffer.
Since
0.1.0

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().