Freehand transfer curves on a monotone spline, per channel — RGB, CIELAB (L*, a*, b*, Chroma) and Saturation — with a live before/after histogram, presets, and a mask-aware, instant-apply pipeline.
The grid is your transfer curve: the horizontal axis is input tone, the vertical axis is output. Each channel keeps its own curve, and the channels you aren't editing are drawn as dim overlays so you always see the whole transform.
in / out values right next to it.| Control | What it does |
|---|---|
| Channel | K (brightness / RGB linked), R/G/B, L*, a*, b*, Chroma, or Saturation — each with an independent curve; the inactive ones draw as dim overlays. |
| Presets | Built-in shapes (S-curves, lift/crush shadows, highlight roll-off, …) plus your own saved multi-channel presets. |
| Pop Out | Detach the editor into its own resizable window (position/size remembered). |
| Histogram | Before (dim) vs after (bright) for R/G/B and luminance, with log scaling and x-axis zoom/pan. |
| Toggle Preview / Apply / Reset | Show the result on the canvas, commit it to the document, or return every channel to the identity line. |
| Grip (bottom-left) | Drag to the canvas to mint a Curves shortcut with all channel curves baked in, or drop on an image to apply headlessly. |
A curve is a monotone spline through your control points, baked to a lookup table and applied either per RGB channel or per colour-space component. Everything runs on float data in [0,1].
The control points are interpolated with a PCHIP (Piecewise Cubic Hermite) spline — a shape-preserving monotone cubic. Unlike a natural cubic it never overshoots or rings between points, so the curve does exactly what the handles say and the transfer stays monotone (no tonal inversions).
The spline is sampled into a lookup table — 65 536 entries for the final apply (256 for the live preview) — and every pixel is mapped through it:
Because the LUT is monotone, the before→after histogram can be remapped exactly by resampling the input CDF onto the output axis — mass-preserving and gap-free, so the displayed “after” histogram can't show a false comb where the curve expands tones.
RGB. K applies one LUT to all three channels (linked brightness); R/G/B curve a single channel. When both are set, the per-channel curve is composed after K:
CIELAB (L*, a*, b*, Chroma). The image is converted sRGB→XYZ (D65) with
then XYZ→Lab about the D65 white point
(Xn, Yn, Zn) = (0.95047, 1, 1.08883):
The selected component is normalised to [0,1], curved, and de-normalised
(L*/100, (a*+128)/255, (b*+128)/255). For
Chroma the curve scales the colour's radius in the a*–b* plane while
keeping its hue:
Saturation curves the S channel of HSV and converts back.
When several channels are active they are applied in a fixed order so the result is deterministic: first the RGB stage (K composed into R/G/B), then the Lab stage (L*, a*, b*, Chroma via one round-trip), then Saturation (via HSV). Mono images support K (applied directly) and L* (through the Lab lightness nonlinearity); the colour-only channels are skipped.
Ctrl+clicking the grid (or double-clicking the image) sets a pivot
u and redistributes the interior handles evenly on either side of it
along the identity line — a quick way to seed a symmetric contrast curve
around a chosen tone.
With an active mask the result is blended back against the source over the mask:
kind="curves_multi" and carries
every channel's points, so a dropped shortcut reproduces the entire
multi-channel transform — not just the active curve.