darktable-org / darktable-org/darktable

`agx`: "read exposure" leaves a stale `curve_gamma` in history

Open
#22,009 0 comments 0 reactions 1 assignee View on GitHub

@kofa73 is already working on this.

Since Aug 26, 2026.

Dominant language
C
Stars
13.1k
Forks
1.4k
Avg merge
22h 14m
Merged PRs (30d)
198

Description

Found by bots via code analysis, 2026-08-25.

Severity: latent. No wrong render while auto_gamma is on, because the pipe
recomputes the value. The stale parameter becomes live the moment the user turns
auto_gamma off, and the image then jumps.

Summary

agx's gui_changed() recomputes curve_gamma from the pivot whenever auto_gamma is
enabled — but the whole body is skipped during a GUI update, and
_read_exposure_params_callback() triggers exactly that path.

The chain:

  1. _read_exposure_params_callback() (src/iop/agx.c:1158-1167) changes
    p->range_black_relative_ev / p->range_white_relative_ev via
    _adjust_relative_exposure_from_exposure_params(), then calls dt_iop_gui_update(self)
    and immediately dt_dev_add_history_item().
  2. dt_iop_gui_update() wraps module->gui_update() in DT_ENTER_GUI_UPDATE()
    (src/develop/imageop.c:2442).
  3. agx's gui_update() ends with gui_changed(self, NULL, NULL) (:2259).
  4. gui_changed() guards its entire body with if(!DT_IN_GUI_UPDATE()) (:1762), so the
    block that recomputes the gamma is skipped:
    if(g && p->auto_gamma)                       // :1798-1804
    {
      tone_mapping_params_t tone_mapping_params;
      _set_log_mapping_params(self->params, &tone_mapping_params);
      _adjust_pivot(self->params, &tone_mapping_params);
      dt_bauhaus_slider_set(g->curve_gamma, tone_mapping_params.curve_gamma);
    }

History is then committed with the old p->curve_gamma.

Why it does not show up immediately

While auto_gamma is on, the pipe ignores p->curve_gamma_adjust_pivot()
(src/iop/agx.c:782-800) recomputes it:

  if(p->auto_gamma)
    tone_mapping_params->curve_gamma =
        pivot_x > 0.f && p->curve_pivot_y_linear_output > 0.f
        ? log2f(p->curve_pivot_y_linear_output) / log2f(tone_mapping_params->pivot_x)
        : p->curve_gamma;

so p->curve_gamma is only a fallback for a degenerate pivot. The slider is also hidden
in this state (gtk_widget_set_visible(g->curve_gamma, !p->auto_gamma), :1742).

The actual consequence

p->curve_gamma sits in history holding a value that no longer matches the pivot. When
the user disables auto_gamma, that stale value becomes the live one and the rendering
jumps discontinuously — with no edit that explains it.

Suggested fix

Recompute curve_gamma in the callback itself rather than relying on gui_changed()
running, or hoist the if(g && p->auto_gamma) block out of the !DT_IN_GUI_UPDATE()
guard. The latter needs care: the block calls dt_bauhaus_slider_set(), whose
value-changed handler is what writes p->curve_gamma, and that handler is itself
suppressed during a GUI update — so hoisting alone would move the slider without updating
the parameter. Writing p->curve_gamma directly and then setting the slider inside
DT_ENTER_GUI_UPDATE()/DT_LEAVE_GUI_UPDATE() is the clearer form.

Check the other callers of _update_pivot_x() for the same pattern.

Environment

Present in 56898c27b8. Static analysis only — nothing verified at runtime.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.