Skip to content

Palette cycles + gradient modes (wrap/mirror) - #41

Merged
tamirelazar merged 7 commits into
devfrom
feat/palette-cycles
Jun 18, 2026
Merged

Palette cycles + gradient modes (wrap/mirror)#41
tamirelazar merged 7 commits into
devfrom
feat/palette-cycles

Conversation

@tamirelazar

Copy link
Copy Markdown
Owner

Implements palette cycles (lever 6, #33): spatial palette repeat that remaps the gradient index pre-lookup so a palette tiles across the brightness range.

What

  • --palette-cycles <N> (1 = off, default) and --palette-cycle-mode <wrap|mirror> (mirror default).
    • wrap = sawtooth fract(t·n) (seam unless palette endpoints match)
    • mirror = triangle 1 − |1 − fract(t·n)·2| (ping-pong, seamless on any palette)
  • CLI flags + saved-config fields (serde(default) back-compat) + per-preset render defaults via the existing RenderArtDefaults payload.

Design

  • The remap is applied at the pinned pass-order step 5 — after the intensity tone curve, before the OKLch/256 gradient lookup — inside cycled cores of map_brightness_rgb/map_brightness. The public functions keep their signatures and delegate with an identity cycle, so the ~136 UI-swatch callers are untouched; only the render chokepoints (colorize_subpixel, the 256 branch, PNG export) consume the cycle.
  • Threaded parallel to intensity_mapping: RenderArtDefaultsRuntimeState/renderer → from_downsampled* → colorizer, and through PNG export and sync_renderer_caches (config-load).

Mechanism-only ship

Every preset defaults to identity (cycles = 1), so output is byte-identical and the 47 golden tests are unchanged. Tasteful per-preset cycle values are deferred to the showcase-presets issue (#36).

Test plan

  • New unit tests: cycle map (wrap/mirror/identity/clamp), cycled-core identity-matches-legacy, colorizer identity, from_downsampled cycle-changes-pixels, saved-config round-trip + missing-field back-compat, renderer cache-sync.
  • cargo clippy --all-targets --all-features -- -D warnings clean; 819 lib + 47 golden + 19 integration pass.
  • Same-seed empirical: default and --preset coral byte-identical vs dev; --palette-cycles 4 --palette-cycle-mode mirror visibly changes output.

Closes #33.

Add map_brightness_rgb_cycled / map_brightness_cycled that insert a
PaletteCycle remap at pass-order step 5 (between the tone curve and the
gradient lookup). The public map_brightness_rgb / map_brightness keep
their existing signatures and delegate with PaletteCycle::default() so
all ~136 existing callers stay byte-identical. colorize_subpixel gains a
trailing cycle: PaletteCycle param and routes the base color through the
cored fn. frame_buffer.rs and png.rs updated with PaletteCycle::default()
stubs (marked TODO(task4)) to keep the build green.
…faults

Adds palette_cycle field to RenderArtDefaults (identity default for all presets),
two new CLI flags (--palette-cycles N, --palette-cycle-mode MODE), a
palette_cycle_mode_parsed() accessor, and extends to_render_art_defaults to
override art.palette_cycle when either flag is explicitly set.
Add `palette_cycle: PaletteCycle` as a trailing param to `from_downsampled`,
`from_downsampled_at`, `create_cell`, `render_colored_cell`, `png_pixel_color`,
and `save_frame_as_png`. Replace all TODO(task4) stubs with the threaded value.

Wire `initial_palette_cycle` from `to_render_art_defaults()` into the renderer
via `set_palette_cycle` and into `RuntimeState.palette_cycle` at startup.
The 256-color branch now routes through `map_brightness_cycled` for consistency.

With the identity cycle (default), output is byte-identical; all 47 golden
regression tests pass unchanged.
@tamirelazar
tamirelazar merged commit 62cfec8 into dev Jun 18, 2026
5 of 6 checks passed
@tamirelazar
tamirelazar deleted the feat/palette-cycles branch June 18, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant