Skip to content

Levels: histogram editor with range handles - #37

Open
RafaelMendes1407 wants to merge 2 commits into
mainfrom
feat/levels-histogram
Open

RafaelMendes1407 wants to merge 2 commits into
mainfrom
feat/levels-histogram

Conversation

@RafaelMendes1407

Copy link
Copy Markdown

Summary

Selecting a Levels node now shows a visual editor above its sliders in the properties panel: a histogram of the node's input with the levels transfer curve on top, plus draggable handles for every parameter. It also fixes two long-standing problems with editing Levels. Clean-room implementation, independent of #36.

Documentation: docs/levels-editor.md.

The Levels editor: the input histogram, the transfer curve and the handles

What the editor shows

  • Input histogram of the image feeding the node:
    • 256 bins over 0..1.
    • One gray histogram for a grayscale input; red, green and blue overlaid for a color input.
    • Values outside 0..1 and NaN count in the end bins.
    • Bar heights scale to the tallest bin between pure black and pure white, because masks pile up at the ends and would flatten everything else.
    • Computed once per cooked result and reused until that result changes.
  • Clipped ranges shaded: the values outside the input range, which become the output low or high.
  • Transfer curve (accent line): the output value for every input value, so the effect of all five parameters is visible at once.
  • Handles:
    • in low, in mid and in high under the histogram.
    • out low and out high under a black-to-white bar.
    • Hovering a handle shows its parameter and value.

Fixes

  • Level in mid: the parameter is relative to the input range (0.5 = linear), but its only editor was an absolute 0..1 slider, so its position meant nothing next to the low and high values. Its handle now sits at in low + in mid × (in high − in low), moves with the range, and stays strictly inside it.
  • Input low and high could cross: dragging one past the other flipped the remap without warning. The input handles now keep at least 0.004 apart. The output handles may still cross, since inverting the output is legitimate.
  • NaN: levels_fn no longer passes a NaN on (from an input or a hostile parameter); it is treated as 0.
  • Existing files cook exactly as before. The sliders and graph.node.param still accept any values.

Behavior

  • Every drag runs graph.node.param, so a drag is one undo step per handle, and agents and scripts use the same command.
  • Parameters bound to a graph input or driven by a function keep their handles locked.
  • The sliders below the editor stay, for exact values and their right-click menu.

Changes

  • crates/ui-egui/src/levels_editor.rs (new):
    • histogram, values, mid_position, drag_value (the handle constraints).
    • The drawing and the handle interaction.
  • crates/ui-egui/src/properties.rs: shows the editor for levels nodes.
  • crates/nodes/src/atomic.rs: the NaN guard in levels_fn, plus unit tests.
  • crates/ui-egui/tests/snapshot.rs: a levels-editor scene.
  • Docs:
    • New docs/levels-editor.md.
    • Updates to architecture.md, target-app-parity.md and the README status.
    • The screenshot, with its ATTRIBUTION.md row. It is an offscreen render of MaterialCraft's own procedural content.

Tests

  • levels_editor::tests:
    • Histogram counts, end bins and NaN.
    • The handle constraints: no crossing, the relative mid, free output handles, NaN.
  • atomic::levels_tests:
    • Identity.
    • Stretch and clip.
    • The relative mid.
    • The output range and its inversion.
    • A collapsed range acting as a threshold.
    • NaN.
  • The snapshots UI test renders the screenshot above, checked visually.

Checks

  • cargo fmt --all
  • cargo test --release for nodes, engine (including panic_hunt) and ui-egui
  • cargo xtask layers
  • cargo clippy --workspace --all-targets -- -D warnings is clean for this change. On Rust 1.99 it still fails on main because of the new clippy::chunks_exact_to_as_chunks lint in crates/nodes/src/filters_extra.rs and text.rs, which this PR does not touch.
  • cargo xtask wasm: not run locally because the wasm32-unknown-unknown target is not installed; left to CI. The new code has no platform-specific parts.

Still open: per-channel levels (RGBA separately) and an optional output histogram.

🤖 Generated with Claude Code

RafaelMendes1407 and others added 2 commits November 3, 2026 15:03
Selecting a Levels node now shows a visual editor above its sliders: a
histogram of the node's input (256 bins; red, green and blue overlaid for a
color input; scaled to the tallest bin between pure black and white) with
the clipped ranges shaded and the levels transfer curve on top, draggable
input low/mid/high handles under it and output low/high handles under a
black-to-white bar.

Fixes in how Levels is edited: Level in mid is relative to the input range,
and its handle now sits where it acts and moves with the range; the input
low and high handles cannot cross. Parameters bound to inputs or driven by
functions keep their handles locked. Every drag is a graph.node.param
command, one undo step per handle. levels_fn no longer propagates NaN.

Tests cover the histogram, the handle constraints and the remap math; the
snapshots UI test renders the editor. Documented in docs/levels-editor.md.

Clean-room implementation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move the Levels screenshot's ATTRIBUTION row, the README mention, the
architecture note and the snapshot scene to places #36 does not touch, so
the two pull requests merge in either order without conflicts. No change
in behavior; the snapshot scene now runs last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@RafaelMendes1407

Copy link
Copy Markdown
Author

Moved this PR's edits to ATTRIBUTION.md, README.md, docs/architecture.md and the snapshot test away from the lines #36 changes. Both PRs now merge into main in either order with no conflicts (checked both orders locally), and the combined tree passes cargo fmt --check, the nodes/engine/render/ui-egui tests and clippy (apart from the existing chunks_exact_to_as_chunks lint on main).

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
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