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

Tap-to-zoom image viewer: viewport state machine + tiled magnifying render (#478). More...

#include <stdint.h>
#include "ra8_err.h"
#include "ra8_ui.h"
Include dependency graph for zoom.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  zoom_source_t
 A magnifiable full-resolution image behind the gray8 sub-rect seam. More...
struct  zoom_scratch_t
 Caller-owned composite buffers – the module's entire memory footprint. More...
struct  zoom_present_t
 What the reader must flush, and with which waveform. More...
struct  zoom_view_cfg_t
 Everything zoom_view_open needs; nothing it does not. More...
struct  zoom_view_t
 Live viewport: where it is looking, how magnified, and what it owes the panel. More...

Typedefs

typedef ra8_err_t(* zoom_read_fn) (void *ctx, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t *out, uint32_t out_stride)
 Read a sub-rectangle of the magnified source as gray8 (the DIP seam).

Enumerations

enum  zoom_limits_t : uint8_t {
  k_zoom_scale_min = 1U ,
  k_zoom_scale_max = 8U ,
  k_zoom_strip_rows_max = 32U ,
  k_zoom_bg_gray = 255U
}
 Magnification ladder bounds, strip geometry and the letterbox fill. More...
enum  zoom_dim_t : uint32_t { k_zoom_dim_max = 65535U }
 Bound on a source dimension the engine will magnify. More...
enum  zoom_timing_t : uint16_t { k_zoom_settle_ms_default = 350U }
 Default gesture-settle delay, in milliseconds. More...
enum  zoom_refresh_t : uint8_t {
  k_zoom_refresh_fast = 0U ,
  k_zoom_refresh_quality = 1U
}
 Waveform class the next flush of the viewport should use. More...
enum  zoom_policy_t : uint8_t {
  k_zoom_policy_responsive = 0U ,
  k_zoom_policy_quality = 1U
}
 How a viewport change is scheduled against the panel's waveforms. More...
enum  zoom_pan_t : uint8_t {
  k_zoom_pan_none = 0U ,
  k_zoom_pan_left = 1U ,
  k_zoom_pan_right = 2U ,
  k_zoom_pan_up = 3U ,
  k_zoom_pan_down = 4U
}
 Discrete pan direction for tap-band navigation. More...

Functions

ra8_err_t zoom_source_init (zoom_source_t *out, zoom_read_fn read, void *ctx, uint32_t width, uint32_t height)
 Bind a gray8 sub-rectangle reader and its extent as a magnifiable source.
ra8_err_t zoom_view_open (zoom_view_t *v, const zoom_view_cfg_t *cfg)
 Open a viewport onto a source, centred on a source-pixel focus.
ra8_err_t zoom_view_rebind (zoom_view_t *v, const zoom_source_t *src, uint32_t now_ms)
 Point an open view at a different source, keeping zoom and position.
ra8_err_t zoom_view_invalidate (zoom_view_t *v, uint32_t now_ms)
 Declare the viewport's pixels stale for a reason the engine cannot see.
ra8_err_t zoom_view_close (zoom_view_t *v)
 Close a view; it stops owing the panel anything.
bool zoom_view_active (const zoom_view_t *v)
 Whether a view is open.
ra8_err_t zoom_view_set_scale (zoom_view_t *v, uint8_t scale, int32_t focus_x, int32_t focus_y, uint32_t now_ms)
 Set the magnification, keeping a panel point fixed under the finger.
uint8_t zoom_scale_cycle (uint8_t scale, uint8_t min, uint8_t max)
 Next magnification on the doubling ladder, wrapping back to the minimum.
ra8_err_t zoom_view_pan (zoom_view_t *v, int32_t dx, int32_t dy, uint32_t now_ms)
 Pan the viewport by a destination-pixel delta.
ra8_err_t zoom_view_pan_dir (zoom_view_t *v, zoom_pan_t dir, uint32_t now_ms)
 Pan by a discrete step in one direction (the tap-band interaction).
ra8_err_t zoom_view_window (const zoom_view_t *v, ra8_ui_rect_t *out)
 The source rectangle currently visible, in source pixels.
bool zoom_view_tick (zoom_view_t *v, uint32_t now_ms)
 Advance the settle timer; promote a bi-level view to full quality.
ra8_err_t zoom_view_present (zoom_view_t *v, zoom_present_t *out)
 Take the pending flush plan, clearing it.
ra8_err_t zoom_view_render (zoom_view_t *v)
 Composite the visible window into the bound ra8_gfx framebuffer.

Detailed Description

Tap-to-zoom image viewer: viewport state machine + tiled magnifying render (#478).

The reader half of the "keep the pixels, magnify into them" decision: .rabook import deliberately does NOT downscale (#210-213), so the reader must be able to magnify a retained full-resolution figure or manga panel instead. This module is that magnifier – one viewport engine serving both presentations the reader needs:

  • full-screen zoom, where zoom_view_t::dst is the whole panel and a tapped EPUB figure or comic page opens into it; and
  • loupe / lens, where dst is a small box composited over the page. Only the lens rectangle changes, so dragging it costs one small partial e-ink update instead of a full-panel refresh.

Both are the same code; the mode is entirely a matter of how big dst is.

The source seam (Dependency Inversion)
The engine never knows what it is magnifying. It pulls through one function pointer, zoom_read_fn – "give me this source rectangle as gray8" – so a .rabook image pool (zoom_book.h), a JOF atlas paged through an ra8_tile_cache (zoom_tiles.h), and a unit-test pattern generator are interchangeable behind it (Liskov). gray8 is the one depth every retained source can produce losslessly: gray4 quantisation is not reversible, so re-dithering must start from continuous tone.
Coordinate model – the magnified plane
The viewport anchor is NOT a source pixel. It is a coordinate in the magnified image plane: the source scaled by zoom_view_t::scale, so the plane is width * scale by height * scale and destination column c shows plane column anchor_x + c. Two properties fall out of that choice, and both matter:
  1. Pan granularity stays one destination pixel at every zoom. An anchor held in source pixels would quantise panning to scale destination pixels, so a 4x view could only pan in 4-pixel jumps.
  2. The blue-noise dither phase is stable under pan. The mask (#477) is indexed at plane coordinates, so a given image pixel gets the same threshold no matter where the viewport sits. Phasing on panel coordinates – what ra8_gfx_blit_gray8_dither does, correctly, for static chrome – would re-roll the grain on every pan step and shimmer.

An anchor may be negative: when the image is smaller than the viewport at this zoom it is centred, and the letterbox is filled with k_zoom_bg_gray.

Memory – why there is no big buffer
A naive zoom viewer decodes the visible window and holds it: 1024x600 gray8 is 600 KiB, and it cannot be malloc'd (NASA P10 Rule 3, zero dynamic allocation after init). This engine never holds the window. It composites in horizontal strips of at most k_zoom_strip_rows_max destination rows, so the resident cost is O(dst.w * strip_rows), not O(dst.w * dst.h) – about 25 KiB for a full-screen 1024-wide viewport at 16 rows, small enough to sit in SRAM. Every buffer is supplied by the caller in zoom_scratch_t; the module allocates nothing and owns nothing. The large working set stays where it already is and is already budgeted: the tile cache in SDRAM.
Refresh behaviour on a 16-level e-ink panel
A pan moves every pixel in the viewport, so there is no "small dirty region" to exploit – the viewport must be flushed whole. What can be exploited is the waveform: under k_zoom_policy_responsive an interactive burst flushes with k_zoom_refresh_fast (the panel's A2 waveform, ~10x faster than GC16 but bi-level), and zoom_view_tick promotes the view to k_zoom_refresh_quality once the gesture has been still for zoom_view_t::settle_ms, repainting the same rectangle in all 16 levels. k_zoom_policy_quality opts out and pays full GC16 per step. The engine emits the decision (zoom_present_t) rather than calling the display PAL, so it stays a pure integer module: identical on host, ra8_emulator and silicon.
Note
No entry point in this module is thread-safe or ISR-safe; zoom_view_render additionally writes through the single ra8_gfx framebuffer binding.
See also
zoom_book.h Bind a .rabook image pool as a source.
zoom_tiles.h Bind an ra8_tile_cache tiled atlas as a source.
ra8_gfx_dither.h The blue-noise quantiser this render re-dithers through.
Since
0.1.0

Definition in file zoom.h.

Typedef Documentation

◆ zoom_read_fn

typedef ra8_err_t(* zoom_read_fn) (void *ctx, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t *out, uint32_t out_stride)

Read a sub-rectangle of the magnified source as gray8 (the DIP seam).

The single seam through which the engine sees pixels. An implementation must write exactly w bytes at each of h successive out_stride offsets, one gray8 sample per source pixel, and must fail rather than truncate if the rectangle leaves the image – the engine only ever asks for rectangles inside the declared zoom_source_t::width x zoom_source_t::height, so an out-of-range request is a defect, not a clamp.

The engine requests exactly one row at a time (h == 1), and never re-requests a row it already holds, so an implementation backed by a cache sees each source row faulted once per frame.

Parameters
[in]ctxImplementation context supplied at bind time (may be NULL).
[in]xLeft edge of the requested rectangle, source pixels.
[in]yTop edge of the requested rectangle, source pixels.
[in]wWidth in source pixels (> 0).
[in]hHeight in source pixels (> 0).
[out]outDestination gray8 buffer of at least out_stride * h bytes.
[in]out_strideBytes between successive output rows (>= w).
Returns
Error code; anything but k_ra8_ok aborts the frame and is returned verbatim to the caller of zoom_view_render.
Return values
k_ra8_okThe rectangle was written.
Precondition
The rectangle lies wholly inside the declared source extent.
out addresses at least out_stride * h writable bytes.
Postcondition
On success every out[r * out_stride + c] is the gray8 value of source pixel (x + c, y + r).
On failure out content is unspecified and the frame is abandoned.
Note
Called from zoom_view_render only; inherits its (non-)thread-safety.
See also
zoom_source_init
Since
0.1.0

Definition at line 267 of file zoom.h.

Enumeration Type Documentation

◆ zoom_dim_t

enum zoom_dim_t : uint32_t

Bound on a source dimension the engine will magnify.

Caps width * k_zoom_scale_max (the widest magnified plane) far inside int32_t, so every plane-coordinate expression in the engine is overflow-free by construction rather than by inspection. The JOF atlas format already caps an edge at 32768 px, so this is not a practical restriction on any retained source.

Invariant
k_zoom_dim_max * k_zoom_scale_max < INT32_MAX.
See also
zoom_source_init
Since
0.1.0
Enumerator
k_zoom_dim_max 

Largest accepted source width or height, pixels.

Definition at line 138 of file zoom.h.

◆ zoom_limits_t

enum zoom_limits_t : uint8_t

Magnification ladder bounds, strip geometry and the letterbox fill.

k_zoom_scale_max is a hard ceiling on the engine, not a policy: a caller narrows it further through zoom_view_cfg_t::scale_max. The ladder is integer-only because every magnifying primitive in this tree is (nearest-neighbour replication); a fractional scale would need a resampler that does not exist and would show uneven pixel blocks on a 16-level panel.

k_zoom_bg_gray is 255 (white) rather than a mid grey because 255 is exactly 15 * 17 – an on-palette level – so the letterbox dithers to a flat level 15 with no grain, which is what a page margin should look like.

Invariant
k_zoom_scale_min <= k_zoom_scale_max.
k_zoom_bg_gray is an exact multiple of the 16-level palette step.
Example:
cfg.scale = k_zoom_scale_min; // open at full source resolution
cfg.scale_max = 4U; // this reader stops at 4x
@ k_zoom_scale_min
1:1 – one source pixel per panel pixel.
Definition zoom.h:118
See also
zoom_view_cfg_t
Since
0.1.0
Enumerator
k_zoom_scale_min 

1:1 – one source pixel per panel pixel.

k_zoom_scale_max 

Engine ceiling on integer magnification.

k_zoom_strip_rows_max 

Destination rows composited per strip.

k_zoom_bg_gray 

Letterbox fill level (on-palette white).

Definition at line 117 of file zoom.h.

◆ zoom_pan_t

enum zoom_pan_t : uint8_t

Discrete pan direction for tap-band navigation.

The reader's touch path (GT911) reports contacts, not drags, so the shipped interaction is edge tap-bands rather than a fling. The direction names the way the content travels under the viewport, matching ra8_tile_pan_dir_t so the tiled adapter can forward it to the cache's read-ahead without a translation table.

Invariant
k_zoom_pan_none names "no movement" and is the zero value.
See also
zoom_view_pan_dir
Since
0.1.0
Enumerator
k_zoom_pan_none 

No movement.

k_zoom_pan_left 

Viewport travels toward -x.

k_zoom_pan_right 

Viewport travels toward +x.

k_zoom_pan_up 

Viewport travels toward -y.

k_zoom_pan_down 

Viewport travels toward +y.

Definition at line 222 of file zoom.h.

◆ zoom_policy_t

enum zoom_policy_t : uint8_t

How a viewport change is scheduled against the panel's waveforms.

The one genuine product fork in this module, so both halves are available rather than one being chosen silently. k_zoom_policy_responsive trades tone for latency during a gesture and pays the quality repaint once, on settle; k_zoom_policy_quality never shows a bi-level frame and pays a full GC16 (~450 ms) for every pan step. A photographic figure wants the second on a slow panel; text and line art want the first.

Invariant
Under k_zoom_policy_quality the engine never emits k_zoom_refresh_fast, and zoom_view_tick never arms a settle repaint.
Example:
cfg.policy = k_zoom_policy_responsive; // A2 while panning, GC16 on settle
@ k_zoom_policy_responsive
Fast during a burst, quality once settled.
Definition zoom.h:204
See also
zoom_view_tick
Since
0.1.0
Enumerator
k_zoom_policy_responsive 

Fast during a burst, quality once settled.

k_zoom_policy_quality 

Always quality; no bi-level frame is shown.

Definition at line 203 of file zoom.h.

◆ zoom_refresh_t

enum zoom_refresh_t : uint8_t

Waveform class the next flush of the viewport should use.

Deliberately not display_refresh_hint_t: this module owes the display PAL nothing and must stay linkable into a host unit test with no panel. The mapping the reader applies is one switch – fast -> k_display_refresh_fast (A2), quality -> k_display_refresh_quality (GC16).

Invariant
Exactly one of the two values is present in zoom_present_t.
See also
zoom_view_present
Since
0.1.0
Enumerator
k_zoom_refresh_fast 

Interactive burst: panel A2 (bi-level, fast).

k_zoom_refresh_quality 

Settled: panel GC16 (all 16 levels, slow).

Definition at line 174 of file zoom.h.

◆ zoom_timing_t

enum zoom_timing_t : uint16_t

Default gesture-settle delay, in milliseconds.

The interval of input silence after which zoom_view_tick promotes a fast (bi-level) view to a full 16-level repaint. Long enough that a multi-step pan is not interrupted by a 450 ms GC16 mid-gesture; short enough that a reader who has stopped sees the clean image essentially at once.

Invariant
k_zoom_settle_ms_default > 0.
See also
zoom_view_tick
Since
0.1.0
Enumerator
k_zoom_settle_ms_default 

Default input-silence settle window, ms.

Definition at line 156 of file zoom.h.

Function Documentation

◆ zoom_scale_cycle()

uint8_t zoom_scale_cycle ( uint8_t scale,
uint8_t min,
uint8_t max )
nodiscard

Next magnification on the doubling ladder, wrapping back to the minimum.

The tap-to-zoom cycle every reader in this tree uses: min -> 2*min -> 4*min -> ... -> max -> min. Doubling rather than incrementing because a 1x -> 2x step is a visible change and a 7x -> 8x step is not. The last rung is max exactly even when max is not a power of two, so a ceiling of 3 gives 1 -> 2 -> 3 -> 1.

Parameters
[in]scaleCurrent magnification.
[in]minLadder floor (0 is read as k_zoom_scale_min).
[in]maxLadder ceiling (below min is read as min).
Returns
The next magnification on the ladder.
Return values
minscale was already at or above max.
2 * scale or maxOtherwise, whichever is smaller.
Precondition
None – every input combination has a defined answer.
min and max are magnifications, not plane coordinates.
Postcondition
The result is in [min, max].
No state is modified (pure function).
Note
Thread-safe: pure integer arithmetic on its arguments.
See also
zoom_view_set_scale
Since
0.1.0

Definition at line 539 of file zoom.c.

References k_zoom_scale_dbl, k_zoom_scale_min, and min.

Referenced by ez_scene_tap().

◆ zoom_source_init()

ra8_err_t zoom_source_init ( zoom_source_t * out,
zoom_read_fn read,
void * ctx,
uint32_t width,
uint32_t height )
nodiscard

Bind a gray8 sub-rectangle reader and its extent as a magnifiable source.

Validates the reader and the extent once, here, so nothing downstream has to re-check them: the engine's plane arithmetic is overflow-free precisely because width and height passed k_zoom_dim_max at this seam.

Parameters
[out]outSource descriptor to populate.
[in]readgray8 sub-rectangle reader (non-NULL).
[in]ctxOpaque context handed back to read (may be NULL).
[in]widthFull-resolution source width, [1, k_zoom_dim_max].
[in]heightFull-resolution source height, [1, k_zoom_dim_max].
Returns
ra8_err_t Error code.
Return values
k_ra8_okout describes the source.
k_ra8_err_null_ptrout or read is NULL.
k_ra8_err_invalid_argwidth or height is 0 or exceeds the cap.
Precondition
out addresses writable storage for one zoom_source_t.
read remains valid for the lifetime of every view bound to this source.
Postcondition
On k_ra8_ok every field of out is set and out->read != NULL.
On any error out is left untouched.
Note
Not thread-safe.
See also
zoom_view_open
Since
0.1.0

Definition at line 375 of file zoom.c.

References zoom_source_t::ctx, zoom_source_t::height, internal_dim_ok(), k_ra8_err_invalid_arg, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_log_error, zoom_source_t::read, s_tag, and zoom_source_t::width.

◆ zoom_view_active()

bool zoom_view_active ( const zoom_view_t * v)
nodiscard

Whether a view is open.

Parameters
[in]vView to query (NULL reads as closed).
Returns
Openness of the view.
Return values
truev is non-NULL and open.
falsev is NULL or closed.
Precondition
None – NULL is an accepted input and answers false.
v, if non-NULL, addresses a zoom_view_t.
Postcondition
No state is modified (pure query).
The result is exactly v != NULL && v->active.
Note
Thread-safe with respect to this module: reads one bool.
See also
zoom_view_open
Since
0.1.0

Definition at line 534 of file zoom.c.

References zoom_view_t::active.

◆ zoom_view_close()

ra8_err_t zoom_view_close ( zoom_view_t * v)
nodiscard

Close a view; it stops owing the panel anything.

Parameters
[in,out]vView to close (may already be closed).
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe view is closed.
k_ra8_err_null_ptrv is NULL.
Precondition
v addresses writable storage for one zoom_view_t.
The caller has already flushed anything it intends the panel to keep.
Postcondition
v->active is false and v->pending is false.
The caller's scratch buffers and source are untouched and may be reused.
Note
Not thread-safe. Closing twice is not an error.
See also
zoom_view_open
Since
0.1.0

Definition at line 525 of file zoom.c.

References zoom_view_t::active, k_ra8_ok, zoom_view_t::pending, RA8_CHECK_NULL_PTR, s_tag, and zoom_view_t::settle_armed.

◆ zoom_view_invalidate()

ra8_err_t zoom_view_invalidate ( zoom_view_t * v,
uint32_t now_ms )
nodiscard

Declare the viewport's pixels stale for a reason the engine cannot see.

The engine marks itself dirty when it changes the view, and deliberately does not when nothing moved – a pan clamped against an edge owes the panel nothing, which is what stops an e-ink refresh per tap. That is exactly right for the engine's own state and exactly wrong for everything a compositor does around it: drawing a loupe over the viewport, lifting one off it, or repainting chrome that overlaps it all invalidate pixels the engine has no way to know about.

Without this the reader hits a silent class of bug that a continuously-scanned LCD hides completely: the framebuffer is correct, no flush is ever requested, and on an e-ink panel the change simply never appears. Call it whenever something outside the engine has damaged the viewport.

Parameters
[in,out]vOpen view.
[in]now_msCurrent millisecond timestamp for the settle timer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okA flush of the whole viewport is now owed.
k_ra8_err_null_ptrv is NULL.
k_ra8_err_invalid_statev is not open.
Precondition
v was opened by zoom_view_open.
The caller genuinely damaged (or is about to repaint) the viewport.
Postcondition
v->pending is true and v->pending_kind follows v->policy.
The anchor and the scale are untouched – only the flush bookkeeping moves.
Note
Not thread-safe.
Example:
s->lens_on = !s->lens_on; // chrome over the page
(void)zoom_view_invalidate(&s->page, ra8_time_ms()); // ...so repaint it
uint32_t ra8_time_ms(void)
Get the current 1 kHz tick count.
Definition ra8_time.c:108
ra8_err_t zoom_view_invalidate(zoom_view_t *v, uint32_t now_ms)
Declare the viewport's pixels stale for a reason the engine cannot see.
Definition zoom.c:514
See also
zoom_view_present
Since
0.1.0

Definition at line 514 of file zoom.c.

References zoom_view_t::active, internal_touch(), k_ra8_err_invalid_state, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_log_error, and s_tag.

Referenced by ez_scene_tap(), and ez_selftest_lens().

◆ zoom_view_open()

ra8_err_t zoom_view_open ( zoom_view_t * v,
const zoom_view_cfg_t * cfg )
nodiscard

Open a viewport onto a source, centred on a source-pixel focus.

Derives everything the engine needs and never re-derives it: the strip height from the scratch capacity, the effective ladder ceiling, the settle window, and the initial anchor from cfg->focus_x / focus_y clamped to the image. The view opens owing a k_zoom_refresh_quality flush regardless of policy – the first frame a reader sees must be the good one.

Parameters
[out]vView to open (overwritten wholesale).
[in]cfgConfiguration; see zoom_view_cfg_t.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe view is open and owes a quality flush.
k_ra8_err_null_ptrv, cfg, cfg->src.read, or a scratch pointer is NULL.
k_ra8_err_invalid_argEmpty viewport, out-of-range scale, or a bad source extent.
k_ra8_err_no_memA scratch buffer is too small for this viewport width.
Precondition
cfg->src was populated by zoom_source_init or an adapter binder.
Every scratch buffer in cfg outlives the view.
Postcondition
On k_ra8_ok v->active is true, v->pending is true and v->pending_kind == k_zoom_refresh_quality.
On any error v is not left half-open: v->active is false.
Note
Not thread-safe. Does not touch the framebuffer – call zoom_view_render for that.
Example:
if (zoom_view_open(&s_view, &cfg) == k_ra8_ok) { (void)zoom_view_render(&s_view); }
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
ra8_err_t zoom_view_render(zoom_view_t *v)
Composite the visible window into the bound ra8_gfx framebuffer.
ra8_err_t zoom_view_open(zoom_view_t *v, const zoom_view_cfg_t *cfg)
Open a viewport onto a source, centred on a source-pixel focus.
Definition zoom.c:482
See also
zoom_view_close
zoom_view_render
Since
0.1.0

Definition at line 482 of file zoom.c.

References zoom_view_t::active, internal_cfg_ok(), internal_view_adopt(), k_ra8_ok, RA8_CHECK_NULL_PTR, RA8_RETURN_ON_ERROR, and s_tag.

Referenced by ez_open_views().

◆ zoom_view_pan()

ra8_err_t zoom_view_pan ( zoom_view_t * v,
int32_t dx,
int32_t dy,
uint32_t now_ms )
nodiscard

Pan the viewport by a destination-pixel delta.

Moves the anchor and re-clamps it. Because the anchor lives in the magnified plane the delta is in destination pixels at every zoom, so a one-pixel pan is one pixel of panel movement whether the view is at 1:1 or 8x. A delta that would leave the image is clamped rather than rejected, and a clamped-to-no-movement pan owes no flush – panning into an edge does not cost an e-ink refresh.

Parameters
[in,out]vOpen view.
[in]dxHorizontal movement in destination pixels (+ moves content left).
[in]dyVertical movement in destination pixels (+ moves content up).
[in]now_msCurrent millisecond timestamp for the settle timer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe anchor moved, or was already against the edge.
k_ra8_err_null_ptrv is NULL.
k_ra8_err_invalid_statev is not open.
Precondition
v was opened by zoom_view_open.
dx and dy are destination pixels, not source pixels.
Postcondition
The anchor satisfies the clamp invariant of zoom_view_t.
A flush is owed if and only if the anchor actually changed.
Note
Not thread-safe.
See also
zoom_view_pan_dir
Since
0.1.0

Definition at line 595 of file zoom.c.

References zoom_view_t::active, zoom_view_t::anchor_x, zoom_view_t::anchor_y, internal_reclamp(), internal_sat_add(), internal_touch(), k_ra8_err_invalid_state, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_log_error, and s_tag.

Referenced by zoom_view_pan_dir().

◆ zoom_view_pan_dir()

ra8_err_t zoom_view_pan_dir ( zoom_view_t * v,
zoom_pan_t dir,
uint32_t now_ms )
nodiscard

Pan by a discrete step in one direction (the tap-band interaction).

One step is zoom_view_t::dst minus a one-eighth overlap on the moving axis, so consecutive taps keep a strip of the previous view on screen and the reader never loses their place. The overlap is a fraction of the viewport rather than a constant so it scales with the lens size in loupe mode.

Parameters
[in,out]vOpen view.
[in]dirDirection; k_zoom_pan_none is a no-op.
[in]now_msCurrent millisecond timestamp for the settle timer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe step was applied (possibly clamped to nothing).
k_ra8_err_null_ptrv is NULL.
k_ra8_err_invalid_statev is not open.
Precondition
v was opened by zoom_view_open.
dir is a zoom_pan_t enumerator.
Postcondition
The anchor satisfies the clamp invariant of zoom_view_t.
k_zoom_pan_none leaves the view byte-identical.
Note
Not thread-safe.
See also
zoom_view_pan
Since
0.1.0

Definition at line 614 of file zoom.c.

References zoom_view_t::active, zoom_view_t::dst, ra8_ui_rect_t::h, k_ra8_err_invalid_state, k_zoom_overlap_div, k_zoom_pan_down, k_zoom_pan_left, k_zoom_pan_none, k_zoom_pan_right, k_zoom_pan_up, RA8_CHECK_NULL_PTR, ra8_log_error, s_tag, ra8_ui_rect_t::w, and zoom_view_pan().

Referenced by ez_apply_pan(), and ez_selftest_pan().

◆ zoom_view_present()

ra8_err_t zoom_view_present ( zoom_view_t * v,
zoom_present_t * out )
nodiscard

Take the pending flush plan, clearing it.

The consume half of the dirty-tracking pair: it reports what the panel owes and immediately marks it paid, so a caller that ignores the plan simply drops a frame rather than flushing the same rectangle forever. Call it after zoom_view_render, not before.

Parameters
[in,out]vOpen view.
[out]outReceives the plan; out->present is false when none is owed.
Returns
ra8_err_t Error code.
Return values
k_ra8_okout holds the plan (possibly present == false).
k_ra8_err_null_ptrv or out is NULL.
k_ra8_err_invalid_statev is not open.
Precondition
v was opened by zoom_view_open.
The framebuffer already holds the pixels this plan describes.
Postcondition
v->pending is false.
A second immediate call reports present == false.
Note
Not thread-safe.
See also
zoom_view_render
Since
0.1.0

Definition at line 684 of file zoom.c.

References zoom_view_t::active, zoom_view_t::dst, k_ra8_err_invalid_state, k_ra8_ok, zoom_view_t::pending, zoom_view_t::pending_kind, zoom_present_t::present, RA8_CHECK_NULL_PTR, ra8_log_error, zoom_present_t::rect, zoom_present_t::refresh, and s_tag.

Referenced by ez_scene_present().

◆ zoom_view_rebind()

ra8_err_t zoom_view_rebind ( zoom_view_t * v,
const zoom_source_t * src,
uint32_t now_ms )
nodiscard

Point an open view at a different source, keeping zoom and position.

The page-turn path, and a deliberate product choice: turning the page while magnified keeps the magnification and the viewport anchor rather than dropping back to the whole page. A reader who zoomed in because the text is small wants the next page at the same magnification; the alternative (a turn exits zoom) is strictly less capable and is recoverable by calling zoom_view_close instead. The anchor is re-clamped to the new extent, so a shorter page simply pins to its bottom edge rather than showing past it.

Parameters
[in,out]vOpen view.
[in]srcNew source (validated as at open).
[in]now_msCurrent millisecond timestamp for the settle timer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe view now reads src.
k_ra8_err_null_ptrv or src is NULL, or src->read is NULL.
k_ra8_err_invalid_argsrc has a zero or oversized extent.
k_ra8_err_invalid_statev is not open.
Precondition
v was opened by zoom_view_open.
src outlives the view.
Postcondition
On k_ra8_ok v->scale is unchanged and the anchor is clamped to src.
On k_ra8_ok a flush is owed (v->pending is true).
Note
Not thread-safe.
See also
zoom_view_open
Since
0.1.0

Definition at line 495 of file zoom.c.

References zoom_view_t::active, zoom_source_t::height, internal_dim_ok(), internal_reclamp(), internal_touch(), k_ra8_err_invalid_arg, k_ra8_err_invalid_state, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_log_error, zoom_source_t::read, s_tag, zoom_view_t::src, and zoom_source_t::width.

◆ zoom_view_render()

ra8_err_t zoom_view_render ( zoom_view_t * v)
nodiscard

Composite the visible window into the bound ra8_gfx framebuffer.

The render pipeline, one horizontal strip at a time (see the file docblock for why strips): magnify the source into the gray8 strip by nearest-neighbour replication, blue-noise dither the strip at magnified-plane phase so the grain belongs to the image rather than the panel, pack it to 4 bpp, and blit it. Every destination pixel of zoom_view_t::dst is written – image where the source covers it, k_zoom_bg_gray in the letterbox – so the caller never has to pre-clear the viewport.

The whole path is integer arithmetic over the source bytes and a const mask, so the output is byte-identical on the unit-test host, in ra8_emulator and on silicon. That is what makes a framebuffer hash a usable golden.

Parameters
[in,out]vOpen view.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe viewport was painted.
k_ra8_err_null_ptrv is NULL.
k_ra8_err_invalid_statev is not open.
k_ra8_err_*Propagated verbatim from the source reader or ra8_gfx.
Precondition
ra8_gfx_init has bound a framebuffer.
v was opened by zoom_view_open.
Postcondition
On k_ra8_ok every pixel of v->dst inside the framebuffer has been written.
The ra8_gfx clip rectangle is left exactly as the caller set it.
Note
Not thread-safe; writes through the single ra8_gfx framebuffer binding. Honours the caller's clip, so a lens can be masked to a rounded box.
Example:
if (zoom_view_render(&view) != k_ra8_ok) { er_show_error(); }
See also
zoom_view_present
Since
0.1.0

Definition at line 339 of file zoom_render.c.

References zoom_view_t::anchor_x, zoom_view_t::dst, ra8_ui_rect_t::h, internal_emit_strip(), internal_render_ready(), k_ra8_ok, priv_zoom_axis(), RA8_RETURN_ON_ERROR, s_tag, zoom_view_t::scale, zoom_view_t::src, zoom_view_t::strip_rows, ra8_ui_rect_t::w, and zoom_source_t::width.

Referenced by ez_scene_render().

◆ zoom_view_set_scale()

ra8_err_t zoom_view_set_scale ( zoom_view_t * v,
uint8_t scale,
int32_t focus_x,
int32_t focus_y,
uint32_t now_ms )
nodiscard

Set the magnification, keeping a panel point fixed under the finger.

Tap-to-zoom, literally: the plane point currently displayed at (focus_x, focus_y) – framebuffer coordinates, i.e. where the user tapped – is the point that stays put. The plane coordinate is rescaled by new_scale / old_scale and the anchor is recomputed so that the same image feature lands under the same pixel, then clamped. A focus outside the viewport still works and simply pulls that off-screen feature toward the tapped edge.

Setting the scale it already has is a no-op and owes no flush, so a reader can call this unconditionally from a tap handler.

Parameters
[in,out]vOpen view.
[in]scaleRequested magnification, [k_zoom_scale_min, v->scale_max].
[in]focus_xFramebuffer column to keep fixed.
[in]focus_yFramebuffer row to keep fixed.
[in]now_msCurrent millisecond timestamp for the settle timer.
Returns
ra8_err_t Error code.
Return values
k_ra8_okThe scale is now scale (or already was).
k_ra8_err_null_ptrv is NULL.
k_ra8_err_invalid_statev is not open.
k_ra8_err_out_of_rangescale is outside the ladder.
Precondition
v was opened by zoom_view_open.
scale is on the integer ladder (fractional zoom is not representable).
Postcondition
On k_ra8_ok v->scale == scale and the anchor is clamped to the new plane.
On k_ra8_ok with a changed scale, a flush is owed.
Note
Not thread-safe.
Example:
const uint8_t next = zoom_scale_cycle(view.scale, k_zoom_scale_min, view.scale_max);
(void)zoom_view_set_scale(&view, next, tap_x, tap_y, ra8_time_ms());
uint8_t zoom_scale_cycle(uint8_t scale, uint8_t min, uint8_t max)
Next magnification on the doubling ladder, wrapping back to the minimum.
Definition zoom.c:539
ra8_err_t zoom_view_set_scale(zoom_view_t *v, uint8_t scale, int32_t focus_x, int32_t focus_y, uint32_t now_ms)
Set the magnification, keeping a panel point fixed under the finger.
Definition zoom.c:556
See also
zoom_scale_cycle
Since
0.1.0

Definition at line 556 of file zoom.c.

References zoom_view_t::active, zoom_view_t::anchor_x, zoom_view_t::anchor_y, zoom_view_t::dst, internal_reclamp(), internal_touch(), k_ra8_err_invalid_state, k_ra8_err_out_of_range, k_ra8_ok, k_zoom_scale_min, RA8_CHECK_NULL_PTR, ra8_log_error, s_tag, zoom_view_t::scale, zoom_view_t::scale_max, ra8_ui_rect_t::x, and ra8_ui_rect_t::y.

Referenced by ez_scene_tap(), and ez_selftest_zoom().

◆ zoom_view_tick()

bool zoom_view_tick ( zoom_view_t * v,
uint32_t now_ms )

Advance the settle timer; promote a bi-level view to full quality.

Call once per main-loop iteration. Under k_zoom_policy_responsive an interactive change leaves the view armed; once zoom_view_t::settle_ms of input silence have passed this arms a k_zoom_refresh_quality repaint of the same rectangle and disarms itself, so the reader repaints once and not per tick. The elapsed-time comparison is unsigned, so a millisecond counter that wraps produces one early settle rather than a hang.

Parameters
[in,out]vView (NULL is accepted and answers false).
[in]now_msCurrent millisecond timestamp.
Returns
Whether a quality repaint just became due.
Return values
trueThe caller should re-render and present.
falseNothing to do this tick.
Precondition
None – NULL, closed and disarmed views all answer false.
now_ms comes from a monotonic millisecond source.
Postcondition
On true, v->pending is true and v->settle_armed is false.
On false, v is unmodified.
Note
Not thread-safe.
See also
zoom_view_present
Since
0.1.0

Definition at line 664 of file zoom.c.

References zoom_view_t::active, k_zoom_refresh_quality, zoom_view_t::last_input_ms, zoom_view_t::pending, zoom_view_t::pending_kind, zoom_view_t::settle_armed, and zoom_view_t::settle_ms.

Referenced by ez_scene_tick().

◆ zoom_view_window()

ra8_err_t zoom_view_window ( const zoom_view_t * v,
ra8_ui_rect_t * out )
nodiscard

The source rectangle currently visible, in source pixels.

The residency question in one call: these are exactly the pixels the next zoom_view_render will read, so a tiled adapter can turn it into a tile rectangle and prove that nothing outside it is pinned. The rectangle is clipped to the image, so a letterboxed view reports the image's own extent rather than the viewport's.

Parameters
[in]vOpen view.
[out]outReceives the visible source rectangle.
Returns
ra8_err_t Error code.
Return values
k_ra8_okout holds the visible source rectangle.
k_ra8_err_null_ptrv or out is NULL.
k_ra8_err_invalid_statev is not open.
Precondition
v was opened by zoom_view_open.
out addresses writable storage for one ra8_ui_rect_t.
Postcondition
out->x + out->w <= (int32_t)v->src.width and likewise for y/h.
out->w >= 1 and out->h >= 1 (a view always shows at least one pixel).
Note
Thread-safe with respect to this module: reads only.
See also
zoom_tiles_prefetch
Since
0.1.0

Definition at line 645 of file zoom.c.

References zoom_view_t::active, zoom_view_t::anchor_x, zoom_view_t::anchor_y, zoom_axis_t::count, zoom_view_t::dst, ra8_ui_rect_t::h, zoom_source_t::height, k_ra8_err_invalid_state, k_ra8_ok, priv_zoom_axis(), RA8_CHECK_NULL_PTR, ra8_log_error, zoom_axis_t::s0, s_tag, zoom_view_t::scale, zoom_view_t::src, ra8_ui_rect_t::w, zoom_source_t::width, ra8_ui_rect_t::x, and ra8_ui_rect_t::y.

Referenced by ez_apply_pan().