Adjust HSL¶
Purpose and expected result¶
Adjust HSL rotates hue, scales saturation, and offsets HSL lightness for every pixel of an RGB image. Use it for a deliberate display-color adjustment after color calibration and the main stretch. The successful result replaces the target; Preview snapshots create separate stills.
This is a nonlinear, display-oriented operation. It works on current normalized stored RGB values and does not decode a camera profile or an ICC working space. Start on a duplicate, especially when color accuracy or metadata matters.
At a glance¶
| Item | Current behavior |
|---|---|
| Menu path | Process > Adjustments > Hue/Saturation/Lightness |
| Enabled when | A color image (red, green, and blue) or a sequence showing one is active; the menu action is disabled for grayscale. A launch is refused with a warning when this process is already attached to the image or an attached process holds exclusive access |
| Inputs | One RGB still, or a sequence whose decoded frames are RGB, in any integer or floating representation; optional enabled active mask. The whole image is processed; there is no channel selector |
| Outputs | Replaces the target at the same geometry, representation, and RGB format; a successful sequence Apply writes a temporary replacement sequence; Take snapshot creates an auxiliary still |
| Recommended image state | Nonlinear display data after color calibration and the main stretch; the process marks committed output nonlinear |
| Preview support | Asynchronous preview that runs the same adjustment as Apply over the target, or over the Preview area; fixed linear display, histogram, original/result switch, Preview area, and snapshot |
| Sequence support | Every decoded frame is processed as Apply processes a still image of it, into a temporary replacement sequence, with per-frame progress and Cancel; every decoded frame must be RGB unless configured automatic CFA debayer supplies RGB |
| Mask support | Active target mask post-blends source and transformed result for Preview, snapshot, still Apply, and sequence Apply; white applies, black protects, gray blends, and Invert Mask reverses weights |
| External dependencies | None; bundled AfterNight engine only |
Before you start¶
Complete background and color calibration, then perform the intended main stretch. HSL Lightness is the average of the largest and smallest stored RGB components, not perceived CIE lightness. Equal HSL Lightness values can look very different across hues.
Work on an RGB duplicate. Floating samples are read as they are and every
processed pixel is clamped to 0..1, so values above 1 or below 0 do not
survive. Preview area is a safe way to test a region: inside the area the
Preview is what Apply makes of that area.
Procedure¶
- Select a nonlinear RGB duplicate and open Process > Adjustments > Hue/Saturation/Lightness.
- Keep Preview on; enable Preview area to work on a region. Use the same viewer display mode while comparing the Preview with the original.
- Rotate Hue first, then adjust Saturation, and finish with small Lightness changes. The three controls are global and interact.
- Inspect neutrals, star colors, saturated regions, histogram endpoints, and any active-mask boundary.
- Select Apply. The Preview shows what Apply writes; recheck the committed RGB result, its history step, and its metadata.
Complete parameter and action reference¶
| UI label/action | Type and unit | Runtime default | Persisted-default exception | Valid values, step, or constraints | Effect of increasing, decreasing, or selecting | Dependencies and enable/visibility rules | Quality, artifact, runtime, memory, or disk trade-off | Scope: preview/apply, channel, mask, still/sequence | Persistence |
|---|---|---|---|---|---|---|---|---|---|
| Histogram plot | Graph, normalized intensity/count | Source or current Preview; RGB channels overlaid | None | Binning follows Plot resolution | Shows source or mask-aware Preview distribution and approximate endpoint-bin clipping | Show histogram controls visibility | Recalculation adds work; endpoint counts are bin-dependent | Display only | Recomputed after source, Preview, or resolution changes |
| Left-drag in histogram | Graph gesture | No readout | None | Hold left and move | Shows normalized coordinate readout | Histogram shown | Diagnostic only | Display only | Ends on release |
| Middle-drag in histogram | Graph gesture | Fitted/unpanned | None | Hold middle and drag | Pans the graph | Useful beyond viewport | No output effect | Display only | Until reset/window close |
| Ctrl+mouse wheel in histogram | Graph gesture | Fitted size | None | Up 1.20x; down 0.8x |
Zooms around cursor | Interactive graph enabled | Rendering only | Display only | Window lifetime |
| Zoom in | Toolbar action | Fitted size | None | Each action 1.30x |
Enlarges plot | Disabled with hidden histogram | Rendering only | Display only | Window lifetime |
| Zoom out | Toolbar action | Fitted size | None | Each action 0.70x |
Shrinks plot | Disabled with hidden histogram | Rendering only | Display only | Window lifetime |
| Zoom reset | Toolbar action | — | None | One action | Fits plot and resets scrolling | Disabled with hidden histogram | Rendering only | Display only | Immediate |
| Show grid | Toggle | Checked | None | Checked/unchecked | Shows or hides graph grid | Disabled with hidden histogram | Display-only aid | Display only | Window lifetime |
| Show clipping info | Toggle | Checked | None | Checked/unchecked | Shows or hides endpoint-bin counts | Disabled with hidden histogram | Binning-dependent estimate, not an exact float clipping test | Display only | Window lifetime |
| Absolute scales | Toggle | Unchecked | None | Checked/unchecked | Normalizes each displayed histogram channel to its own peak | Disabled with hidden histogram | Can conceal relative channel strength; no image normalization | Display only | Window lifetime |
| Draw histogram areas | Toggle | Unchecked | None | Checked/unchecked | Filled-area versus line graph | Disabled with hidden histogram | Overlap can obscure channels | Display only | Window lifetime |
| Show histogram | Toggle | Checked | None | Checked/unchecked | Hides/clears graph and toolbar or reloads it | Available while idle | Saves graph work, not Preview processing | Display only | Window lifetime |
| Plot resolution | Combo box, bins | 8-bit (256) | None | 6-bit (64) through 16-bit (64K), plus 100, 1000, 10000 levels | More bins refine graph and endpoint estimates | Disabled with hidden histogram | Higher bins cost graph memory/rendering; output precision unchanged | Display only | Window lifetime |
| Hue | Integer value plus slider, degrees | 0 |
None | -180..180, step 1 |
Adds Hue/360 turns and wraps around the HSL circle |
Idle; Apply disabled only when all three parameters are zero; locked during Apply | Large rotations remap every chromatic color; hue is powerless on neutral pixels | Preview/snapshot/Apply; all RGB pixels; mask post-blend; still/sequence | Captured per execution; Reset/success returns 0 |
| Lightness | Integer value plus slider, HSL percentage points | 0 |
None | -100..100, step 1 |
Adds Lightness/100 to HSL L, then clamps 0..1 |
Idle; locked during Apply | Positive values move pixels toward white; negative toward black; endpoint clipping loses detail | Preview/snapshot/Apply; all RGB pixels; mask post-blend; still/sequence | Captured per execution; Reset/success returns 0 |
| Saturation | Integer value plus slider, mapped strength | 0 |
None | -100..100, step 1; negative multiplier 1+S/100, positive multiplier 1+S/25 |
-100 removes saturation; +25 doubles it; +100 requests 5x; result clamps 0..1 |
Idle; locked during Apply | Positive side is four times steeper than a percentage interpretation and quickly clips saturation | Preview/snapshot/Apply; all RGB pixels; mask post-blend; still/sequence | Captured per execution; Reset/success returns 0 |
| Target Image | RGB image selector | Attached/active RGB target | None | Open color stills/sequences | Changes source, histogram, and Preview | Lists color views only; the window closes if its target stops holding a color image; locked during Apply | Wrong target is destructively replaced | Preview/Apply source and active mask | Target lifecycle; Reset does not change it |
| Preview | Checkbox | Checked | None | Checked/unchecked | Creates/destroys asynchronous fixed-linear Preview and switches histogram source/result | Remains interactive during Apply | Each preview run works on a copy of the target or of the Preview area, plus a floating working copy for integer images; a burst of changes collapses into one run with the latest values; Apply cancels a preview in progress | Preview only; current displayed frame | Window lifetime; Reset does not change it |
| Preview pane | Full-image viewer | Latest Preview result | None | Standard viewer zoom/pan/channel controls | Shows mask-aware result | Exists only with Preview | Shows the adjustment Apply writes, in the target's representation and RGB format | Preview only; target unchanged | Window lifetime |
| Switch image | Preview-toolbar action | Processed Preview shown | None | Click, Space, right-click, or Ctrl+Shift+O | Toggles result/original display | Exists only with Preview | Display comparison only | Preview display only | Preview-window lifetime |
| Preview area | Preview-toolbar checkbox | Unchecked | None | Checked/unchecked; area selected in the Preview pane | Runs the Preview over the selected area only and shows that result over the rest of the image as it was | Exists only with Preview; disabled during Apply | Faster regional test; inside the area the result is what Apply makes of an image of that area | Preview and snapshot path | Window lifetime |
| Take snapshot | Preview-toolbar action | No snapshot | None | Click or Ctrl+Shift+K | Runs the current settings again and opens <target> - Adjust Hue/Saturation/Lightness - SNAPSHOT; with Preview area on, the snapshot is the composite the Preview showed |
Exists only with Preview; disabled during Apply | Samples are what the Preview shows; the snapshot keeps the source's metadata and is not recorded as nonlinear | Auxiliary still; target unchanged; mask applies | Each action creates a view |
| Apply | Button/action | Disabled at all-zero identity | None | One action when any parameter is nonzero | Captures the three parameters and runs the adjustment on a worker over the still or every decoded sequence frame | Target lock required; during the run Target, the sliders, Apply, Reset, Close, and the Preview pane's snapshot and area actions are disabled whether or not Preview is on; the Preview checkbox stays available | RGB/HSL conversion in double precision per pixel and clamps; destructive result | Still/sequence replacement; active mask applies | Success records one history step and resets parameters |
| Reset | Button/action | Enabled while idle | None | One action | Returns Hue, Lightness, and Saturation to 0 |
Does not change Target, Preview, histogram, or toolbar | No image change | Configuration only | Immediate |
| Close | Button/action | Enabled while idle | None | One action | Closes without applying current settings | Disabled during Apply; closing the window while a run is in progress hides it at once and it closes when the run has landed | Nothing waits on the GUI thread; a sequence Apply in progress is cancelled | Window/execution | Parameters are not saved |
| Sequence progress / Cancel | Shared progress dialog and Cancel | Created for sequence Apply | None | Per-frame progress; Cancel | Writes processed frames to a temporary sequence and replaces target only after every frame succeeds | Sequence target; every decoded frame must be RGB | Decode/encode and temporary disk cost; Cancel is observed between frames and inside the engine operation | Sequence Apply; same parameters/mask for every frame | Cancel discards temporary result; success resets settings |
How the algorithm works¶
For normalized stored RGB values, AfterNight computes ordinary HSL. If
v=max(R,G,B), m=min(R,G,B), and d=v-m, then HSL lightness is
l=(v+m)/2; saturation is derived from d relative to l, and neutral pixels
have zero saturation and powerless hue.
For UI values H, S, and L, the current transform is
The adjusted triplet is converted back to RGB and each channel is clamped to
0..1. The HSL is that of the stored samples as they are: the samples are read
and written in the same encoding, so no transfer function is decoded or
re-encoded on the way. Each pixel is computed in double precision.
Samples are read in normalized units: an unsigned integer divided by its largest code, a signed integer counted from its smallest code to its largest, and a floating sample as it is. Integer images are converted into a Float32 working copy (Float64 for 32-bit integers) and the result is narrowed back to the original representation, rounding to the nearest code with ties to even. Floating images are worked on in place.
Pixels that the image's own validity marks as not valid are left as they were. On an ordinary image, whose samples are all valid, a pixel holding NaN or an infinity in any channel is also left as it was. Under any other validity rule, a NaN or an infinity in a pixel that rule calls valid makes the engine refuse the run: Apply publishes nothing and reports the error.
Preview runs this same adjustment over the target, or over the Preview area, on a worker. Preview, Preview area, and snapshot hold what Apply makes, in the target's representation and RGB format.
An active mask blends original and transformed samples after the adjustment. The engine operation runs on the run's threads and observes a stop request; a stopped run publishes nothing.
Parameter interactions and starting points¶
| Combination | Conservative starting point | Too weak looks like | Too strong looks like | Adjustment order |
|---|---|---|---|---|
| Hue + calibrated color | -5..5 degrees |
Color cast remains | Star/nebula colors move into unrelated hues | Rotate first, inspect known neutrals and stars |
| Saturation + stretched RGB | -10..10; remember +10 means 1.4x |
Color separation barely changes | Saturation clips, noise and chromatic halos intensify | Raise slowly, inspect channel endpoints |
| Lightness + stretch | -5..5 |
Tonal change is negligible | Shadows/whites clip globally | Prefer the main stretch for tone; use HSL L only as finishing |
| Active mask + color change | Verify mask before tuning | Intended region changes too little | Boundary becomes visible | Inspect mask, reduce adjustment, feather upstream |
| Preview area + regional test | Choose an area holding neutrals, stars, and saturated detail | The area misses the colors that matter | One area is taken to stand for the whole image | Tune on the area, then check the whole-image Preview before Apply |
Inputs, outputs, and metadata¶
- Apply retains dimensions, RGB channel count, representation, and image name.
It keeps every FITS card it does not own, the astrometric solution, and the
acquisition record, and records the result as nonlinear on the
processing-state cards:
ANLINnonlinear,ANASRCprocess,ANOPhsl-adjustment, andANBASISdisplay-rgb(amonochromebasis the source's cards recorded is kept). A linear encoding the source claimed is no longer claimed, and the image's lineage gains an entry namingan::adjustImageHslwith the three values. - Image History records one step named Adjust Hue/Saturation/Lightness,
described as
Adjust hue to 30 saturation to 40 and lightness to -10for those values; undo restores the previous image. The step does not record the active mask or whether a sequence auto-debayer supplied RGB. - Preview snapshots are separate stills holding what the Preview showed. They keep the source's metadata and are not recorded as nonlinear, so use Apply on a duplicate for an archival result.
- Sequence Apply processes every decoded frame as Apply processes a still image of it, into a temporary sequence, and replaces the target only after every frame succeeds. Automatic CFA debayer can alter the decoded frame format before this process.
- Nothing is stored between openings of the window.
Performance¶
Conversion and adjustment are linear in pixel count and run on the threads the run is granted. Preview and Apply each work on a copy of the target (a Preview area works on a copy of the area); an integer image adds a floating working copy, and Apply copies the result once more when it restates the processing-state cards. A mask adds another full-image blend; sequence Apply adds decode, encode, and temporary-disk work. A run the session's memory budget cannot hold is refused with the reason and publishes nothing. No production timing or peak memory was measured.
Worked example¶
- Evidence mode: Source/equation-derived; not executed on project-owned astronomical data.
- Input: normalized red
(1,0,0), whose HSL is(0°,1,0.5). - Hue
+120: wraps to120°and reconstructs green(0,1,0). - Saturation
-50: uses multiplier0.5, producing HSL saturation0.5. - Lightness
+25: adds0.25, producing HSL lightness0.75; with the original red hue/saturation this reconstructs approximately(1,0.5,0.5). - Inspect in real use: neutral objects, star colors, saturation clipping, histogram endpoints, mask boundaries, and final RGB metadata/state.
- Limitations: These checkpoints verify the current mappings, not astronomical color quality, a saved sequence, runtime, or memory use.
Warnings and limitations¶
- Positive Saturation is not a percentage:
+25means2xand+100means5xbefore clamping. Negative values run linearly to zero at-100. - HSL Lightness is not CIE L*, scene luminance, exposure, or a full stretch.
- HSL is not perceptually uniform; equal hue steps and equal lightness values do not imply equal visual differences.
- Input values are treated as current normalized stored RGB. No embedded ICC profile or transfer curve is consulted.
- Every processed pixel is clamped to
0..1; floating values outside that range do not survive Apply. - The process needs exactly red, green, and blue. A grayscale image or frame, or a color image that also carries an alpha channel, is refused at Apply with the reason, and nothing changes.
Troubleshooting¶
| Symptom or exact error | Verified cause/validation path | Recovery | Evidence source |
|---|---|---|---|
| Menu action is disabled or process closes | Target is not a color image | Convert/debayer intentionally, or use an RGB target | ActionStateCoordinator::setEnableImageColorActions; AdjustHSLProcess::isViewControlSupported |
| Apply is disabled | Hue, Saturation, and Lightness are all 0 |
Set any nonzero value | AdjustHSLProcess::settingsUpdatedImpl |
| Small positive Saturation looks unexpectedly strong | Positive mapping is 1+S/25 |
Use smaller positive values; +5 is already 1.2x |
AdjustHSLProcess::hslAdjustment |
| Hue rotation does nothing in gray areas | Zero-saturation HSL hue is powerless | This is expected; change saturation only if color is intended | an::adjustImageHsl (RGB-to-HSL conversion) |
Error message includes the HSL adjustment needs an image of red, green and blue |
The target, or a decoded sequence frame, is not three-channel RGB (grayscale, or color with alpha) | Supply RGB images or frames, or configure valid CFA auto-debayering; the target was not changed | AdjustHSLProcess::executeTypedProcessImpl |
| Output clips toward white or black | Additive Lightness clamps to 0..1; saturation and the RGB result also clamp |
Reduce Lightness/Saturation and start again from duplicate | an::adjustImageHsl |
| Pixels holding NaN are unchanged | Non-finite pixels are left as they were | Repair or replace them upstream if needed | process_utils::skippingNonFinite |
| Snapshot carries no nonlinear record | Snapshots are Preview results and are not recorded | Apply on a duplicate for an archival result | AdjustHSLProcess::executeTypedProcessImpl; process_utils::recordNonlinearResult |
| Error names the memory budget | The session cannot hold the run's copies of the image | Close other images or work on a smaller crop; nothing was changed | process_utils::openNormalizedDestination; process_utils::recordNonlinearResult |
| Sequence Cancel leaves the sequence as it was | Cancel stops the run between frames or inside the engine operation, and the target is replaced only when every frame succeeds | Expected; Apply again when ready | ImageRTPreviewControl::runOverSequence; an::adjustImageHsl |
Related workflow steps¶
Complete Photometric Color Calibration or Spectrophotometric Color Calibration before display-color finishing. Compare Adjust LCh when CIE L*, chroma, and hue are the desired coordinates. Use Color Saturation for hue-range-selective saturation, and use Histogram Transformation, Generalized Hyperbolic Transformation, or Exposure Correction for deliberate tone work rather than treating HSL Lightness as a substitute. Verify Image History and Image Statistics after Apply.
References and provenance¶
- W3C CSS Color 4: HSL colors and conversion describes the ordinary sRGB-oriented HSL model, circular hue, and its perceptual limitations. AfterNight's exact parameter mappings and Preview behavior come from the project source.
- AfterNight project source is authoritative for the asymmetric Saturation mapping, additive Lightness, representation/metadata/state behavior, masks, Preview and Preview-area behavior, and temporary sequence workflow.