Skip to content

colorcalibration also for manual wb - #20304

Open
kofa73 wants to merge 41 commits into
darktable-org:masterfrom
kofa73:colorcalibration-also-for-manual-wb
Open

kofa73 wants to merge 41 commits into
darktable-org:masterfrom
kofa73:colorcalibration-also-for-manual-wb

Conversation

@kofa73

@kofa73 kofa73 commented Feb 8, 2026 •

Copy link
Copy Markdown
Collaborator

Addresses #19873

What this would buy us:

  • technical
    • when the dual white balance + color calibration method was introduced, white balance used the 'camera reference' (D65) multipliers. Colours, including neutral areas, were fixed in color calibration, via proper CAT. The drawback was that some algorithms are hurt by the not-quite-accurate white balance.
      Quoting 1c5df70, which introduced as-shot to reference - the excerpt below is about the use of reference (D65) multipliers:

      There is one drawback with this approach. Some modules in the pipe would like to have "perfect white balance" correction - the rgb channels all have the same value for any greytone.

      Examples:

      1. Some hightlights reconstruction algos take the other channels or surrounding data into account and modify data "towards white".
      2. raw chromatic aberration correction also iterates from channel differences. Good "white is white" coeffs help significantly here.
      3. Some demosaicers also have slightly improved output on pixelpeeping level.
    • we now take this further, allowing the user to choose RGB multipliers via any method (sliders, colour picker, temperature + tint) supported by the white balance module, to provide neutrals that are even better than the camera's estimate
    • finally, we allow color calibration to use those user-provided multipliers ('as set in white balance module', in addition to 'as shot in camera' introduced in 1c5df70).
  • usability
    • these changes allow users to work with controls they are familiar with from other software, like 'temperature' and 'tint', and setting white balance in white balance, a more discoverable place for newcomers.

Changes:

  • added a new checkbox, prepare data for color calibration to white balance temperature.c to explicitly control the late_correction flag in 'user' / 'manual' modes
    • the checkbox is only visible in such modes (late_correction is always off in 'camera reference' mode, always on in 'as-shot to reference')
    • if switching from 'camera reference' or 'as-shot to reference' to a 'user' mode, the checkbox is automatically ticked if color calibration is enabled
  • replaced reference chr->as_shot with chr->wb_coeffs in highlights.c, colorin.c, opposed.c and segbased.c
  • illuminants.h:
    • added a new illuminant, DT_ILLUMINANT_FROM_WB, plus handling in conditionals, similar to DT_ILLUMINANT_CAMERA, but relying on dev->chroma->wb_coeffs
    • find_temperature_from_raw_coeffs:
      • renamed to find_temperature_from_as_shot_coeffs
      • extracted the logic to find_temperature_from_wb_coeffs, to allow directly using coefficients from temperature.c, instead of always going through ratios relative to the D65 values
      • added param wb_coeffs to illuminant_to_xy for absolute coefficients from temperature.c; renamed param custom_wb to correction_ratios to express what it represents; passed to find_temperature_from_wb_coeffs in DT_ILLUMINANT_FROM_WB mode
  • channelmixerrgb.c:
    • _dev_is_D65_chroma no longer needs as_shot multipliers in late_correction mode
    • renamed _get_white_balance_coeff to _get_d65_correction_ratios
    • wired up the calls to find_temperature_from_wb_coeffs to handle DT_ILLUMINANT_FROM_WB

Draft for now, to allow discussion (and I haven't tested much, but wanted to make the changes visible to others; plus, I need to clean up some temp files)

Open issues

These were found by the bots, I'll go through them, will take time.

Major

  • src/iop/temperature.c:1565 — In the default scene-referred workflow, reload_defaults() leaves late_correction = TRUE for images without camera-matrix support (ordinary non-raw and HDR RGB input), so for a non-Lab input profile colorin's D65 correction (D65coeffs = {1,1,1,1}) exactly cancels manually enabled and adjusted white balance; monochrome raws are not affected because hide_enable_button forces piece->enabled = FALSE and commit_params returns before setting chr->late_correction.
  • src/iop/temperature.c:1203 — _update_preset() uses chr->adaptation != NULL as a proxy for "color calibration is doing CAT", but channelmixerrgb registers itself unconditionally in reload_defaults, so leaving the D65 preset always switches late_correction on and silently replaces the user's white balance with the camera reference when color calibration is disabled.
  • src/iop/colorin.c:1201 — The new arbitrary-WB correction paths read session-global WB state instead of the current pipe's coefficients, so concurrent pipe generations can render and cache colors using mismatched white balance.
  • src/iop/hlreconstruct/opposed.c:236 — The shared-state defect recorded above at
    src/iop/colorin.c:1201 also exists in three highlight-reconstruction paths that this branch
    switched from the per-image constant chr->as_shot to the live chr->wb_coeffs; in
    opposed.c the value derived from the racy read is additionally stored in a process-wide
    static chrominance cache under a key built from the pipe's committed parameters, so one
    mixed-generation run keeps being served until those parameters change.
  • src/iop/temperature.c:1223 — _update_preset() passes the DtBauhausWidget toggle g->check_late_correction to the GtkToggleButton API, an invalid GObject cast, so every call logs GLib/GTK criticals and the "prepare data for color calibration" checkbox keeps a stale state on preset transitions that change late_correction, until the next dt_iop_gui_update().

Minor

  • dev-doc updates needed

To be investigated further

  • src/iop/channelmixerrgb.c:593 — This branch redefined _dev_is_D65_chroma() to return TRUE for
    any late_correction state, where the base version additionally required
    wb_coeffs == as_shot. As a result _get_d65_correction_ratios() now yields all-1.0 ratios in
    exactly the state this branch exists to enable — a manual white balance with late_correction
    on — so the "as set in camera" illuminant is derived from the unmodified EXIF as-shot
    coefficients in process(), process_cl(), reload_defaults() and the GUI, and the
    wb_applied_twice warning can no longer fire. Whether 1.0 is the correct new value depends on
    what colorin normalises the data to -> not a bug: with late correction on, "as shot" and a manual WB render the same outside clipped areas (dE2000 mean 0.15); the "white balance applied twice" warning appears only with late correction off and WB not at camera reference

@ralfbrown

Copy link
Copy Markdown
Collaborator

Did you intend to include cmixer_orig.c.txt in the PR?
And are the two .md files in the right place?

@kofa73

kofa73 commented Feb 9, 2026

Copy link
Copy Markdown
Collaborator Author

Did you intend to include cmixer_orig.c.txt in the PR? And are the two .md files in the right place?

See the end of the description:

Draft for now, to allow discussion (and I haven't tested much, but wanted to make the changes visible to others; plus, I need to clean up some temp files)

@TurboGit
TurboGit marked this pull request as draft February 9, 2026 16:07
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 6631dc3 to be0ad7a Compare February 9, 2026 17:41
@kofa73

kofa73 commented Feb 9, 2026

Copy link
Copy Markdown
Collaborator Author

@ralfbrown : extra files removed, thank for your patience

@kofa73
kofa73 marked this pull request as ready for review February 13, 2026 19:33
@kofa73 kofa73 changed the title Draft: colorcalibration also for manual wb colorcalibration also for manual wb Feb 13, 2026
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch 2 times, most recently from ce74cf6 to daae18d Compare February 14, 2026 13:34
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from daae18d to 5474b61 Compare February 21, 2026 10:32
@jenshannoschwalm

Copy link
Copy Markdown
Collaborator

A first complete code reading and remembering your comments before; generally i think this is a minor but very good improvement and will certainly help with some issues. Also it make the intention more clear and fixes some common misunderstandings.

I spotted some places like this:

dt_aligned_pixel_t correction;
  for_four_channels(k)
  {
    if(late && chr->wb_coeffs[k] > 1e-6f)
      correction[k] = chr->D65coeffs[k] / chr->wb_coeffs[k];
    else
      correction[k] = 1.0f;
  }

Could you explain if/why this is nevessary or is it just code-style?

@kofa73

kofa73 commented Feb 28, 2026

Copy link
Copy Markdown
Collaborator Author

The key is the line:

correction[k] = chr->D65coeffs[k] / chr->wb_coeffs[k];

This used to be something like (below is one concrete example):

chr->D65coeffs[0] / chr->as_shot[0]

The new denominator comes from chr->wb_coeffs[k] (value set in temperature.c), which means it may be set using one of the sliders. The sliders may have a value of 0, so we must protect the division:

typedef struct dt_iop_temperature_params_t
{
  float red;     // $MIN: 0.0 $MAX: 8.0
  float green;   // $MIN: 0.0 $MAX: 8.0
  float blue;    // $MIN: 0.0 $MAX: 8.0
  float various; // $MIN: 0.0 $MAX: 8.0
  int preset;
} dt_iop_temperature_params_t;

One option would be to restrict the values to MIN: 0.1 or so, then we can avoid adding the protection everywhere.

If I misunderstood the question, please let me know.

@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 5474b61 to 6d612e0 Compare February 28, 2026 05:44
Comment thread src/common/illuminants.h Outdated
Comment thread src/common/illuminants.h
Comment thread src/iop/channelmixerrgb.c Outdated
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from fa618ac to 6156826 Compare February 28, 2026 13:26
Comment thread src/common/illuminants.h Outdated
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 152cb16 to b529102 Compare March 6, 2026 18:23
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from b529102 to 994a97c Compare March 28, 2026 08:46
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 994a97c to 5217b8c Compare April 4, 2026 17:13
Comment thread src/iop/hlreconstruct/opposed.c
Comment thread src/iop/hlreconstruct/opposed.c
Comment thread src/iop/hlreconstruct/segbased.c
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 217d52f to 3e8cb65 Compare April 7, 2026 16:56
@kofa73

kofa73 commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator Author

Any updates? There have been no new commits for a while, just resolving conflicts.

@TurboGit TurboGit added this to the 5.6 milestone Apr 18, 2026
@TurboGit

Copy link
Copy Markdown
Member

@kofa73 : This gets under the radar and I had many things to do for the AI integration. The discussion on pixl.us was difficult and my motivation was quite down for some time... I'm still a simple human :) I'll get back to this for 5.6.

@TurboGit TurboGit added the feature: enhancement current features to improve label Apr 18, 2026
kofa73 added 19 commits October 28, 2026 18:05
…h left-over gui->reset to DT_ENTER|LEAVE_GUI_UPDATE
…rity), no need to allocate + copy xy in illuminants.h find_illuminant_xy_from_wb_coeffs; no need for 0 check after isnormal
… fields are the multipliers, aliased and set to 1 via dcoeffs, which matches the values used in the daylights declaration -> no-op; we won't crash
…abled; the previous "(cat != NULL) && cat->enabled" check did not know anything about what channelmixerrgb.c was doing.
- added a white balance GUI proxy, so color calibration's GUI gets
  the white balance from temperature's current params. Before, it
  read dev->chroma.wb, which holds stale values until the next commit
  and which a pipe thread may rewrite during the read
- _update_preset() no longer writes dev->chroma.wb.late_correction
- the white balance and color calibration dev-doc page and
  Module_Lifecycle.md now describe the proxy
In "as set in white balance module" mode, a failed lookup (for example
a coefficient of 0) kept the illuminant stored at commit time, which
depends on history order. Identical settings could render differently.

Now every consumer uses D50 through _illuminant_xy_from_wb_or_D50().
illuminant_to_xy() loses the wb_coeffs parameter added on this branch.
Details are in dev-doc/iop/wb_and_colorcalibration/README.md.
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 499cc94 to 07ea771 Compare October 28, 2026 17:06
The camera illuminant's CCT and swatch stayed stale after WB edits.
This bug predates the white balance and color calibration rework.
…er applied late correction even with white balance turned off
reload_defaults() sets the late_correction default per image, but the
toggle's reset value was only set once, in gui_init(). After switching
images in darkroom, double-clicking "prepare data for color calibration"
restored the previous image's default.
…iers

Extreme WB multipliers may result in zero or negative x, y, z
or CAT16 cone response. find_illuminant_xy_from_wb_coeffs() now
treats such an illuminant as a failed lookup
Since "color calibration: reject illuminants derived from extreme WB
multipliers", the "as shot in camera" illuminant lookup fails for an
extreme manual white balance with late correction off. process() and
process_cl() repeated the lookup with the pipe's white balance, but
replaced the illuminant only when the lookup succeeded. After a failed
lookup, the piece kept the illuminant of the last white balance that
worked. That illuminant is not saved: the params hold the one computed
when "as shot in camera" was selected, and an export or a reopened
edit uses that, so the darkroom could show a different result.

This commit extracts the block that process() and process_cl()
duplicated, which derives the illuminant from the white balance, into
_get_pipe_illuminant(). The helper writes a local variable instead of
the piece data. The fix is in its else branch: after a failed lookup,
it uses the illuminant from the saved x and y. commit_params() now
keeps those for "as shot in camera" instead of running its own lookup.
Edits on images where the lookup always fails, because they lack
usable as-shot multipliers or a color matrix (such as HDR DNGs
created by darktable before 3.6), render as before.
"camera reference" ignores late correction and hides the "prepare data
for color calibration" checkbox. gui_update() overwrote the saved mode
when the coefficients matched the as-shot or the reference ones. On a
raw without EXIF white balance, as shot equals the reference, so a
saved "camera reference" became "as shot" and the checkbox appeared.
A saved "camera reference" with pasted coefficients could become "user
modified", so its stored late_correction value took effect. Both
relabels reached the history with the next white balance edit.

gui_update() now keeps a saved "camera reference", even when its
coefficients differ from this image's reference. It infers "camera
reference" from the coefficients only for legacy params without a
saved mode, so a match no longer moves another saved mode into
"camera reference".
@kofa73
kofa73 force-pushed the colorcalibration-also-for-manual-wb branch from 07ea771 to afd950a Compare October 28, 2026 17:31
@kofa73

kofa73 commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Had the bots go through the remaining issues that were genuinely new on this branch; the remaining ones existed in some shape or form already on master. Since this is huge enough, I'd continue with them once (if) this gets merged. Thanks for your patience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: enhancement current features to improve priority: low core features work as expected, only secondary/optional features don't scope: codebase making darktable source code easier to manage scope: image processing correcting pixels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants