April 15: Color Controls and Cleaner Signatures
Added saturation, vibrancy, and hue rotation controls to the composite pipeline — then immediately refactored compositeService to replace the 10+ positional parameters with an options object so adding the new controls wasn't a mess.
Developer Journal
Saturation, vibrancy, and hue rotation (#1203)
Three new global adjustments on the composite pipeline, applied in HSL space after color mapping:
- Saturation (
-1.0to1.0, default 0) — uniform multiply on S channel. Negative desaturates toward grayscale, positive pushes everything more vivid. - Vibrancy (
-1.0to1.0, default 0) — non-linear saturation that targets the less-saturated pixels more than the already-saturated ones. Useful when the image has a color-saturated galaxy core plus a muted background — vibrancy brings up the background without blowing out the core. Implemented ass += amount * (1 - s) * swhich peaks the adjustment arounds=0.5. - Hue rotation (
-180°to180°, default 0) — rotates the whole palette around the color wheel. Useful for tweaking hue relationships without rebuilding the palette from scratch.
All three are composable — applying saturation and then hue rotation produces the expected result. All three are optional and default to no-op so existing saved recipes keep their exact output.
Options objects replace positional params (#1205)
The compositeService functions had grown to 10+ positional parameters. Adding the three new controls from #1203 would have pushed several call sites to 15+ positional args, which is unreadable and guaranteed to produce argument-swap bugs in call sites.
Refactored to options objects:
// before
await runComposite(fits, wcs, channels, weights, stretch, black, white, gamma,
sharpness, feather, blend, instrumentBlend, saturation, vibrancy, hueRotation);
// after
await runComposite({ fits, wcs, channels, weights, stretch, black, white, gamma,
sharpness, feather, blend, instrumentBlend, saturation, vibrancy, hueRotation });
All call sites use named fields now, which makes arg-swap bugs impossible, makes the defaults explicit at the call site, and makes adding future fields non-breaking. TypeScript's partial-object pattern with required Picks for the mandatory fields gives the best of both worlds.
Also deleted a handful of utility functions that only existed to work around the positional-arg ordering. Net reduction: ~60 lines despite the interface changes.
What shipped
| PR | Title |
|---|---|
| #1205 | refactor: replace positional params with options objects in compositeService |
| #1203 | feat: add saturation, vibrancy, and hue rotation controls to composite pipeline |