Skip to content

Commit 06f0b0b

Browse files
committed
agx: Scale contrast according to gamma to maintain midtone contrast around the pivot. Fixed security_factor scaling bug introduced during percentage -> fractional conversion.
1 parent 85feb57 commit 06f0b0b

File tree

1 file changed

+28
-6
lines changed

1 file changed

+28
-6
lines changed

src/iop/agx.c

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,
7676
}
7777

7878
static const float _epsilon = 1E-6f;
79+
static const float _default_gamma = 2.2f;
7980

8081
typedef enum dt_iop_agx_base_primaries_t
8182
{
@@ -906,7 +907,28 @@ static tone_mapping_params_t _calculate_tone_mapping_params(const dt_iop_agx_par
906907
_adjust_pivot(p, &tone_mapping_params);
907908

908909
// avoid range altering slope - 16.5 EV is the default AgX range; keep the meaning of slope
909-
tone_mapping_params.slope = p->curve_contrast_around_pivot * (tone_mapping_params.range_in_ev / 16.5f);
910+
const float range_adjusted_slope = p->curve_contrast_around_pivot * (tone_mapping_params.range_in_ev / 16.5f);
911+
912+
// compensate contrast relative to gamma 2.2 to keep contrast around the pivot constant
913+
const float gamma = tone_mapping_params.curve_gamma;
914+
const float pivot_y = tone_mapping_params.pivot_y;
915+
916+
// We want to maintain the contrast after linearisation, so we need to apply
917+
// the chain rule (f(g(x)' = f'(g(x)) * g'(x))
918+
// to find the derivative of linearisation(curve(x)) = curve(x)^gamma.
919+
// By definition, the derivative of the curve g'(pivot_x)) = the slope;
920+
// also, curve(pivot_x) = pivot_y, so we need the derivative of the
921+
// power function at that point: f'(pivot_y).
922+
// We want to find gamma_compensated_slope to keep the overall derivative constant:
923+
// gamma_compensated_slope * [gamma * pivot_y^(current_gamma-1)] =
924+
// range_adjusted_slope * [_default_gamma * pivot_y^(_default_gamma-1)],
925+
// and thus gamma_compensated_slope = range_adjusted_slope *
926+
// [_default_gamma * pivot_y^(_default_gamma-1)] / [gamma * pivot_y^(current_gamma-1)]
927+
const float derivative_at_current_gamma = gamma * powf(fmaxf(_epsilon, pivot_y), gamma - 1.0f);
928+
const float derivative_at_default_gamma = _default_gamma * powf(fmaxf(_epsilon, pivot_y), _default_gamma - 1.0f);
929+
const float compensation_factor = derivative_at_current_gamma / derivative_at_default_gamma;
930+
931+
tone_mapping_params.slope = range_adjusted_slope / compensation_factor;
910932

911933
// toe
912934
tone_mapping_params.target_black =
@@ -1460,7 +1482,7 @@ static gboolean _agx_draw_curve(GtkWidget *widget, cairo_t *crf, const dt_iop_mo
14601482
if(ev % 5 == 0 || ev == ceilf(min_ev) || ev == floorf(max_ev))
14611483
{
14621484
cairo_save(cr);
1463-
cairo_identity_matrix(cr); // eeset transformations for text
1485+
cairo_identity_matrix(cr); // reset transformations for text
14641486
set_color(cr, darktable.bauhaus->graph_fg);
14651487
snprintf(text, sizeof(text), "%d", ev);
14661488
pango_layout_set_text(layout, text, -1);
@@ -1580,7 +1602,7 @@ void gui_changed(dt_iop_module_t *self, GtkWidget *widget, void *previous)
15801602
{
15811603
darktable.gui->reset++;
15821604
const float prev = *(float *)previous;
1583-
const float ratio = (p->security_factor - prev) / (prev + 100.f);
1605+
const float ratio = (p->security_factor - prev) / (prev + 1.f);
15841606

15851607
p->range_black_relative_exposure *= (1.f + ratio);
15861608
p->range_white_relative_exposure *= (1.f + ratio);
@@ -1808,15 +1830,15 @@ static GtkWidget* _create_advanced_box(dt_iop_module_t *self, dt_iop_agx_gui_dat
18081830
_("tries to make sure the curve always remains S-shaped,\n"
18091831
"given that contrast is high enough, so toe and shoulder\n"
18101832
"controls remain effective.\n"
1811-
"affects overall contrast, you may have to counteract it with the contrast slider."));
1833+
"affects overall contrast, you may have to counteract it with the contrast slider, or with toe / shoulder controls."));
18121834

18131835
slider = dt_bauhaus_slider_from_params(section, "curve_gamma");
18141836
g->curve_gamma = slider;
18151837
dt_bauhaus_slider_set_soft_range(slider, 1.f, 5.f);
18161838
gtk_widget_set_tooltip_text(slider,
18171839
_("shifts representation (but not output brightness) of pivot\n"
18181840
"along the y axis of the curve.\n"
1819-
"affects overall contrast, you may have to counteract it with the contrast slider."));
1841+
"affects overall contrast, you may have to counteract it with the contrast slider, or with toe / shoulder controls."));
18201842

18211843
self->widget = parent;
18221844

@@ -2246,7 +2268,7 @@ static void _set_neutral_params(dt_iop_agx_params_t *p)
22462268
p->curve_target_display_black_ratio = 0.f;
22472269
p->curve_target_display_white_ratio = 1.f;
22482270
p->auto_gamma = FALSE;
2249-
p->curve_gamma = 2.2f;
2271+
p->curve_gamma = _default_gamma;
22502272
p->curve_pivot_x_shift_ratio = 0.f;
22512273
p->curve_pivot_y_ratio = 0.18f;
22522274

0 commit comments

Comments
 (0)