From d478c7d00c5281e68a0b9d0135552f4484d5c24e Mon Sep 17 00:00:00 2001
From: Marc Fouquet
Date: Sun, 6 Apr 2025 10:21:46 +0200
Subject: [PATCH 1/6] 2025-04-06 preview version with post-shift, post_scale,
auto-align and curve coloring.
---
src/iop/toneequal.c | 2492 ++++++++++++++++++++++++++++---------------
1 file changed, 1654 insertions(+), 838 deletions(-)
diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c
index 3a89a3a32936..790d15b908a2 100644
--- a/src/iop/toneequal.c
+++ b/src/iop/toneequal.c
@@ -37,14 +37,14 @@
* perfect, and I'm still looking forward to a real spectral energy
* estimator. The best physically-accurate norm should be the
* euclidean norm, but the best looking is often the power norm, which
- * has no theoretical background. The geometric mean also display
+ * has no theoretical background. The geometric mean also display
* interesting properties as it interprets saturated colours as
* low-lights, allowing to lighten and desaturate them in a realistic
* way.
*
* The exposure correction is computed as a series of each octave's
* gain weighted by the gaussian of the radial distance between the
- * current pixel exposure and each octave's center. This allows for a
+ * current pixel exposure and each octave's center. This allows for a
* smooth and continuous infinite-order interpolation, preserving
* exposure gradients as best as possible. The radius of the kernel is
* user-defined and can be tweaked to get a smoother interpolation
@@ -69,11 +69,11 @@
* smooth contiguous region (quantization parameter), but also to
* translate (exposure boost) and dilate (contrast boost) the exposure
* histogram through the control octaves, to center it on the control
- * view and make maximum use of the available channels.
+ * view and make maximum use of the available NUM_SLIDERS.
*
* Users should be aware that not all the available octaves will be
* useful on every pictures. Some automatic options will help them to
- * optimize the luminance mask, performing histogram analyse, mapping
+ * optimize the luminance mask, performing histogram analys, mapping
* the average exposure to -4EV, and mapping the first and last
* deciles of the histogram on its average ± 4EV. These automatic
* helpers usually fail on X-Trans sensors, maybe because of bad
@@ -126,22 +126,35 @@
DT_MODULE_INTROSPECTION(2, dt_iop_toneequalizer_params_t)
+/****************************************************************************
+ *
+ * Definition of constants
+ *
+ ****************************************************************************/
-#define UI_SAMPLES 256 // 128 is a bit small for 4K resolution
+#define UI_HISTO_SAMPLES 256 // 128 is a bit small for 4K resolution
+#define HIRES_HISTO_SAMPLES UI_HISTO_SAMPLES * 3 * 16
#define CONTRAST_FULCRUM exp2f(-4.0f)
#define MIN_FLOAT exp2f(-16.0f)
+#define DT_TONEEQ_MIN_EV (-8.0f)
+#define DT_TONEEQ_MAX_EV (0.0f)
+#define DT_TONEEQ_USE_LUT TRUE
+
+#define HIRES_HISTO_MIN_EV -16.0f
+#define HIRES_HISTO_MAX_EV 8.0f
+
/**
* Build the exposures octaves :
* band-pass filters with gaussian windows spaced by 1 EV
**/
-#define CHANNELS 9
-#define PIXEL_CHAN 8
+#define NUM_SLIDERS 9
+#define NUM_OCTAVES 8
#define LUT_RESOLUTION 10000
// radial distances used for pixel ops
-static const float centers_ops[PIXEL_CHAN] DT_ALIGNED_ARRAY =
+static const float centers_ops[NUM_OCTAVES] DT_ALIGNED_ARRAY =
{-56.0f / 7.0f, // = -8.0f
-48.0f / 7.0f,
-40.0f / 7.0f,
@@ -149,12 +162,24 @@ static const float centers_ops[PIXEL_CHAN] DT_ALIGNED_ARRAY =
-24.0f / 7.0f,
-16.0f / 7.0f,
-8.0f / 7.0f,
- 0.0f / 7.0f}; // split 8 EV into 7 evenly-spaced channels
+ 0.0f / 7.0f}; // split 8 EV into 7 evenly-spaced NUM_SLIDERS
-static const float centers_params[CHANNELS] DT_ALIGNED_ARRAY =
+static const float centers_params[NUM_SLIDERS] DT_ALIGNED_ARRAY =
{ -8.0f, -7.0f, -6.0f, -5.0f,
-4.0f, -3.0f, -2.0f, -1.0f, 0.0f};
+// gaussian-ish kernel - sum is == 1.0f so we don't care much about actual coeffs
+static const dt_colormatrix_t gauss_kernel =
+ { { 0.076555024f, 0.124401914f, 0.076555024f },
+ { 0.124401914f, 0.196172249f, 0.124401914f },
+ { 0.076555024f, 0.124401914f, 0.076555024f } };
+
+
+/****************************************************************************
+ *
+ * Types
+ *
+ ****************************************************************************/
typedef enum dt_iop_toneequalizer_filter_t
{
@@ -165,40 +190,53 @@ typedef enum dt_iop_toneequalizer_filter_t
DT_TONEEQ_EIGF // $DESCRIPTION: "EIGF"
} dt_iop_toneequalizer_filter_t;
+typedef enum dt_iop_toneequalizer_post_auto_align_t
+{
+ DT_TONEEQ_ALIGN_CUSTOM = 0, // $DESCRIPTION: "custom"
+ DT_TONEEQ_ALIGN_LEFT, // $DESCRIPTION: "auto-align at shadows"
+ DT_TONEEQ_ALIGN_CENTER, // $DESCRIPTION: "auto-align at mid-tones"
+ DT_TONEEQ_ALIGN_RIGHT, // $DESCRIPTION: "auto-align at highlights"
+ DT_TONEEQ_ALIGN_FIT, // $DESCRIPTION: "fully fit"
+
+} dt_iop_toneequalizer_post_auto_align_t;
typedef struct dt_iop_toneequalizer_params_t
{
- float noise; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "blacks"
+ float noise; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "blacks"
float ultra_deep_blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "deep shadows"
- float deep_blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
- float blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "light shadows"
- float shadows; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
- float midtones; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "dark highlights"
- float highlights; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
- float whites; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "whites"
- float speculars; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "speculars"
- float blending; // $MIN: 0.01 $MAX: 100.0 $DEFAULT: 5.0 $DESCRIPTION: "smoothing diameter"
- float smoothing; // $DEFAULT: 1.414213562 sqrtf(2.0f)
- float feathering; // $MIN: 0.01 $MAX: 10000.0 $DEFAULT: 1.0 $DESCRIPTION: "edges refinement/feathering"
- float quantization; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "mask quantization"
- float contrast_boost; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "mask contrast compensation"
- float exposure_boost; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "mask exposure compensation"
- dt_iop_toneequalizer_filter_t details; // $DEFAULT: DT_TONEEQ_EIGF
- dt_iop_luminance_mask_method_t method; // $DEFAULT: DT_TONEEQ_NORM_2 $DESCRIPTION: "luminance estimator"
- int iterations; // $MIN: 1 $MAX: 20 $DEFAULT: 1 $DESCRIPTION: "filter diffusion"
+ float deep_blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
+ float blacks; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "light shadows"
+ float shadows; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
+ float midtones; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "dark highlights"
+ float highlights; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
+ float whites; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "whites"
+ float speculars; // $MIN: -2.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "speculars"
+ float blending; // $MIN: 0.01 $MAX: 100.0 $DEFAULT: 5.0 $DESCRIPTION: "smoothing diameter"
+ float smoothing; // $DEFAULT: 1.414213562 sqrtf(2.0f)
+ float feathering; // $MIN: 0.01 $MAX: 10000.0 $DEFAULT: 1.0 $DESCRIPTION: "edges refinement/feathering"
+ float quantization; // $MIN: 0.0 $MAX: 2.0 $DEFAULT: 0.0 $DESCRIPTION: "mask quantization"
+ float contrast_boost; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "mask contrast compensation"
+ float exposure_boost; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "mask exposure compensation"
+ dt_iop_toneequalizer_filter_t filter; // $DEFAULT: DT_TONEEQ_EIGF
+ dt_iop_luminance_mask_method_t lum_estimator; // $DEFAULT: DT_TONEEQ_NORM_2 $DESCRIPTION: "luminance estimator"
+ int iterations; // $MIN: 1 $MAX: 20 $DEFAULT: 1 $DESCRIPTION: "filter diffusion"
+ float post_scale; // $MIN: -3.0 $MAX: 3.0 $DEFAULT: 0.0 $DESCRIPTION: "mask contrast / scale histogram"
+ float post_shift; // $MIN: -4.0 $MAX: 4.0 $DEFAULT: 0.0 $DESCRIPTION: "mask brightness / shift histogram"
+ dt_iop_toneequalizer_post_auto_align_t post_auto_align; // $DEFAULT: DT_TONEEQ_ALIGN_CUSTOM $DESCRIPTION: "auto align mask exposure"
} dt_iop_toneequalizer_params_t;
typedef struct dt_iop_toneequalizer_data_t
{
- float factors[PIXEL_CHAN] DT_ALIGNED_ARRAY;
- float correction_lut[PIXEL_CHAN * LUT_RESOLUTION + 1] DT_ALIGNED_ARRAY;
- float blending, feathering, contrast_boost, exposure_boost, quantization, smoothing;
+ float factors[NUM_OCTAVES] DT_ALIGNED_ARRAY;
+ float correction_lut[NUM_OCTAVES * LUT_RESOLUTION + 1] DT_ALIGNED_ARRAY;
+ float blending, feathering, contrast_boost, exposure_boost, quantization, smoothing, post_scale, post_shift;
float scale;
int radius;
int iterations;
- dt_iop_luminance_mask_method_t method;
- dt_iop_toneequalizer_filter_t details;
+ dt_iop_luminance_mask_method_t lum_estimator;
+ dt_iop_toneequalizer_filter_t filter;
+ dt_iop_toneequalizer_post_auto_align_t post_auto_align;
} dt_iop_toneequalizer_data_t;
@@ -211,11 +249,15 @@ typedef struct dt_iop_toneequalizer_global_data_t
typedef struct dt_iop_toneequalizer_gui_data_t
{
// Mem arrays 64-bytes aligned - contiguous memory
- float factors[PIXEL_CHAN] DT_ALIGNED_ARRAY;
- float gui_lut[UI_SAMPLES] DT_ALIGNED_ARRAY; // LUT for the UI graph
- float interpolation_matrix[CHANNELS * PIXEL_CHAN] DT_ALIGNED_ARRAY;
- int histogram[UI_SAMPLES] DT_ALIGNED_ARRAY; // histogram for the UI graph
- float temp_user_params[CHANNELS] DT_ALIGNED_ARRAY;
+ float factors[NUM_OCTAVES] DT_ALIGNED_ARRAY;
+ float gui_curve[UI_HISTO_SAMPLES] DT_ALIGNED_ARRAY; // LUT for the UI graph
+ GdkRGBA gui_curve_colors[UI_HISTO_SAMPLES] DT_ALIGNED_ARRAY; // color for the UI graph
+ float interpolation_matrix[NUM_SLIDERS * NUM_OCTAVES] DT_ALIGNED_ARRAY;
+ int histogram[UI_HISTO_SAMPLES] DT_ALIGNED_ARRAY; // mask histogram for the UI graph
+ int hires_histogram[HIRES_HISTO_SAMPLES] DT_ALIGNED_ARRAY; // hires mask histogram
+ int image_histogram[UI_HISTO_SAMPLES] DT_ALIGNED_ARRAY; // image histogram for UI graph
+ int image_hires_histogram[HIRES_HISTO_SAMPLES] DT_ALIGNED_ARRAY; // hires image histogram
+ float temp_user_params[NUM_SLIDERS] DT_ALIGNED_ARRAY;
float cursor_exposure; // store the exposure value at current cursor position
float step; // scrolling step
@@ -229,30 +271,47 @@ typedef struct dt_iop_toneequalizer_gui_data_t
int pipe_order;
// 6 uint64 to pack - contiguous-ish memory
- dt_hash_t ui_preview_hash;
- dt_hash_t thumb_preview_hash;
- size_t full_preview_buf_width, full_preview_buf_height;
- size_t thumb_preview_buf_width, thumb_preview_buf_height;
+ dt_hash_t full_upstream_hash;
+ dt_hash_t preview_upstream_hash;
+ dt_hash_t sync_hash;
+
+ size_t preview_buf_width, preview_buf_height;
+ size_t full_buf_width, full_buf_height;
+
+ // Heap arrays, 64 bits-aligned, unknown length
+ float *preview_buf; // For performance and to get the mask luminance under the mouse cursor
+ float *full_buf; // For performance and for displaying the mask as greyscale
// Misc stuff, contiguity, length and alignment unknown
float scale;
float sigma;
- float histogram_average;
+
+ // stats for the mask histogram
float histogram_first_decile;
float histogram_last_decile;
- // Heap arrays, 64 bits-aligned, unknown length
- float *thumb_preview_buf;
- float *full_preview_buf;
+ // automatic values for post scale/shift from PREVIEW thread
+ float post_scale_value;
+ float post_shift_value;
+
+ // stats for the image histogram
+ float image_histogram_first_decile;
+ float image_histogram_last_decile;
+ int max_image_histogram;
+ float image_EV_per_UI_sample;
+ gboolean two_histograms_display;
// GTK garbage, nobody cares, no SIMD here
GtkWidget *noise, *ultra_deep_blacks, *deep_blacks, *blacks, *shadows, *midtones, *highlights, *whites, *speculars;
GtkDrawingArea *area, *bar;
GtkWidget *blending, *smoothing, *quantization;
+ GtkWidget *post_auto_align;
GtkWidget *method;
- GtkWidget *details, *feathering, *contrast_boost, *iterations, *exposure_boost;
+ GtkWidget *details, *feathering, *contrast_boost, *iterations, *exposure_boost, *post_scale, *post_shift;
GtkNotebook *notebook;
+ dt_gui_collapsible_section_t sliders_section, advanced_masking_section;
GtkWidget *show_luminance_mask;
+ GtkWidget *show_two_histograms;
// Cache Pango and Cairo stuff for the equalizer drawing
float line_height;
@@ -277,8 +336,8 @@ typedef struct dt_iop_toneequalizer_gui_data_t
GtkStyleContext *context;
// Event for equalizer drawing
- float nodes_x[CHANNELS] DT_ALIGNED_ARRAY;
- float nodes_y[CHANNELS] DT_ALIGNED_ARRAY;
+ float nodes_x[NUM_SLIDERS] DT_ALIGNED_ARRAY;
+ float nodes_y[NUM_SLIDERS] DT_ALIGNED_ARRAY;
float area_x; // x coordinate of cursor over graph/drawing area
float area_y; // y coordinate
int area_active_node;
@@ -294,16 +353,20 @@ typedef struct dt_iop_toneequalizer_gui_data_t
gboolean has_focus; // TRUE if the widget has the focus from GTK
// Flags for buffer caches invalidation
- gboolean interpolation_valid; // TRUE if the interpolation_matrix is ready
- gboolean luminance_valid; // TRUE if the luminance cache is ready
- gboolean histogram_valid; // TRUE if the histogram cache and stats are ready
- gboolean lut_valid; // TRUE if the gui_lut is ready
+ gboolean luminance_valid; // TRUE if the luminance cache is ready,
+ // hires_histogram and deciles are valid
+ gboolean gui_histogram_valid; // TRUE if the histogram cache and stats are ready
gboolean graph_valid; // TRUE if the UI graph view is ready
+
+ // For the curve interpolation
+ gboolean interpolation_valid; // TRUE if the interpolation_matrix is ready
+
gboolean user_param_valid; // TRUE if users params set in
// interactive view are in bounds
gboolean factors_valid; // TRUE if radial-basis coeffs are ready
+ gboolean gui_curve_valid; // TRUE if the gui_curve is ready
- gboolean distort_signal_actif;
+ gboolean distort_signal_active;
} dt_iop_toneequalizer_gui_data_t;
/* the signal DT_SIGNAL_DEVELOP_DISTORT is used to refresh the internal
@@ -311,11 +374,18 @@ typedef struct dt_iop_toneequalizer_gui_data_t
static void _set_distort_signal(dt_iop_module_t *self);
static void _unset_distort_signal(dt_iop_module_t *self);
+/****************************************************************************
+ *
+ * Darktable housekeeping functions
+ *
+ ****************************************************************************/
+
const char *name()
{
return _("tone equalizer");
}
+
const char *aliases()
{
return _("tone curve|tone mapping|relight|background light|shadows highlights");
@@ -332,16 +402,19 @@ const char **description(dt_iop_module_t *self)
_("quasi-linear, RGB, scene-referred"));
}
+
int default_group()
{
return IOP_GROUP_BASIC | IOP_GROUP_GRADING;
}
+
int flags()
{
return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING;
}
+
dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,
dt_dev_pixelpipe_t *pipe,
dt_dev_pixelpipe_iop_t *piece)
@@ -349,6 +422,7 @@ dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,
return IOP_CS_RGB;
}
+
int legacy_params(dt_iop_module_t *self,
const void *const old_params,
const int old_version,
@@ -356,7 +430,9 @@ int legacy_params(dt_iop_module_t *self,
int32_t *new_params_size,
int *new_version)
{
- typedef struct dt_iop_toneequalizer_params_v2_t
+ printf("legacy_params old_version=%d\n", old_version);
+
+ typedef struct dt_iop_toneequalizer_params_v3_t
{
float noise;
float ultra_deep_blacks;
@@ -376,7 +452,10 @@ int legacy_params(dt_iop_module_t *self,
dt_iop_toneequalizer_filter_t details;
dt_iop_luminance_mask_method_t method;
int iterations;
- } dt_iop_toneequalizer_params_v2_t;
+ float post_scale;
+ float post_shift;
+ dt_iop_toneequalizer_post_auto_align_t post_auto_align;
+ } dt_iop_toneequalizer_params_v3_t;
if(old_version == 1)
{
@@ -391,7 +470,7 @@ int legacy_params(dt_iop_module_t *self,
} dt_iop_toneequalizer_params_v1_t;
const dt_iop_toneequalizer_params_v1_t *o = old_params;
- dt_iop_toneequalizer_params_v2_t *n = malloc(sizeof(dt_iop_toneequalizer_params_v2_t));
+ dt_iop_toneequalizer_params_v3_t *n = malloc(sizeof(dt_iop_toneequalizer_params_v3_t));
// Olds params
n->noise = o->noise;
@@ -413,18 +492,81 @@ int legacy_params(dt_iop_module_t *self,
n->iterations = o->iterations;
n->method = o->method;
- // New params
+ // V2 params
n->quantization = 0.0f;
n->smoothing = sqrtf(2.0f);
+ // V3 params
+ n->post_scale = 0.0f;
+ n->post_shift = 0.0f;
+ n->post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
+
+ *new_params = n;
+ *new_params_size = sizeof(dt_iop_toneequalizer_params_v3_t);
+ *new_version = 3;
+ return 0;
+ }
+
+ if(old_version == 2)
+ {
+ typedef struct dt_iop_toneequalizer_params_v2_t
+ {
+ float noise; float ultra_deep_blacks; float deep_blacks; float blacks;
+ float shadows; float midtones; float highlights; float whites;
+ float speculars; float blending; float smoothing; float feathering;
+ float quantization; float contrast_boost; float exposure_boost;
+ dt_iop_toneequalizer_filter_t details;
+ dt_iop_luminance_mask_method_t method;
+ int iterations;
+ } dt_iop_toneequalizer_params_v2_t;
+
+ const dt_iop_toneequalizer_params_v2_t *o = old_params;
+ dt_iop_toneequalizer_params_v3_t *n = malloc(sizeof(dt_iop_toneequalizer_params_v3_t));
+
+ // V1 params
+ n->noise = o->noise;
+ n->ultra_deep_blacks = o->ultra_deep_blacks;
+ n->deep_blacks = o->deep_blacks;
+ n->blacks = o->blacks;
+ n->shadows = o->shadows;
+ n->midtones = o->midtones;
+ n->highlights = o->highlights;
+ n->whites = o->whites;
+ n->speculars = o->speculars;
+
+ n->blending = o->blending;
+ n->feathering = o->feathering;
+ n->contrast_boost = o->contrast_boost;
+ n->exposure_boost = o->exposure_boost;
+
+ n->details = o->details;
+ n->iterations = o->iterations;
+ n->method = o->method;
+
+ // V2 params
+ n->quantization = o->quantization;
+ n->smoothing = o->smoothing;
+
+ // V3 params
+ n->post_scale = 0.0f;
+ n->post_shift = 0.0f;
+ n->post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
+
*new_params = n;
- *new_params_size = sizeof(dt_iop_toneequalizer_params_v2_t);
- *new_version = 2;
+ *new_params_size = sizeof(dt_iop_toneequalizer_params_v3_t);
+ *new_version = 3;
return 0;
}
+
return 1;
}
+
+/****************************************************************************
+ *
+ * Presets
+ *
+ ****************************************************************************/
static void compress_shadows_highlight_preset_set_exposure_params
(dt_iop_toneequalizer_params_t* p,
const float step)
@@ -461,19 +603,23 @@ static void dilate_shadows_highlight_preset_set_exposure_params
p->speculars = 15.f / 9.f * step;
}
+
void init_presets(dt_iop_module_so_t *self)
{
dt_iop_toneequalizer_params_t p;
memset(&p, 0, sizeof(p));
- p.method = DT_TONEEQ_NORM_POWER;
+ p.lum_estimator = DT_TONEEQ_NORM_POWER;
p.contrast_boost = 0.0f;
- p.details = DT_TONEEQ_NONE;
+ p.filter = DT_TONEEQ_NONE;
p.exposure_boost = -0.5f;
p.feathering = 1.0f;
p.iterations = 1;
p.smoothing = sqrtf(2.0f);
p.quantization = 0.0f;
+ p.post_scale = 0.0f;
+ p.post_shift = 0.0f;
+ p.post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
// Init exposure settings
p.noise = p.ultra_deep_blacks = p.deep_blacks = p.blacks = 0.0f;
@@ -485,8 +631,8 @@ void init_presets(dt_iop_module_so_t *self)
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// Simple utils blendings
- p.details = DT_TONEEQ_EIGF;
- p.method = DT_TONEEQ_NORM_2;
+ p.filter = DT_TONEEQ_EIGF;
+ p.lum_estimator = DT_TONEEQ_NORM_2;
p.blending = 5.0f;
p.feathering = 1.0f;
@@ -495,14 +641,14 @@ void init_presets(dt_iop_module_so_t *self)
p.exposure_boost = 0.0f;
p.contrast_boost = 0.0f;
dt_gui_presets_add_generic
- (_("mask blending | all purposes"), self->op,
+ (_("mask blending: all purposes"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.blending = 1.0f;
p.feathering = 10.0f;
p.iterations = 3;
dt_gui_presets_add_generic
- (_("mask blending | people with backlight"), self->op,
+ (_("mask blending: people with backlight"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// Shadows/highlights presets
@@ -515,66 +661,66 @@ void init_presets(dt_iop_module_so_t *self)
p.quantization = 0.0f;
// slight modification to give higher compression
- p.details = DT_TONEEQ_EIGF;
+ p.filter = DT_TONEEQ_EIGF;
p.feathering = 20.0f;
compress_shadows_highlight_preset_set_exposure_params(&p, 0.65f);
dt_gui_presets_add_generic
- (_("compress shadows/highlights | EIGF | strong"), self->op,
+ (_("compress shadows/highlights (EIGF): strong"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- p.details = DT_TONEEQ_GUIDED;
+ p.filter = DT_TONEEQ_GUIDED;
p.feathering = 500.0f;
dt_gui_presets_add_generic
- (_("compress shadows/highlights | GF | strong"), self->op,
+ (_("compress shadows/highlights (GF): strong"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- p.details = DT_TONEEQ_EIGF;
+ p.filter = DT_TONEEQ_EIGF;
p.blending = 3.0f;
p.feathering = 7.0f;
p.iterations = 3;
compress_shadows_highlight_preset_set_exposure_params(&p, 0.45f);
dt_gui_presets_add_generic
- (_("compress shadows/highlights | EIGF | medium"), self->op,
+ (_("compress shadows/highlights (EIGF): medium"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- p.details = DT_TONEEQ_GUIDED;
+ p.filter = DT_TONEEQ_GUIDED;
p.feathering = 500.0f;
dt_gui_presets_add_generic
- (_("compress shadows/highlights | GF | medium"), self->op,
+ (_("compress shadows/highlights (GF): medium"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- p.details = DT_TONEEQ_EIGF;
+ p.filter = DT_TONEEQ_EIGF;
p.blending = 5.0f;
p.feathering = 1.0f;
p.iterations = 1;
compress_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
dt_gui_presets_add_generic
- (_("compress shadows/highlights | EIGF | soft"), self->op,
+ (_("compress shadows/highlights (EIGF): soft"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- p.details = DT_TONEEQ_GUIDED;
+ p.filter = DT_TONEEQ_GUIDED;
p.feathering = 500.0f;
dt_gui_presets_add_generic
- (_("compress shadows/highlights | GF | soft"), self->op,
+ (_("compress shadows/highlights (GF): soft"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// build the 1D contrast curves that revert the local compression of
// contrast above
- p.details = DT_TONEEQ_NONE;
+ p.filter = DT_TONEEQ_NONE;
dilate_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
dt_gui_presets_add_generic
- (_("contrast tone curve | soft"), self->op,
+ (_("contrast tone curve: soft"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
dilate_shadows_highlight_preset_set_exposure_params(&p, 0.45f);
dt_gui_presets_add_generic
- (_("contrast tone curve | medium"), self->op,
+ (_("contrast tone curve: medium"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
dilate_shadows_highlight_preset_set_exposure_params(&p, 0.65f);
dt_gui_presets_add_generic
- (_("contrast tone curve | strong"), self->op,
+ (_("contrast tone curve: strong"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// relight
- p.details = DT_TONEEQ_EIGF;
+ p.filter = DT_TONEEQ_EIGF;
p.blending = 5.0f;
p.feathering = 1.0f;
p.iterations = 1;
@@ -598,16 +744,82 @@ void init_presets(dt_iop_module_so_t *self)
}
-/**
- * Helper functions
- **/
-
-static gboolean in_mask_editing(dt_iop_module_t *self)
+/****************************************************************************
+ *
+ * Functions that are needed by process and therefore
+ * are part of worker threads
+ *
+ ****************************************************************************/
+__DT_CLONE_TARGETS__
+static inline void compute_hires_histogram_and_stats(const float *const restrict luminance,
+ int hires_histogram[HIRES_HISTO_SAMPLES],
+ const size_t num_elem,
+ float *first_decile,
+ float *last_decile,
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
- const dt_develop_t *dev = self->dev;
- return dev->form_gui && dev->form_visible;
+ printf("compute_hires_histogram_and_stats pipe=%d num_elem=%ld\n", debug_pipe, num_elem);
+ // The GUI histogram comprises 8 EV (UI_HISTO_SAMPLES, -8 to 0).
+ // The high resolution histogram extends this to an exta 8 EV before and
+ // 8EV after, for a total of 24.
+ // Also the resolution is increased to compensate for the fact that the user
+ // can scale the histogram.
+ const float temp_ev_range = HIRES_HISTO_MAX_EV - HIRES_HISTO_MIN_EV;
+
+ // (Re)init the histogram
+ memset(hires_histogram, 0, sizeof(int) * HIRES_HISTO_SAMPLES);
+
+ // Split exposure in bins
+ DT_OMP_FOR_SIMD(reduction(+:hires_histogram[:HIRES_HISTO_SAMPLES]))
+ for(size_t k = 0; k < num_elem; k++)
+ {
+ const int index =
+ CLAMP((int)(((log2f(luminance[k]) - HIRES_HISTO_MIN_EV) / temp_ev_range) * (float)HIRES_HISTO_SAMPLES),
+ 0, HIRES_HISTO_SAMPLES - 1);
+ hires_histogram[index] += 1;
+ }
+
+ // printf("hires_histogram 0: %d 256: %d 512: %d 767: %d\n", hires_histogram[0], hires_histogram[256], hires_histogram[512], hires_histogram[767]);
+
+ const int first_decile_pop = (int)((float)num_elem * 0.05f);
+ const int last_decile_pop = (int)((float)num_elem * (1.0f - 0.95f));
+ int population = 0;
+ int first_decile_pos = 0;
+ int last_decile_pos = 0;
+ int k;
+
+ // Scout the extended histogram bins looking for the
+ // absolute first and last non-zero values and for deciles.
+ // These would not be accurate with the gui histogram.
+
+ for(k = 0; k < HIRES_HISTO_SAMPLES; ++k)
+ {
+ population += hires_histogram[k];
+ if (population >= first_decile_pop)
+ {
+ first_decile_pos = k;
+ break;
+ }
+ }
+
+ population = 0;
+ for(k = HIRES_HISTO_SAMPLES - 1; k >= 0; --k)
+ {
+ population += hires_histogram[k];
+ if (population >= last_decile_pop)
+ {
+ last_decile_pos = k;
+ break;
+ }
+ }
+ // printf("First pos: %d, Last pos: %d\n", first_pos, last_pos);
+
+ // Convert decile positions to exposures
+ *first_decile = (temp_ev_range * ((float)first_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
+ *last_decile = (temp_ev_range * ((float)last_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
}
+<<<<<<< HEAD
static void hash_set_get(const dt_hash_t *hash_in,
dt_hash_t *hash_out,
dt_pthread_mutex_t *lock)
@@ -640,115 +852,254 @@ static const dt_colormatrix_t gauss_kernel =
{ { 0.076555024f, 0.124401914f, 0.076555024f },
{ 0.124401914f, 0.196172249f, 0.124401914f },
{ 0.076555024f, 0.124401914f, 0.076555024f } };
+=======
+>>>>>>> acabda0cbe (2025-04-06 preview version with post-shift, post_scale, auto-align and curve coloring.)
__DT_CLONE_TARGETS__
-static float get_luminance_from_buffer(const float *const buffer,
- const size_t width,
- const size_t height,
- const size_t x,
- const size_t y)
+static inline void compute_luminance_mask(const float *const restrict in,
+ float *const restrict luminance,
+ const size_t width,
+ const size_t height,
+ const dt_iop_toneequalizer_data_t *const d,
+ const gboolean compute_image_stats, // Optionally get the histogram of the image
+ int hires_histogram[HIRES_HISTO_SAMPLES], // only for compute_image_stats
+ float *first_decile, // only for compute_image_stats
+ float *last_decile, // only for compute_image_stats
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
- // Get the weighted average luminance of the 3×3 pixels region centered in (x, y)
- // x and y are ratios in [0, 1] of the width and height
+ printf("compute_luminance_mask pipe=%d width=%ld height=%ld first luminance=%f, compute stats=%d\n", debug_pipe, width, height, luminance[0], compute_image_stats);
+ const int num_elem = width * height;
+ switch(d->filter)
+ {
+ case(DT_TONEEQ_NONE):
+ {
+ // No contrast boost here
+ luminance_mask(in, luminance, width, height,
+ d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
+ if (compute_image_stats)
+ compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ break;
+ }
- if(y >= height || x >= width) return NAN;
+ case(DT_TONEEQ_AVG_GUIDED):
+ {
+ // Still no contrast boost
+ luminance_mask(in, luminance, width, height,
+ d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
+ if (compute_image_stats)
+ compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
+ DT_GF_BLENDING_GEOMEAN, d->scale, d->quantization,
+ exp2f(-14.0f), 4.0f);
+ break;
+ }
- const size_t y_abs[4] DT_ALIGNED_PIXEL =
- { MAX(y, 1) - 1, // previous line
- y, // center line
- MIN(y + 1, height - 1), // next line
- y }; // padding for vectorization
+ case(DT_TONEEQ_GUIDED):
+ {
+ // Contrast boosting is done around the average luminance of the mask.
+ // This is to make exposure corrections easier to control for users, by spreading
+ // the dynamic range along all exposure NUM_SLIDERS, because guided filters
+ // tend to flatten the luminance mask a lot around an average ± 2 EV
+ // which makes only 2-3 NUM_SLIDERS usable.
+ // we assume the distribution is centered around -4EV, e.g. the center of the nodes
+ // the exposure boost should be used to make this assumption true
+ luminance_mask(in, luminance, width, height, d->lum_estimator, d->exposure_boost,
+ CONTRAST_FULCRUM, d->contrast_boost);
+ if (compute_image_stats)
+ compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
+ DT_GF_BLENDING_LINEAR, d->scale, d->quantization,
+ exp2f(-14.0f), 4.0f);
+ break;
+ }
- float luminance = 0.0f;
- if(x > 1 && x < width - 2)
- {
- // no clamping needed on x, which allows us to vectorize
- // apply the convolution
- for(int i = 0; i < 3; ++i)
+ case(DT_TONEEQ_AVG_EIGF):
{
- const size_t y_i = y_abs[i];
- for_each_channel(j)
- luminance += buffer[width * y_i + x-1 + j] * gauss_kernel[i][j];
+ // Still no contrast boost
+ luminance_mask(in, luminance, width, height,
+ d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
+ if (compute_image_stats)
+ compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ fast_eigf_surface_blur(luminance, width, height,
+ d->radius, d->feathering, d->iterations,
+ DT_GF_BLENDING_GEOMEAN, d->scale,
+ d->quantization, exp2f(-14.0f), 4.0f);
+ break;
}
- return luminance;
- }
- const size_t x_abs[4] DT_ALIGNED_PIXEL =
- { MAX(x, 1) - 1, // previous column
- x, // center column
- MIN(x + 1, width - 1), // next column
- x }; // padding for vectorization
+ case(DT_TONEEQ_EIGF):
+ {
+ luminance_mask(in, luminance, width, height, d->lum_estimator, d->exposure_boost,
+ CONTRAST_FULCRUM, d->contrast_boost);
+ if (compute_image_stats)
+ compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ fast_eigf_surface_blur(luminance, width, height,
+ d->radius, d->feathering, d->iterations,
+ DT_GF_BLENDING_LINEAR, d->scale,
+ d->quantization, exp2f(-14.0f), 4.0f);
+ break;
+ }
- // convolution
- for(int i = 0; i < 3; ++i)
- {
- const size_t y_i = y_abs[i];
- for_each_channel(j)
- luminance += buffer[width * y_i + x_abs[j]] * gauss_kernel[i][j];
+ default:
+ {
+ luminance_mask(in, luminance, width, height,
+ d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
+ if (compute_image_stats)
+ compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ break;
+ }
}
- return luminance;
}
-static void _get_point(dt_iop_module_t *self,
- const int c_x,
- const int c_y,
- int *x,
- int *y)
-{
- // TODO: For this to fully work non depending on the place of the module
- // in the pipe we need a dt_dev_distort_backtransform_plus that
- // can skip crop only. With the current version if toneequalizer
- // is moved below rotation & perspective it will fail as we are
- // then missing all the transform after tone-eq.
- const double crop_order =
- dt_ioppr_get_iop_order(self->dev->iop_order_list, "crop", 0);
-
- float pts[2] = { c_x, c_y };
-
- // only a forward backtransform as the buffer already contains all the transforms
- // done before toneequal and we are speaking of on-screen cursor coordinates.
- // also we do transform only after crop as crop does change roi for the whole pipe
- // and so it is already part of the preview buffer cached in this implementation.
- dt_dev_distort_backtransform_plus(darktable.develop, darktable.develop->preview_pipe,
- crop_order,
- DT_DEV_TRANSFORM_DIR_FORW_EXCL, pts, 1);
- *x = pts[0];
- *y = pts[1];
-}
-static float _luminance_from_module_buffer(dt_iop_module_t *self)
+// This is similar to exposure/contrast boost.
+// However it is applied AFTER the guided filter calculation, so it is much
+// easier to control and does not mess with the detail detection of the
+// guided filter.
+static inline float post_scale_shift(const float v, const float post_scale, const float post_shift)
{
- dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ const float scale_exp = exp2f(post_scale);
+ // signifficant range -8..0, centering around the middle
+ return (v + 4.0f) * scale_exp - 4.0f + post_shift;
+}
- const size_t c_x = g->cursor_pos_x;
- const size_t c_y = g->cursor_pos_y;
- // get buffer x,y given the cursor position
- int b_x = 0;
- int b_y = 0;
+// This is similar to the auto-buttons for exposure/contrast boost.
+// However it runs automatically in the pipe, so it does not need to be
+// triggered by the user each time the upstream exposure changes.
+void compute_auto_post_scale_shift(float *post_scale, float *post_shift,
+ dt_iop_toneequalizer_post_auto_align_t post_auto_align,
+ float histogram_first_decile,
+ float histogram_last_decile,
+ dt_dev_pixelpipe_type_t const debug_pipe
+ )
+{
+ const float first_decile_target = -7.0f;
+ const float last_decile_target = -1.0f;
+ const float pivot = -4.0f; // for scaling
- _get_point(self, c_x, c_y, &b_x, &b_y);
+ printf("compute_auto_post_shift_scale: Pipe=%d old post_scale=%f post_shift=%f histogram_first_decile=%f histogram_last_decile=%f\n",
+ debug_pipe, *post_scale, *post_shift, histogram_first_decile, histogram_last_decile);
- return get_luminance_from_buffer(g->thumb_preview_buf,
- g->thumb_preview_buf_width,
- g->thumb_preview_buf_height,
- b_x,
- b_y);
-}
+ switch(post_auto_align)
+ {
+ case(DT_TONEEQ_ALIGN_CUSTOM):
+ {
+ // fully user-controlled, do not modify
+ break;
+ }
+ case(DT_TONEEQ_ALIGN_LEFT):
+ {
+ // auto-align at shadows
+ // the histogram might be scaled around pivot
+ const float scaled_first_decile = (histogram_first_decile - pivot) * exp2f(*post_scale) + pivot;
+ *post_shift = first_decile_target - scaled_first_decile;
+ break;
+ }
+ case(DT_TONEEQ_ALIGN_CENTER):
+ {
+ const float histogram_middle = (histogram_first_decile + histogram_last_decile) / 2.0f;
+ const float target_middle = (first_decile_target + last_decile_target) / 2.0f;
+ const float scaled_middle = (histogram_middle - pivot) * exp2f(*post_scale) + pivot;
+ *post_shift = target_middle - scaled_middle;
+ break;
+ }
+ case(DT_TONEEQ_ALIGN_RIGHT):
+ {
+ // auto-align at highlights
+ const float scaled_last_decile = (histogram_last_decile - pivot) * exp2f(*post_scale) + pivot;
+ *post_shift = last_decile_target - scaled_last_decile;
+ break;
+ }
+ case(DT_TONEEQ_ALIGN_FIT):
+ {
+ // fully fit
+ *post_scale = log2f((last_decile_target - first_decile_target) / (histogram_last_decile - histogram_first_decile));
+ const float scaled_first_decile = (histogram_first_decile - pivot) * exp2f(*post_scale) + pivot;
+ *post_shift = first_decile_target - scaled_first_decile;
+ break;
+ }
+ }
+ printf("compute_auto_post_shift_scale: New post_scale=%f post_shift=%f\n", *post_scale, *post_shift);
+};
-/***
- * Exposure compensation computation
- *
- * Construct the final correction factor by summing the octaves
- * channels gains weighted by the gaussian of the radial distance
- * (pixel exposure - octave center)
- *
- ***/
-DT_OMP_DECLARE_SIMD()
__DT_CLONE_TARGETS__
-static float gaussian_denom(const float sigma)
-{
+static inline void display_luminance_mask(const float *const restrict in,
+ const float *const restrict luminance,
+ float *const restrict out,
+ const dt_iop_roi_t *const roi_in,
+ const dt_iop_roi_t *const roi_out,
+ const float post_scale,
+ const float post_shift,
+ dt_dev_pixelpipe_type_t const debug_pipe)
+{
+ const size_t offset_x = (roi_in->x < roi_out->x) ? -roi_in->x + roi_out->x : 0;
+ const size_t offset_y = (roi_in->y < roi_out->y) ? -roi_in->y + roi_out->y : 0;
+
+ printf("display_luminance_mask pipe=%d offset_x=%ld offset_y=%ld roi_in %d %d %d %d roi_out %d %d %d %d, post_scale=%f, post_shift=%f\n",
+ debug_pipe, offset_x, offset_y,
+ roi_in->x, roi_in->y, roi_in->width, roi_in->height,
+ roi_out->x, roi_out->y, roi_out->width, roi_out->height,
+ post_scale, post_shift);
+
+ // The output dimensions need to be smaller or equal to the input ones
+ // there is no logical reason they shouldn't, except some weird bug in the pipe
+ // in this case, ensure we don't segfault
+ const size_t in_width = roi_in->width;
+ const size_t out_width = (roi_in->width > roi_out->width)
+ ? roi_out->width
+ : roi_in->width;
+
+ const size_t out_height = (roi_in->height > roi_out->height)
+ ? roi_out->height
+ : roi_in->height;
+
+ DT_OMP_FOR(collapse(2))
+ for(size_t i = 0 ; i < out_height; ++i)
+ for(size_t j = 0; j < out_width; ++j)
+ {
+ // normalize the mask intensity between -8 EV and 0 EV for clarity,
+ // and add a "gamma" 2.0 for better legibility in shadows
+ const int lum_index = (i + offset_y) * in_width + (j + offset_x);
+ const float lum_log = log2f(luminance[lum_index]);
+ const float lum_corrected = post_scale_shift(lum_log, post_scale, post_shift);
+
+ // IMHO it would be fine, to show the log version of the mask to the user.
+ // const float intensity =
+ // fminf(fmaxf((lum_corrected + 8.0f) / 8.0f, 0.f), 1.f);
+ // However to keep everything identical to before, we go back to linear
+ // space and apply the square root/"gamma".
+ const float lum_linear = exp2f(lum_corrected);
+ const float intensity =
+ sqrtf(fminf(
+ fmaxf(lum_linear - 0.00390625f, 0.f) / 0.99609375f,
+ 1.f));
+
+ const size_t index = (i * out_width + j) * 4;
+ // set gray level for the mask
+ for_each_channel(c,aligned(out))
+ {
+ out[index + c] = intensity;
+ }
+ // copy alpha channel
+ out[index + 3] = in[((i + offset_y) * in_width + (j + offset_x)) * 4 + 3];
+ }
+}
+
+
+/***
+ * Exposure compensation computation
+ *
+ * Construct the final correction factor by summing the octaves
+ * NUM_SLIDERS gains weighted by the gaussian of the radial distance
+ * (pixel exposure - octave center)
+ *
+ ***/
+DT_OMP_DECLARE_SIMD()
+__DT_CLONE_TARGETS__
+static float gaussian_denom(const float sigma)
+{
// Gaussian function denominator such that y = exp(- radius^2 / denominator)
// this is the constant factor of the exponential, so we don't need to recompute it
// for every single pixel
@@ -767,11 +1118,37 @@ static float gaussian_func(const float radius, const float denominator)
return expf(- radius * radius / denominator);
}
-#define DT_TONEEQ_MIN_EV (-8.0f)
-#define DT_TONEEQ_MAX_EV (0.0f)
-#define DT_TONEEQ_USE_LUT TRUE
-#if DT_TONEEQ_USE_LUT
+static void compute_correction_lut(float *restrict lut, const float sigma,
+ const float *const restrict factors,
+ const float post_scale, const float post_shift,
+ dt_dev_pixelpipe_type_t const debug_pipe)
+{
+ printf("compute_correction_lut pipe=%d, post_scale=%f, post_shift=%f\n", debug_pipe, post_scale, post_shift);
+ const float gauss_denom = gaussian_denom(sigma);
+ assert(NUM_OCTAVES == 8);
+
+ // TODO MF: Does the openmp still work here?
+ DT_OMP_FOR(shared(centers_ops))
+ for(int j = 0; j <= LUT_RESOLUTION * NUM_OCTAVES; j++)
+ {
+ // build the correction for each pixel
+ // as the sum of the contribution of each luminance channelcorrection
+ const float exposure_uncorrected = (float)j / (float)LUT_RESOLUTION + DT_TONEEQ_MIN_EV; // [-8...0] EV
+ const float exposure = fast_clamp(post_scale_shift(exposure_uncorrected, post_scale, post_shift),
+ DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+ float result = 0.0f;
+ for(int i = 0; i < NUM_OCTAVES; i++)
+ {
+ result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
+ }
+ // the user-set correction is expected in [-2;+2] EV, so is the interpolated one
+ lut[j] = fast_clamp(result, 0.25f, 4.0f);
+ }
+}
+
+
+#if DT_TONEEQ_USE_LUT
// this is the version currently used, as using a lut gives a
// big performance speedup on some cpus
__DT_CLONE_TARGETS__
@@ -780,8 +1157,14 @@ static inline void apply_toneequalizer(const float *const restrict in,
float *const restrict out,
const dt_iop_roi_t *const roi_in,
const dt_iop_roi_t *const roi_out,
- const dt_iop_toneequalizer_data_t *const d)
+ const dt_iop_toneequalizer_data_t *const d,
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
+ printf("apply_toneequalizer pipe=%d first luminance=%f roi_in %d %d %d %d roi_out %d %d %d %d post_scale=%f, post_shift=%f\n",
+ debug_pipe, luminance[0],
+ roi_in->x, roi_in->y, roi_in->width, roi_in->height,
+ roi_out->x, roi_out->y, roi_out->width, roi_out->height,
+ d->post_scale, d->post_shift);
const size_t npixels = (size_t)roi_in->width * roi_in->height;
const float* restrict lut = d->correction_lut;
const float lutres = LUT_RESOLUTION;
@@ -804,6 +1187,7 @@ static inline void apply_toneequalizer(const float *const restrict in,
// we keep this version for further reference (e.g. for implementing
// a gpu version)
+// TODO MF: Remove? This is no longer correct anyways.
__DT_CLONE_TARGETS__
static inline void apply_toneequalizer(const float *const restrict in,
const float *const restrict luminance,
@@ -828,8 +1212,8 @@ static inline void apply_toneequalizer(const float *const restrict in,
// quickely diverge outside
const float exposure = fast_clamp(log2f(luminance[k]), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
- DT_OMP_SIMD(aligned(luminance, centers_ops, factors:64) safelen(PIXEL_CHAN) reduction(+:result))
- for(int i = 0; i < PIXEL_CHAN; ++i)
+ DT_OMP_SIMD(aligned(luminance, centers_ops, factors:64) safelen(NUM_OCTAVES) reduction(+:result))
+ for(int i = 0; i < NUM_OCTAVES; ++i)
result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
// the user-set correction is expected in [-2;+2] EV, so is the interpolated one
@@ -842,150 +1226,6 @@ static inline void apply_toneequalizer(const float *const restrict in,
}
#endif // USE_LUT
-__DT_CLONE_TARGETS__
-static inline float pixel_correction(const float exposure,
- const float *const restrict factors,
- const float sigma)
-{
- // build the correction for the current pixel
- // as the sum of the contribution of each luminance channel
- float result = 0.0f;
- const float gauss_denom = gaussian_denom(sigma);
- const float expo = fast_clamp(exposure, DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
-
- DT_OMP_SIMD(aligned(centers_ops, factors:64) safelen(PIXEL_CHAN) reduction(+:result))
- for(int i = 0; i < PIXEL_CHAN; ++i)
- result += gaussian_func(expo - centers_ops[i], gauss_denom) * factors[i];
-
- return fast_clamp(result, 0.25f, 4.0f);
-}
-
-
-__DT_CLONE_TARGETS__
-static inline void compute_luminance_mask(const float *const restrict in,
- float *const restrict luminance,
- const size_t width,
- const size_t height,
- const dt_iop_toneequalizer_data_t *const d)
-{
- switch(d->details)
- {
- case(DT_TONEEQ_NONE):
- {
- // No contrast boost here
- luminance_mask(in, luminance, width, height,
- d->method, d->exposure_boost, 0.0f, 1.0f);
- break;
- }
-
- case(DT_TONEEQ_AVG_GUIDED):
- {
- // Still no contrast boost
- luminance_mask(in, luminance, width, height,
- d->method, d->exposure_boost, 0.0f, 1.0f);
- fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
- DT_GF_BLENDING_GEOMEAN, d->scale, d->quantization,
- exp2f(-14.0f), 4.0f);
- break;
- }
-
- case(DT_TONEEQ_GUIDED):
- {
- // Contrast boosting is done around the average luminance of the mask.
- // This is to make exposure corrections easier to control for users, by spreading
- // the dynamic range along all exposure channels, because guided filters
- // tend to flatten the luminance mask a lot around an average ± 2 EV
- // which makes only 2-3 channels usable.
- // we assume the distribution is centered around -4EV, e.g. the center of the nodes
- // the exposure boost should be used to make this assumption true
- luminance_mask(in, luminance, width, height, d->method, d->exposure_boost,
- CONTRAST_FULCRUM, d->contrast_boost);
- fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
- DT_GF_BLENDING_LINEAR, d->scale, d->quantization,
- exp2f(-14.0f), 4.0f);
- break;
- }
-
- case(DT_TONEEQ_AVG_EIGF):
- {
- // Still no contrast boost
- luminance_mask(in, luminance, width, height,
- d->method, d->exposure_boost, 0.0f, 1.0f);
- fast_eigf_surface_blur(luminance, width, height,
- d->radius, d->feathering, d->iterations,
- DT_GF_BLENDING_GEOMEAN, d->scale,
- d->quantization, exp2f(-14.0f), 4.0f);
- break;
- }
-
- case(DT_TONEEQ_EIGF):
- {
- luminance_mask(in, luminance, width, height, d->method, d->exposure_boost,
- CONTRAST_FULCRUM, d->contrast_boost);
- fast_eigf_surface_blur(luminance, width, height,
- d->radius, d->feathering, d->iterations,
- DT_GF_BLENDING_LINEAR, d->scale,
- d->quantization, exp2f(-14.0f), 4.0f);
- break;
- }
-
- default:
- {
- luminance_mask(in, luminance, width, height,
- d->method, d->exposure_boost, 0.0f, 1.0f);
- break;
- }
- }
-}
-
-/***
- * Actual transfer functions
- **/
-
-__DT_CLONE_TARGETS__
-static inline void display_luminance_mask(const float *const restrict in,
- const float *const restrict luminance,
- float *const restrict out,
- const dt_iop_roi_t *const roi_in,
- const dt_iop_roi_t *const roi_out)
-{
- const size_t offset_x = (roi_in->x < roi_out->x) ? -roi_in->x + roi_out->x : 0;
- const size_t offset_y = (roi_in->y < roi_out->y) ? -roi_in->y + roi_out->y : 0;
-
- // The output dimensions need to be smaller or equal to the input ones
- // there is no logical reason they shouldn't, except some weird bug in the pipe
- // in this case, ensure we don't segfault
- const size_t in_width = roi_in->width;
- const size_t out_width = (roi_in->width > roi_out->width)
- ? roi_out->width
- : roi_in->width;
-
- const size_t out_height = (roi_in->height > roi_out->height)
- ? roi_out->height
- : roi_in->height;
-
- DT_OMP_FOR(collapse(2))
- for(size_t i = 0 ; i < out_height; ++i)
- for(size_t j = 0; j < out_width; ++j)
- {
- // normalize the mask intensity between -8 EV and 0 EV for clarity,
- // and add a "gamma" 2.0 for better legibility in shadows
- const float intensity =
- sqrtf(fminf(
- fmaxf(luminance[(i + offset_y) * in_width + (j + offset_x)] - 0.00390625f,
- 0.f) / 0.99609375f,
- 1.f));
- const size_t index = (i * out_width + j) * 4;
- // set gray level for the mask
- for_each_channel(c,aligned(out))
- {
- out[index + c] = intensity;
- }
- // copy alpha channel
- out[index + 3] = in[((i + offset_y) * in_width + (j + offset_x)) * 4 + 3];
- }
-}
-
__DT_CLONE_TARGETS__
static
@@ -996,93 +1236,116 @@ void toneeq_process(dt_iop_module_t *self,
const dt_iop_roi_t *const roi_in,
const dt_iop_roi_t *const roi_out)
{
- const dt_iop_toneequalizer_data_t *const d = piece->data;
+ dt_iop_toneequalizer_data_t *const d = piece->data;
dt_iop_toneequalizer_gui_data_t *const g = self->gui_data;
const float *const restrict in = (float *const)ivoid;
float *const restrict out = (float *const)ovoid;
- float *restrict luminance = NULL;
const size_t width = roi_in->width;
const size_t height = roi_in->height;
const size_t num_elem = width * height;
- // Get the hash of the upstream pipe to track changes
- const dt_hash_t hash = dt_dev_pixelpipe_piece_hash(piece, roi_out, TRUE);
-
// Sanity checks
if(width < 1 || height < 1) return;
if(roi_in->width < roi_out->width || roi_in->height < roi_out->height)
return; // input should be at least as large as output
if(piece->colors != 4) return; // we need RGB signal
- // Init the luminance masks buffers
- gboolean cached = FALSE;
+ // This will be local memory or global cache stored in g
+ float *restrict luminance = NULL;
+ int *hires_histogram = NULL;
+
+ // Remember to free stuff that is allocated here
+ gboolean local_luminance = FALSE;
+ gboolean local_hires_hist = FALSE;
+
+ printf("toneeq_process, piece type %d, post_align=%d, post_scale=%f, post_shift=%f, roi with=%d, roi_height=%d\n",
+ piece->pipe->type, d->post_auto_align, d->post_scale, d->post_shift, roi_in->width, roi_in->height);
+ /**************************************************************************
+ * Initialization
+ **************************************************************************/
if(self->dev->gui_attached)
{
// If the module instance has changed order in the pipe, invalidate the caches
if(g->pipe_order != piece->module->iop_order)
{
dt_iop_gui_enter_critical_section(self);
+<<<<<<< HEAD
g->ui_preview_hash = DT_INVALID_HASH;
g->thumb_preview_hash = DT_INVALID_HASH;
+=======
+ g->full_upstream_hash = DT_INVALID_CACHEHASH;
+ g->preview_upstream_hash = DT_INVALID_CACHEHASH;
+>>>>>>> acabda0cbe (2025-04-06 preview version with post-shift, post_scale, auto-align and curve coloring.)
g->pipe_order = piece->module->iop_order;
g->luminance_valid = FALSE;
- g->histogram_valid = FALSE;
+ g->gui_histogram_valid = FALSE;
dt_iop_gui_leave_critical_section(self);
}
- if(piece->pipe->type & DT_DEV_PIXELPIPE_FULL)
+ if(piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW)
{
- // For DT_DEV_PIXELPIPE_FULL, we cache the luminance mask for performance
- // but it's not accessed from GUI
- // no need for threads lock since no other function is writing/reading that buffer
+ // For DT_DEV_PIXELPIPE_PREVIEW, we need to cache the luminace mask
+ // and the hires histogram for the GUI thread.
+ // Locks are required since GUI reads and writes on that buffer.
- // Re-allocate a new buffer if the full preview size has changed
- if(g->full_preview_buf_width != width || g->full_preview_buf_height != height)
+ // TODO MF: Is the above comment correct? Except for gui_cache_init
+ // there seems to be no place where the GUI writes.
+
+ // Re-allocate a new buffer if the thumb preview size has changed
+ dt_iop_gui_enter_critical_section(self);
+ if(g->preview_buf_width != width || g->preview_buf_height != height)
{
- dt_free_align(g->full_preview_buf);
- g->full_preview_buf = dt_alloc_align_float(num_elem);
- g->full_preview_buf_width = width;
- g->full_preview_buf_height = height;
+ dt_free_align(g->preview_buf);
+ g->preview_buf = dt_alloc_align_float(num_elem);
+ g->preview_buf_width = width;
+ g->preview_buf_height = height;
+ g->luminance_valid = FALSE;
}
- luminance = g->full_preview_buf;
- cached = TRUE;
+ luminance = g->preview_buf;
+ hires_histogram = g->hires_histogram;
+
+ dt_iop_gui_leave_critical_section(self);
}
- else if(piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW)
+ else if (piece->pipe->type & DT_DEV_PIXELPIPE_FULL)
{
- // For DT_DEV_PIXELPIPE_PREVIEW, we need to cache it too to
- // compute the full image stats upon user request in GUI threads
- // locks are required since GUI reads and writes on that buffer.
+ // For DT_DEV_PIXELPIPE_FULL, we cache the luminance mask for performance
+ // but it's not accessed from GUI
+ // no need for threads lock since no other function is writing/reading that buffer
+ // This is also used to quickly switch between the mask display and the main view.
- // Re-allocate a new buffer if the thumb preview size has changed
- dt_iop_gui_enter_critical_section(self);
- if(g->thumb_preview_buf_width != width || g->thumb_preview_buf_height != height)
+ // Re-allocate a new buffer if the full preview size has changed
+ if(g->full_buf_width != width || g->full_buf_height != height)
{
- dt_free_align(g->thumb_preview_buf);
- g->thumb_preview_buf = dt_alloc_align_float(num_elem);
- g->thumb_preview_buf_width = width;
- g->thumb_preview_buf_height = height;
- g->luminance_valid = FALSE;
+ dt_free_align(g->full_buf);
+ g->full_buf = dt_alloc_align_float(num_elem);
+ g->full_buf_width = width;
+ g->full_buf_height = height;
}
- luminance = g->thumb_preview_buf;
- cached = TRUE;
+ luminance = g->full_buf;
- dt_iop_gui_leave_critical_section(self);
+ hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
+ local_hires_hist = TRUE;
}
- else // just to please GCC
+ else
{
luminance = dt_alloc_align_float(num_elem);
+ local_luminance = TRUE;
+ hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
+ local_hires_hist = TRUE;
}
-
}
else
{
// no interactive editing/caching : just allocate a local temp buffer
luminance = dt_alloc_align_float(num_elem);
+ local_luminance = TRUE;
+ hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
+ local_hires_hist = TRUE;
}
// Check if the luminance buffer exists
@@ -1092,78 +1355,201 @@ void toneeq_process(dt_iop_module_t *self,
return;
}
- // Compute the luminance mask
- if(cached)
+ // The values from d are set by the user.
+ // If the user requested auto-alignment, these will be changed.
+ float post_scale = d->post_scale;
+ float post_shift = d->post_shift;
+
+ /**************************************************************************
+ * Compute the luminance mask
+ **************************************************************************/
+ if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW))
{
- // caching path : store the luminance mask for GUI access
+ // PREVIEW sees the whole image (at a lower resolution).
+ // PREVIEW needs to store the luminance mask, hires_histogram and deciles
+ // for GUI access.
+ // PREVIEW also computes post_scale and post_shift for FULL.
- if(piece->pipe->type & DT_DEV_PIXELPIPE_FULL)
- {
- dt_hash_t saved_hash;
- hash_set_get(&g->ui_preview_hash, &saved_hash, &self->gui_lock);
+ // We use the upstream hash to check if the upstream pipe has changed,
+ // which requires us to re-compute this pipe's luminance mask.
+ const dt_hash_t current_upstream_hash
+ = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
+
+ dt_iop_gui_enter_critical_section(self);
+ const dt_hash_t saved_upstream_hash = g->preview_upstream_hash;
+ const gboolean luminance_valid = g->luminance_valid;
+ dt_iop_gui_leave_critical_section(self);
+
+ printf("toneeq_process PIXELPIPE_PREVIEW: hash=%ld saved_hash=%ld luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
+ luminance_valid);
+ if(saved_upstream_hash != current_upstream_hash || !luminance_valid)
+ {
+ /* compute only if upstream pipe state has changed */
dt_iop_gui_enter_critical_section(self);
- const gboolean luminance_valid = g->luminance_valid;
- dt_iop_gui_leave_critical_section(self);
+ g->preview_upstream_hash = current_upstream_hash;
+ g->gui_histogram_valid = FALSE;
+ compute_luminance_mask(in, luminance, width, height, d,
+ TRUE, // Also compute an image (not mask!) histogram for coloring the curve
+ g->image_hires_histogram,
+ &g->image_histogram_first_decile, &g->image_histogram_last_decile,
+ piece->pipe->type);
+
+ // Histogram and deciles
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
+ &g->histogram_first_decile, &g->histogram_last_decile,
+ piece->pipe->type);
+
+ // GUI can assume that mask, histogram and deciles are valid
+ g->luminance_valid = TRUE;
+
+ compute_auto_post_scale_shift(&post_scale, &post_shift,
+ d->post_auto_align,
+ g->histogram_first_decile, g->histogram_last_decile,
+ piece->pipe->type);
+
+ // save for FULL
+ g->post_scale_value = post_scale;
+ g->post_shift_value = post_shift;
- if(hash != saved_hash || !luminance_valid)
- {
- /* compute only if upstream pipe state has changed */
- compute_luminance_mask(in, luminance, width, height, d);
- hash_set_get(&hash, &g->ui_preview_hash, &self->gui_lock);
- }
+ dt_iop_gui_leave_critical_section(self);
}
- else if(piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW)
+ else
{
- dt_hash_t saved_hash;
- hash_set_get(&g->thumb_preview_hash, &saved_hash, &self->gui_lock);
+ // No need to re-compute mask, histogram, deciles.
+ // We re-use stored deciles for auto alignment.
+ dt_iop_gui_enter_critical_section(self);
+
+ compute_auto_post_scale_shift(&post_scale, &post_shift,
+ d->post_auto_align,
+ g->histogram_first_decile, g->histogram_last_decile,
+ piece->pipe->type);
+
+ // for FULL
+ g->post_scale_value = post_scale;
+ g->post_shift_value = post_shift;
+
+ dt_iop_gui_leave_critical_section(self);
+ }
+
+ // TODO MF: Not completely sure in which cases this must be called.
+ // Assumption is once per output image change by this module and
+ // only when the GUI is active.
+ dt_dev_pixelpipe_cache_invalidate_later(piece->pipe, self->iop_order);
+
+ // Sync hash to make FULL wait if it needs auto-alignment
+ // TODO MF: Is it necessary to do this in two steps to prevent DT getting stuck in a race condition?
+ const dt_hash_t sync_hash = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL);
+ dt_iop_gui_enter_critical_section(self);
+ g->sync_hash = sync_hash;
+ dt_iop_gui_leave_critical_section(self);
+ }
+ else if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL))
+ {
+ // FULL may only see a part of the image if the user has zoomed in.
+ // We need to compute a luminance mask for this pipe and cache it for
+ // quick reuse (i.e. if the user only changes the curve).
+ // But we can not compute statistics like histograms or deciles here,
+ // so we re-use values that PREVIEW has stored in g.
+
+ // We use the upstream hash to check if the upstream pipe has changed,
+ // which requires us to re-compute this pipe's luminance mask.
+ // TODO: Is this correct? We take the same hash as in PREVIEW, using self->dev->preview_pipe,
+ // assuming that this can also be used in FULL to detect upstream changes.
+ const dt_hash_t current_upstream_hash
+ = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
+
+ dt_iop_gui_enter_critical_section(self);
+ const dt_hash_t saved_upstream_hash = g->full_upstream_hash;
+ const gboolean luminance_valid = g->luminance_valid;
+ dt_iop_gui_leave_critical_section(self);
+
+ printf("toneeq_process GUI FULL: hash=%ld saved_hash=%ld luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
+ luminance_valid);
+
+ // Re-compute if the upstream state has changed
+ if(current_upstream_hash != saved_upstream_hash || !luminance_valid)
+ {
+ /* compute only if upstream pipe state has changed */
dt_iop_gui_enter_critical_section(self);
- const gboolean luminance_valid = g->luminance_valid;
+ g->full_upstream_hash = current_upstream_hash;
dt_iop_gui_leave_critical_section(self);
- if(saved_hash != hash || !luminance_valid)
- {
- /* compute only if upstream pipe state has changed */
- dt_iop_gui_enter_critical_section(self);
- g->thumb_preview_hash = hash;
- g->histogram_valid = FALSE;
- compute_luminance_mask(in, luminance, width, height, d);
- g->luminance_valid = TRUE;
- dt_iop_gui_leave_critical_section(self);
- dt_dev_pixelpipe_cache_invalidate_later(piece->pipe, self->iop_order);
- }
+ compute_luminance_mask(in, luminance, width, height, d,
+ FALSE, NULL, NULL, NULL,
+ piece->pipe->type);
}
- else // make it dummy-proof
+
+ if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM)
{
- compute_luminance_mask(in, luminance, width, height, d);
+ printf("toneeq_process GUI FULL: waiting for sync\n");
+ // Wait for PREVIEW to calculate automatic post scale/shift
+ if(!dt_dev_sync_pixelpipe_hash(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &self->gui_lock, &g->sync_hash)) {
+ dt_control_log(_("inconsistent output"));
+ printf("toneeq_process GUI FULL: sync failed\n");
+ }
+ else
+ {
+ printf("toneeq_process GUI FULL: synced\n");
+ }
+
+ dt_iop_gui_enter_critical_section(self);
+ post_scale = g->post_scale_value;
+ post_shift = g->post_shift_value;
+ dt_iop_gui_leave_critical_section(self);
}
- }
- else
- {
+ } else {
// no caching path : compute no matter what
- compute_luminance_mask(in, luminance, width, height, d);
+ // TODO MF: Post scale/shift are calculated with local data.
+ // Not guraranteed to be identical to what PREVIEW saw.
+
+ compute_luminance_mask(in, luminance, width, height, d,
+ FALSE, NULL, NULL, NULL,
+ piece->pipe->type);
+
+ float histogram_first_decile, histogram_last_decile;
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
+ &histogram_first_decile, &histogram_last_decile,
+ piece->pipe->type);
+
+ compute_auto_post_scale_shift(&post_scale, &post_shift, d->post_auto_align,
+ histogram_first_decile, histogram_last_decile, piece->pipe->type);
}
- // Display output
- if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL))
+ /**************************************************************************
+ * Display output
+ **************************************************************************/
+ if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) && g->mask_display)
{
- if(g->mask_display)
- {
- display_luminance_mask(in, luminance, out, roi_in, roi_out);
- piece->pipe->mask_display = DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
- }
- else
- apply_toneequalizer(in, luminance, out, roi_in, roi_out, d);
+ display_luminance_mask(in, luminance, out,
+ roi_in, roi_out,
+ post_scale, post_shift,
+ piece->pipe->type);
+ piece->pipe->mask_display = DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
}
else
{
- apply_toneequalizer(in, luminance, out, roi_in, roi_out, d);
+ compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+ post_scale, post_shift,
+ piece->pipe->type);
+ apply_toneequalizer(in, luminance, out,
+ roi_in, roi_out,
+ d, piece->pipe->type);
}
- if(!cached) dt_free_align(luminance);
+ /**************************************************************************
+ * Cleanup
+ **************************************************************************/
+ if(local_luminance) {
+ dt_free_align(luminance);
+ }
+ if(local_hires_hist) {
+ dt_free_align(hires_histogram);
+ }
}
+
void process(dt_iop_module_t *self,
dt_dev_pixelpipe_iop_t *piece,
const void *const restrict ivoid,
@@ -1175,6 +1561,48 @@ void process(dt_iop_module_t *self,
}
+/****************************************************************************
+ *
+ * Initialization and Cleanup
+ *
+ ****************************************************************************/
+
+void init_global(dt_iop_module_so_t *self)
+{
+ printf("toneequalizer init_global\n");
+ dt_iop_toneequalizer_global_data_t *gd = malloc(sizeof(dt_iop_toneequalizer_global_data_t));
+
+ self->data = gd;
+}
+
+
+void cleanup_global(dt_iop_module_so_t *self)
+{
+ printf("toneequalizer cleanup_global\n");
+ free(self->data);
+ self->data = NULL;
+}
+
+
+void init_pipe(dt_iop_module_t *self,
+ dt_dev_pixelpipe_t *pipe,
+ dt_dev_pixelpipe_iop_t *piece)
+{
+ printf("toneequalizer init_pipe, pipe %d\n", pipe->type);
+ piece->data = dt_calloc1_align_type(dt_iop_toneequalizer_data_t);
+}
+
+
+void cleanup_pipe(dt_iop_module_t *self,
+ dt_dev_pixelpipe_t *pipe,
+ dt_dev_pixelpipe_iop_t *piece)
+{
+ printf("toneequalizer cleanup_pipe, pipe %d\n", pipe->type);
+ dt_free_align(piece->data);
+ piece->data = NULL;
+}
+
+
void modify_roi_in(dt_iop_module_t *self,
dt_dev_pixelpipe_iop_t *piece,
const dt_iop_roi_t *roi_out,
@@ -1196,9 +1624,9 @@ void modify_roi_in(dt_iop_module_t *self,
/***
* Setters and Getters for parameters
*
- * Remember the user params split the [-8; 0] EV range in 9 channels
+ * Remember the user params split the [-8; 0] EV range in 9 NUM_SLIDERS
* and define a set of (x, y) coordinates, where x are the exposure
- * channels (evenly-spaced by 1 EV in [-8; 0] EV) and y are the
+ * NUM_SLIDERS (evenly-spaced by 1 EV in [-8; 0] EV) and y are the
* desired exposure compensation for each channel.
*
* This (x, y) set is interpolated by radial-basis function using a
@@ -1220,34 +1648,12 @@ void modify_roi_in(dt_iop_module_t *self,
* should be used in combination with a tone curve or filmic.
*
***/
-
-static void compute_correction_lut(float* restrict lut,
- const float sigma,
- const float *const restrict factors)
-{
- const float gauss_denom = gaussian_denom(sigma);
- assert(PIXEL_CHAN == 8);
-
- DT_OMP_FOR(shared(centers_ops))
- for(int j = 0; j <= LUT_RESOLUTION * PIXEL_CHAN; j++)
- {
- // build the correction for each pixel
- // as the sum of the contribution of each luminance channelcorrection
- const float exposure = (float)j / (float)LUT_RESOLUTION + DT_TONEEQ_MIN_EV;
- float result = 0.0f;
- for(int i = 0; i < PIXEL_CHAN; i++)
- result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
- // the user-set correction is expected in [-2;+2] EV, so is the interpolated one
- lut[j] = fast_clamp(result, 0.25f, 4.0f);
- }
-}
-
-static void get_channels_gains(float factors[CHANNELS],
+static void get_channels_gains(float factors[NUM_SLIDERS],
const dt_iop_toneequalizer_params_t *p)
{
- assert(CHANNELS == 9);
+ assert(NUM_SLIDERS == 9);
- // Get user-set channels gains in EV (log2)
+ // Get user-set NUM_SLIDERS gains in EV (log2)
factors[0] = p->noise; // -8 EV
factors[1] = p->ultra_deep_blacks; // -7 EV
factors[2] = p->deep_blacks; // -6 EV
@@ -1260,36 +1666,55 @@ static void get_channels_gains(float factors[CHANNELS],
}
-static void get_channels_factors(float factors[CHANNELS],
+static void get_channels_factors(float factors[NUM_SLIDERS],
const dt_iop_toneequalizer_params_t *p)
{
- assert(CHANNELS == 9);
+ assert(NUM_SLIDERS == 9);
- // Get user-set channels gains in EV (log2)
+ // Get user-set NUM_SLIDERS gains in EV (log2)
get_channels_gains(factors, p);
// Convert from EV offsets to linear factors
DT_OMP_SIMD(aligned(factors:64))
- for(int c = 0; c < CHANNELS; ++c)
+ for(int c = 0; c < NUM_SLIDERS; ++c)
factors[c] = exp2f(factors[c]);
}
__DT_CLONE_TARGETS__
-static gboolean compute_channels_factors(const float factors[PIXEL_CHAN],
- float out[CHANNELS],
+static inline float pixel_correction(const float exposure,
+ const float *const restrict factors,
+ const float sigma)
+{
+ // build the correction for the current pixel
+ // as the sum of the contribution of each luminance channel
+ float result = 0.0f;
+ const float gauss_denom = gaussian_denom(sigma);
+ const float expo = fast_clamp(exposure, DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+
+ DT_OMP_SIMD(aligned(centers_ops, factors:64) safelen(NUM_OCTAVES) reduction(+:result))
+ for(int i = 0; i < NUM_OCTAVES; ++i)
+ result += gaussian_func(expo - centers_ops[i], gauss_denom) * factors[i];
+
+ return fast_clamp(result, 0.25f, 4.0f);
+}
+
+
+__DT_CLONE_TARGETS__
+static gboolean compute_channels_factors(const float factors[NUM_OCTAVES],
+ float out[NUM_SLIDERS],
const float sigma)
{
// Input factors are the weights for the radial-basis curve
// approximation of user params Output factors are the gains of the
- // user parameters channels aka the y coordinates of the
- // approximation for x = { CHANNELS }
- assert(PIXEL_CHAN == 8);
+ // user parameters NUM_SLIDERS aka the y coordinates of the
+ // approximation for x = { NUM_SLIDERS }
+ assert(NUM_OCTAVES == 8);
DT_OMP_FOR_SIMD(aligned(factors, out, centers_params:64) firstprivate(centers_params))
- for(int i = 0; i < CHANNELS; ++i)
+ for(int i = 0; i < NUM_SLIDERS; ++i)
{
- // Compute the new channels factors; pixel_correction clamps the factors, so we don't
+ // Compute the new NUM_SLIDERS factors; pixel_correction clamps the factors, so we don't
// need to check for validity here
out[i] = pixel_correction(centers_params[i], factors, sigma);
}
@@ -1298,18 +1723,18 @@ static gboolean compute_channels_factors(const float factors[PIXEL_CHAN],
__DT_CLONE_TARGETS__
-static void compute_channels_gains(const float in[CHANNELS],
- float out[CHANNELS])
+static void compute_channels_gains(const float in[NUM_SLIDERS],
+ float out[NUM_SLIDERS])
{
- // Helper function to compute the new channels gains (log) from the factors (linear)
- assert(PIXEL_CHAN == 8);
+ // Helper function to compute the new NUM_SLIDERS gains (log) from the factors (linear)
+ assert(NUM_OCTAVES == 8);
- for(int i = 0; i < CHANNELS; ++i)
+ for(int i = 0; i < NUM_SLIDERS; ++i)
out[i] = log2f(in[i]);
}
-static void commit_channels_gains(const float factors[CHANNELS],
+static void commit_channels_gains(const float factors[NUM_SLIDERS],
dt_iop_toneequalizer_params_t *p)
{
p->noise = factors[0];
@@ -1324,28 +1749,35 @@ static void commit_channels_gains(const float factors[CHANNELS],
}
-/***
- * Cache invalidation and initializatiom
- ***/
-
-
+/****************************************************************************
+ *
+ * Cache invalidation and initialization
+ *
+ ****************************************************************************/
static void gui_cache_init(dt_iop_module_t *self)
{
+ printf("gui_cache_init\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
dt_iop_gui_enter_critical_section(self);
+<<<<<<< HEAD
g->ui_preview_hash = DT_INVALID_HASH;
g->thumb_preview_hash = DT_INVALID_HASH;
+=======
+ g->full_upstream_hash = DT_INVALID_CACHEHASH;
+ g->preview_upstream_hash = DT_INVALID_CACHEHASH;
+>>>>>>> acabda0cbe (2025-04-06 preview version with post-shift, post_scale, auto-align and curve coloring.)
g->max_histogram = 1;
g->scale = 1.0f;
g->sigma = sqrtf(2.0f);
g->mask_display = FALSE;
+ g->image_EV_per_UI_sample = 0.00001; // In case no value is calculated yet, use something small, but not 0
g->interpolation_valid = FALSE; // TRUE if the interpolation_matrix is ready
g->luminance_valid = FALSE; // TRUE if the luminance cache is ready
- g->histogram_valid = FALSE; // TRUE if the histogram cache and stats are ready
- g->lut_valid = FALSE; // TRUE if the gui_lut is ready
+ g->gui_histogram_valid = FALSE; // TRUE if the histogram cache and stats are ready
+ g->gui_curve_valid = FALSE; // TRUE if the gui_curve_lut is ready
g->graph_valid = FALSE; // TRUE if the UI graph view is ready
g->user_param_valid = FALSE; // TRUE if users params set in interactive view are in bounds
g->factors_valid = TRUE; // TRUE if radial-basis coeffs are ready
@@ -1357,13 +1789,13 @@ static void gui_cache_init(dt_iop_module_t *self)
g->cursor_valid = FALSE; // TRUE if mouse cursor is over the preview image
g->has_focus = FALSE; // TRUE if module has focus from GTK
- g->full_preview_buf = NULL;
- g->full_preview_buf_width = 0;
- g->full_preview_buf_height = 0;
+ g->preview_buf = NULL;
+ g->preview_buf_width = 0;
+ g->preview_buf_height = 0;
- g->thumb_preview_buf = NULL;
- g->thumb_preview_buf_width = 0;
- g->thumb_preview_buf_height = 0;
+ g->full_buf = NULL;
+ g->full_buf_width = 0;
+ g->full_buf_height = 0;
g->desc = NULL;
g->layout = NULL;
@@ -1376,144 +1808,85 @@ static void gui_cache_init(dt_iop_module_t *self)
}
-static inline void build_interpolation_matrix(float A[CHANNELS * PIXEL_CHAN],
- const float sigma)
+static void invalidate_luminance_cache(dt_iop_module_t *const self)
{
- // Build the symmetrical definite positive part of the augmented matrix
- // of the radial-basis interpolation weights
+ printf("invalidate_luminance_cache\n");
+ // Invalidate the private luminance cache and histogram when
+ // the luminance mask extraction parameters have changed
+ dt_iop_toneequalizer_gui_data_t *const restrict g = self->gui_data;
- const float gauss_denom = gaussian_denom(sigma);
+ dt_iop_gui_enter_critical_section(self);
+ g->luminance_valid = FALSE;
+ g->preview_upstream_hash = DT_INVALID_CACHEHASH;
+ g->full_upstream_hash = DT_INVALID_CACHEHASH;
- DT_OMP_SIMD(aligned(A, centers_ops, centers_params:64) collapse(2))
- for(int i = 0; i < CHANNELS; ++i)
- for(int j = 0; j < PIXEL_CHAN; ++j)
- A[i * PIXEL_CHAN + j] =
- gaussian_func(centers_params[i] - centers_ops[j], gauss_denom);
+ g->gui_histogram_valid = FALSE;
+ g->max_histogram = 1;
+ dt_iop_gui_leave_critical_section(self);
+ dt_iop_refresh_all(self);
}
-__DT_CLONE_TARGETS__
-static inline void compute_log_histogram_and_stats(const float *const restrict luminance,
- int histogram[UI_SAMPLES],
- const size_t num_elem,
- int *max_histogram,
- float *first_decile,
- float *last_decile)
+static void invalidate_lut_and_histogram(dt_iop_module_t *const self)
{
- // (Re)init the histogram
- memset(histogram, 0, sizeof(int) * UI_SAMPLES);
-
- // we first calculate an extended histogram for better accuracy
- #define TEMP_SAMPLES 2 * UI_SAMPLES
- int temp_hist[TEMP_SAMPLES];
- memset(temp_hist, 0, sizeof(int) * TEMP_SAMPLES);
-
- // Split exposure in bins
- DT_OMP_FOR_SIMD(reduction(+:temp_hist[:TEMP_SAMPLES]))
- for(size_t k = 0; k < num_elem; k++)
- {
- // extended histogram bins between [-10; +6] EV remapped between [0 ; 2 * UI_SAMPLES]
- const int index =
- CLAMP((int)(((log2f(luminance[k]) + 10.0f) / 16.0f) * (float)TEMP_SAMPLES),
- 0, TEMP_SAMPLES - 1);
- temp_hist[index] += 1;
- }
-
- const int first = (int)((float)num_elem * 0.05f);
- const int last = (int)((float)num_elem * (1.0f - 0.95f));
- int population = 0;
- int first_pos = 0;
- int last_pos = 0;
-
- // scout the extended histogram bins looking for deciles
- // these would not be accurate with the regular histogram
- for(int k = 0; k < TEMP_SAMPLES; ++k)
- {
- const size_t prev_population = population;
- population += temp_hist[k];
- if(prev_population < first && first <= population)
- {
- first_pos = k;
- break;
- }
- }
- population = 0;
- for(int k = TEMP_SAMPLES - 1; k >= 0; --k)
- {
- const size_t prev_population = population;
- population += temp_hist[k];
- if(prev_population < last && last <= population)
- {
- last_pos = k;
- break;
- }
- }
-
- // Convert decile positions to exposures
- *first_decile = 16.0 * (float)first_pos / (float)(TEMP_SAMPLES - 1) - 10.0;
- *last_decile = 16.0 * (float)last_pos / (float)(TEMP_SAMPLES - 1) - 10.0;
-
- // remap the extended histogram into the normal one
- // bins between [-8; 0] EV remapped between [0 ; UI_SAMPLES]
- for(size_t k = 0; k < TEMP_SAMPLES; ++k)
- {
- float EV = 16.0 * (float)k / (float)(TEMP_SAMPLES - 1) - 10.0;
- const int i =
- CLAMP((int)(((EV + 8.0f) / 8.0f) * (float)UI_SAMPLES),
- 0, UI_SAMPLES - 1);
- histogram[i] += temp_hist[k];
+ printf("invalidate_lut_and_histogram\n");
+ dt_iop_toneequalizer_gui_data_t *const restrict g = self->gui_data;
- // store the max numbers of elements in bins for later normalization
- *max_histogram = histogram[i] > *max_histogram ? histogram[i] : *max_histogram;
- }
+ dt_iop_gui_enter_critical_section(self);
+ g->gui_curve_valid = FALSE;
+ g->gui_histogram_valid = FALSE;
+ g->max_histogram = 1;
+ dt_iop_gui_leave_critical_section(self);
+ dt_iop_refresh_all(self);
}
-static inline void update_histogram(dt_iop_module_t *const self)
+
+/****************************************************************************
+ *
+ * Curve Interpolation
+ *
+ ****************************************************************************/
+static inline void build_interpolation_matrix(float A[NUM_SLIDERS * NUM_OCTAVES],
+ const float sigma)
{
- dt_iop_toneequalizer_gui_data_t *const g = self->gui_data;
- if(g == NULL) return;
+ // Build the symmetrical definite positive part of the augmented matrix
+ // of the radial-basis interpolation weights
- dt_iop_gui_enter_critical_section(self);
- if(!g->histogram_valid && g->luminance_valid)
- {
- const size_t num_elem = g->thumb_preview_buf_height * g->thumb_preview_buf_width;
- compute_log_histogram_and_stats(g->thumb_preview_buf, g->histogram, num_elem,
- &g->max_histogram,
- &g->histogram_first_decile, &g->histogram_last_decile);
- g->histogram_average = (g->histogram_first_decile + g->histogram_last_decile) / 2.0f;
- g->histogram_valid = TRUE;
- }
- dt_iop_gui_leave_critical_section(self);
+ const float gauss_denom = gaussian_denom(sigma);
+
+ DT_OMP_SIMD(aligned(A, centers_ops, centers_params:64) collapse(2))
+ for(int i = 0; i < NUM_SLIDERS; ++i)
+ for(int j = 0; j < NUM_OCTAVES; ++j)
+ A[i * NUM_OCTAVES + j] =
+ gaussian_func(centers_params[i] - centers_ops[j], gauss_denom);
}
__DT_CLONE_TARGETS__
-static inline void compute_lut_correction(dt_iop_toneequalizer_gui_data_t *g,
- const float offset,
- const float scaling)
+static inline void compute_gui_curve(dt_iop_toneequalizer_gui_data_t *g)
{
- // Compute the LUT of the exposure corrections in EV,
+ // Compute the curve of the exposure corrections in EV,
// offset and scale it for display in GUI widget graph
if(g == NULL) return;
- float *const restrict LUT = g->gui_lut;
+ float *const restrict curve = g->gui_curve;
const float *const restrict factors = g->factors;
const float sigma = g->sigma;
- DT_OMP_FOR_SIMD(aligned(LUT, factors:64))
- for(int k = 0; k < UI_SAMPLES; k++)
+ DT_OMP_FOR_SIMD(aligned(curve, factors:64))
+ for(int k = 0; k < UI_HISTO_SAMPLES; k++)
{
// build the inset graph curve LUT
// the x range is [-14;+2] EV
- const float x = (8.0f * (((float)k) / ((float)(UI_SAMPLES - 1)))) - 8.0f;
- LUT[k] = offset - log2f(pixel_correction(x, factors, sigma)) / scaling;
+ const float x = (8.0f * (((float)k) / ((float)(UI_HISTO_SAMPLES - 1)))) - 8.0f;
+ // curve[k] = offset - log2f(pixel_correction(x, factors, sigma)) / scaling;
+ curve[k] = log2f(pixel_correction(x, factors, sigma));
}
}
-
-static inline gboolean update_curve_lut(dt_iop_module_t *self)
+static inline gboolean curve_interpolation(dt_iop_module_t *self)
{
dt_iop_toneequalizer_params_t *p = self->params;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -1533,28 +1906,28 @@ static inline gboolean update_curve_lut(dt_iop_module_t *self)
if(!g->user_param_valid)
{
- float factors[CHANNELS] DT_ALIGNED_ARRAY;
+ float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
get_channels_factors(factors, p);
- dt_simd_memcpy(factors, g->temp_user_params, CHANNELS);
+ dt_simd_memcpy(factors, g->temp_user_params, NUM_SLIDERS);
g->user_param_valid = TRUE;
g->factors_valid = FALSE;
}
if(!g->factors_valid && g->user_param_valid)
{
- float factors[CHANNELS] DT_ALIGNED_ARRAY;
- dt_simd_memcpy(g->temp_user_params, factors, CHANNELS);
- valid = pseudo_solve(g->interpolation_matrix, factors, CHANNELS, PIXEL_CHAN, TRUE);
- if(valid) dt_simd_memcpy(factors, g->factors, PIXEL_CHAN);
+ float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
+ dt_simd_memcpy(g->temp_user_params, factors, NUM_SLIDERS);
+ valid = pseudo_solve(g->interpolation_matrix, factors, NUM_SLIDERS, NUM_OCTAVES, TRUE);
+ if(valid) dt_simd_memcpy(factors, g->factors, NUM_OCTAVES);
else dt_print(DT_DEBUG_PIPE, "tone equalizer pseudo solve problem");
g->factors_valid = TRUE;
- g->lut_valid = FALSE;
+ g->gui_curve_valid = FALSE;
}
- if(!g->lut_valid && g->factors_valid)
+ if(!g->gui_curve_valid && g->factors_valid)
{
- compute_lut_correction(g, 0.5f, 4.0f);
- g->lut_valid = TRUE;
+ compute_gui_curve(g);
+ g->gui_curve_valid = TRUE;
}
dt_iop_gui_leave_critical_section(self);
@@ -1563,33 +1936,25 @@ static inline gboolean update_curve_lut(dt_iop_module_t *self)
}
-void init_global(dt_iop_module_so_t *self)
-{
- dt_iop_toneequalizer_global_data_t *gd = malloc(sizeof(dt_iop_toneequalizer_global_data_t));
-
- self->data = gd;
-}
-
-
-void cleanup_global(dt_iop_module_so_t *self)
-{
- free(self->data);
- self->data = NULL;
-}
-
-
-void commit_params(dt_iop_module_t *self,
- dt_iop_params_t *p1,
- dt_dev_pixelpipe_t *pipe,
+/****************************************************************************
+ *
+ * Commit Params
+ *
+ ****************************************************************************/
+void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe,
dt_dev_pixelpipe_iop_t *piece)
{
+
dt_iop_toneequalizer_params_t *p = (dt_iop_toneequalizer_params_t *)p1;
dt_iop_toneequalizer_data_t *d = piece->data;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ printf("commit_params pipe=%d align=%d p->post_scale=%f p->post_shift=%f d->post_scale=%f d->post_shift=%f\n",
+ pipe->type, p->post_auto_align, p->post_scale, p->post_shift, d->post_scale, d->post_shift);
+
// Trivial params passing
- d->method = p->method;
- d->details = p->details;
+ d->lum_estimator = p->lum_estimator;
+ d->filter = p->filter;
d->iterations = p->iterations;
d->smoothing = p->smoothing;
d->quantization = p->quantization;
@@ -1605,65 +1970,129 @@ void commit_params(dt_iop_module_t *self,
d->contrast_boost = exp2f(p->contrast_boost);
d->exposure_boost = exp2f(p->exposure_boost);
+ // printf("commit_params: post_auto_align %d\n", p->post_auto_align);
+ d->post_auto_align = p->post_auto_align;
+ d->post_scale = p->post_scale;
+ d->post_shift = p->post_shift;
+
/*
* Perform a radial-based interpolation using a series gaussian functions
*/
if(self->dev->gui_attached && g)
{
dt_iop_gui_enter_critical_section(self);
- if(g->sigma != p->smoothing)
- g->interpolation_valid = FALSE;
+
+ if(g->sigma != p->smoothing) g->interpolation_valid = FALSE;
g->sigma = p->smoothing;
- g->user_param_valid = FALSE; // force updating channels factors
+ g->user_param_valid = FALSE; // force updating NUM_SLIDERS factors
dt_iop_gui_leave_critical_section(self);
- update_curve_lut(self);
+ curve_interpolation(self);
dt_iop_gui_enter_critical_section(self);
- dt_simd_memcpy(g->factors, d->factors, PIXEL_CHAN);
+ dt_simd_memcpy(g->factors, d->factors, NUM_OCTAVES);
dt_iop_gui_leave_critical_section(self);
}
else
{
// No cache : Build / Solve interpolation matrix
- float factors[CHANNELS] DT_ALIGNED_ARRAY;
+ float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
get_channels_factors(factors, p);
- float A[CHANNELS * PIXEL_CHAN] DT_ALIGNED_ARRAY;
+ float A[NUM_SLIDERS * NUM_OCTAVES] DT_ALIGNED_ARRAY;
build_interpolation_matrix(A, p->smoothing);
- pseudo_solve(A, factors, CHANNELS, PIXEL_CHAN, FALSE);
+ pseudo_solve(A, factors, NUM_SLIDERS, NUM_OCTAVES, FALSE);
- dt_simd_memcpy(factors, d->factors, PIXEL_CHAN);
+ dt_simd_memcpy(factors, d->factors, NUM_OCTAVES);
}
-
- // compute the correction LUT here to spare some time in process
- // when computing several times toneequalizer with same parameters
- compute_correction_lut(d->correction_lut, d->smoothing, d->factors);
}
-void init_pipe(dt_iop_module_t *self,
- dt_dev_pixelpipe_t *pipe,
- dt_dev_pixelpipe_iop_t *piece)
+/****************************************************************************
+ *
+ * GUI Helpers
+ *
+ ****************************************************************************/
+static inline void compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLES],
+ int histogram[UI_HISTO_SAMPLES],
+ float histogram_scale,
+ float histogram_shift,
+ int *max_histogram)
{
- piece->data = dt_calloc1_align_type(dt_iop_toneequalizer_data_t);
+ printf("compute_gui_histogram\n");
+ // (Re)init the histogram
+ memset(histogram, 0, sizeof(int) * UI_HISTO_SAMPLES);
+
+ const float temp_ev_range = HIRES_HISTO_MAX_EV - HIRES_HISTO_MIN_EV;
+
+ // remap the extended histogram into the gui histogram
+ // bins between [-8; 0] EV remapped between [0 ; UI_HISTO_SAMPLES]
+ for(size_t k = 0; k < HIRES_HISTO_SAMPLES; ++k)
+ {
+ // from [0...HIRES_HISTO_SAMPLES] to [-16...8EV]
+ float EV = temp_ev_range * (float)k / (float)(HIRES_HISTO_SAMPLES - 1) + HIRES_HISTO_MIN_EV;
+
+ // apply shift & scale to the EV value
+ const float shift_scaled_EV = post_scale_shift(EV, histogram_scale, histogram_shift);
+
+ // from [-8...0] EV to [0...UI_HISTO_SAMPLES]
+ const int i = CLAMP((int)(((shift_scaled_EV + 8.0f) / 8.0f) * (float)UI_HISTO_SAMPLES), 0, UI_HISTO_SAMPLES - 1);
+
+ histogram[i] += hires_histogram[k];
+ }
+
+ // store the max numbers of elements in bins for later normalization
+ // ignore the first and last value to keep the histogram readable
+ *max_histogram = 1; // don't divide by 0 if there are no values at all
+ for (int i = 1; i < UI_HISTO_SAMPLES - 1; i++)
+ {
+ *max_histogram = histogram[i] > *max_histogram ? histogram[i] : *max_histogram;
+ }
}
-void cleanup_pipe(dt_iop_module_t *self,
- dt_dev_pixelpipe_t *pipe,
- dt_dev_pixelpipe_iop_t *piece)
+static inline void update_gui_histogram(dt_iop_module_t *const self)
{
- dt_free_align(piece->data);
- piece->data = NULL;
+ dt_iop_toneequalizer_gui_data_t *const g = self->gui_data;
+ dt_iop_toneequalizer_params_t *const p = self->params;
+ if(g == NULL) return;
+
+ dt_iop_gui_enter_critical_section(self);
+ if(!g->gui_histogram_valid && g->luminance_valid)
+ {
+ compute_auto_post_scale_shift(&p->post_scale, &p->post_shift, p->post_auto_align, g->histogram_first_decile, g->histogram_last_decile, 999);
+ compute_gui_histogram(g->hires_histogram, g->histogram, p->post_scale, p->post_shift, &g->max_histogram);
+
+ // Computation of "image_EV_per_UI_sample"
+ // The graph shows 8EV, but when we align the histogram, we consider 6EV [-7; -1] ("target")
+ const float target_EV_range = 6.0f;
+ const float full_EV_range = 8.0f;
+ const float target_to_full = full_EV_range / target_EV_range;
+
+ // What is the real dynamic range of the histogram-part [-7; -1])? We unscale.
+ const float mask_EV_of_target = target_EV_range / exp2f(p->post_scale);
+
+ // The histogram shows mask EV, but for evaluating curve steepness, we need image EVs
+ const float mask_to_image = (g->image_histogram_last_decile - g->image_histogram_first_decile)
+ / (g->histogram_last_decile - g->histogram_first_decile);
+
+ g->image_EV_per_UI_sample = (mask_EV_of_target * mask_to_image * target_to_full) / (float)UI_HISTO_SAMPLES;
+
+ if (g->show_two_histograms)
+ compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->max_image_histogram);
+
+ g->gui_histogram_valid = TRUE;
+ }
+ dt_iop_gui_leave_critical_section(self);
}
+
static void show_guiding_controls(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
const dt_iop_toneequalizer_params_t *p = self->params;
- switch(p->details)
+ switch(p->filter)
{
case(DT_TONEEQ_NONE):
{
@@ -1697,43 +2126,62 @@ static void show_guiding_controls(dt_iop_module_t *self)
break;
}
}
-}
-void update_exposure_sliders(dt_iop_toneequalizer_gui_data_t *g,
- dt_iop_toneequalizer_params_t *p)
-{
- ++darktable.gui->reset;
- dt_bauhaus_slider_set(g->noise, p->noise);
- dt_bauhaus_slider_set(g->ultra_deep_blacks, p->ultra_deep_blacks);
- dt_bauhaus_slider_set(g->deep_blacks, p->deep_blacks);
- dt_bauhaus_slider_set(g->blacks, p->blacks);
- dt_bauhaus_slider_set(g->shadows, p->shadows);
- dt_bauhaus_slider_set(g->midtones, p->midtones);
- dt_bauhaus_slider_set(g->highlights, p->highlights);
- dt_bauhaus_slider_set(g->whites, p->whites);
- dt_bauhaus_slider_set(g->speculars, p->speculars);
- --darktable.gui->reset;
+ switch(p->post_auto_align) {
+ case(DT_TONEEQ_ALIGN_LEFT):
+ case(DT_TONEEQ_ALIGN_CENTER):
+ case(DT_TONEEQ_ALIGN_RIGHT):
+ {
+ gtk_widget_set_visible(g->post_scale, TRUE);
+ gtk_widget_set_visible(g->post_shift, FALSE);
+ break;
+ }
+ case(DT_TONEEQ_ALIGN_FIT):
+ {
+ gtk_widget_set_visible(g->post_scale, FALSE);
+ gtk_widget_set_visible(g->post_shift, FALSE);
+ break;
+ }
+ case(DT_TONEEQ_ALIGN_CUSTOM):
+ {
+ gtk_widget_set_visible(g->post_scale, TRUE);
+ gtk_widget_set_visible(g->post_shift, TRUE);
+ break;
+ }
+ }
}
+/****************************************************************************
+ *
+ * GUI Callbacks
+ *
+ ****************************************************************************/
void gui_update(dt_iop_module_t *self)
{
+ printf("gui_update\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_toneequalizer_params_t *p = self->params;
dt_bauhaus_slider_set(g->smoothing, logf(p->smoothing) / logf(sqrtf(2.0f)) - 1.0f);
show_guiding_controls(self);
+
invalidate_luminance_cache(self);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
+
}
+
void gui_changed(dt_iop_module_t *self,
GtkWidget *w,
void *previous)
{
+ printf("gui_changed\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ dt_iop_toneequalizer_params_t *p = self->params;
if(w == g->method
|| w == g->blending
@@ -1754,8 +2202,26 @@ void gui_changed(dt_iop_module_t *self,
invalidate_luminance_cache(self);
dt_bauhaus_widget_set_quad_active(w, FALSE);
}
+ else if (w == g->post_scale
+ || w == g->post_shift)
+ {
+ invalidate_lut_and_histogram(self);
+ }
+ else if (w == g->post_auto_align)
+ {
+ // We may have switched from a more automatic to a less automatic mode.
+ // Copy the automatically determined parameters to the GUI sliders.
+ ++darktable.gui->reset;
+ dt_bauhaus_slider_set(g->post_scale, p->post_scale);
+ dt_bauhaus_slider_set(g->post_shift, p->post_shift);
+ --darktable.gui->reset;
+
+ invalidate_lut_and_histogram(self);
+ show_guiding_controls(self);
+ }
}
+
static void smoothing_callback(GtkWidget *slider, dt_iop_module_t *self)
{
if(darktable.gui->reset) return;
@@ -1764,11 +2230,11 @@ static void smoothing_callback(GtkWidget *slider, dt_iop_module_t *self)
p->smoothing= powf(sqrtf(2.0f), 1.0f + dt_bauhaus_slider_get(slider));
- float factors[CHANNELS] DT_ALIGNED_ARRAY;
+ float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
get_channels_factors(factors, p);
// Solve the interpolation by least-squares to check the validity of the smoothing param
- if(!update_curve_lut(self))
+ if(!curve_interpolation(self))
dt_control_log
(_("the interpolation is unstable, decrease the curve smoothing"));
@@ -1781,6 +2247,7 @@ static void smoothing_callback(GtkWidget *slider, dt_iop_module_t *self)
dt_iop_color_picker_reset(self, TRUE);
}
+
static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
{
dt_iop_toneequalizer_params_t *p = self->params;
@@ -1802,7 +2269,7 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
return;
}
- if(!g->luminance_valid || self->dev->full.pipe->processing || !g->histogram_valid)
+ if(!g->luminance_valid || self->dev->full.pipe->processing || !g->gui_histogram_valid)
{
dt_control_log(_("wait for the preview to finish recomputing"));
return;
@@ -1812,12 +2279,11 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
// to spread it over as many nodes as possible for better exposure control.
// Controls nodes are between -8 and 0 EV,
// so we aim at centering the exposure distribution on -4 EV
-
dt_iop_gui_enter_critical_section(self);
- g->histogram_valid = FALSE;
+ g->gui_histogram_valid = FALSE;
dt_iop_gui_leave_critical_section(self);
- update_histogram(self);
+ update_gui_histogram(self);
// calculate exposure correction
const float fd_new = exp2f(g->histogram_first_decile);
@@ -1868,98 +2334,237 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
return;
}
- if(!g->luminance_valid || self->dev->full.pipe->processing || !g->histogram_valid)
+ if(!g->luminance_valid || self->dev->full.pipe->processing || !g->gui_histogram_valid)
+ {
+ dt_control_log(_("wait for the preview to finish recomputing"));
+ return;
+ }
+
+ // The goal is to spread 90 % of the exposure histogram in the [-7, -1] EV
+ dt_iop_gui_enter_critical_section(self);
+ g->gui_histogram_valid = FALSE;
+ dt_iop_gui_leave_critical_section(self);
+
+ update_gui_histogram(self);
+
+ // calculate contrast correction
+ const float fd_new = exp2f(g->histogram_first_decile);
+ const float ld_new = exp2f(g->histogram_last_decile);
+ const float e = exp2f(p->exposure_boost);
+ float c = exp2f(p->contrast_boost);
+ // revert current transformation
+ const float fd_old = ((fd_new - CONTRAST_FULCRUM) / c + CONTRAST_FULCRUM) / e;
+ const float ld_old = ((ld_new - CONTRAST_FULCRUM) / c + CONTRAST_FULCRUM) / e;
+
+ // calculate correction
+ const float s1 = CONTRAST_FULCRUM - exp2f(-7.0);
+ const float s2 = exp2f(-1.0) - CONTRAST_FULCRUM;
+ const float mix = fd_old * s2 + ld_old * s1;
+
+ c = log2f(mix / (CONTRAST_FULCRUM * (ld_old - fd_old)) / c);
+
+ // when adding contrast, blur filters modify the histogram in a way
+ // difficult to predict here we implement a heuristic correction
+ // based on a set of images and regression analysis
+ if(p->filter == DT_TONEEQ_EIGF && c > 0.0f)
+ {
+ const float correction = -0.0276f + 0.01823 * p->feathering + (0.7566f - 1.0f) * c;
+ if(p->feathering < 5.0f)
+ c += correction;
+ else if(p->feathering < 10.0f)
+ c += correction * (2.0f - p->feathering / 5.0f);
+ }
+ else if(p->filter == DT_TONEEQ_GUIDED && c > 0.0f)
+ c = 0.0235f + 1.1225f * c;
+
+ p->contrast_boost += c;
+
+ // Update the GUI stuff
+ ++darktable.gui->reset;
+ dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
+ --darktable.gui->reset;
+ invalidate_luminance_cache(self);
+ dt_dev_add_history_item(darktable.develop, self, TRUE);
+
+ // Unlock the colour picker so we can display our own custom cursor
+ dt_iop_color_picker_reset(self, TRUE);
+}
+
+
+static void show_luminance_mask_callback(GtkWidget *togglebutton,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
+{
+ if(darktable.gui->reset) return;
+ dt_iop_request_focus(self);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
+
+ dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+
+ // if blend module is displaying mask do not display it here
+ if(self->request_mask_display)
+ {
+ dt_control_log(_("cannot display masks when the blending mask is displayed"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), FALSE);
+ g->mask_display = FALSE;
+ return;
+ }
+ else
+ g->mask_display = !g->mask_display;
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
+// dt_dev_reprocess_center(self->dev);
+ dt_iop_refresh_center(self);
+
+ // Unlock the colour picker so we can display our own custom cursor
+ dt_iop_color_picker_reset(self, TRUE);
+}
+
+
+// TODO MF: Remove this again? Two histograms are only useful for debugging.
+static void show_two_histograms_callback(GtkWidget *togglebutton,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
+{
+ if(darktable.gui->reset) return;
+ dt_iop_request_focus(self);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
+
+ dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ g->two_histograms_display = !g->two_histograms_display;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
+
+ // dt_dev_reprocess_center(self->dev);
+ dt_iop_refresh_center(self); // TODO: is this needed?
+ // Unlock the colour picker so we can display our own custom cursor
+ // dt_iop_color_picker_reset(self, TRUE);
+}
+
+
+/****************************************************************************
+ *
+ * GUI Interactivity
+ *
+ ****************************************************************************/
+static void _get_point(dt_iop_module_t *self, const int c_x, const int c_y, int *x, int *y)
+{
+ // TODO: For this to fully work non depending on the place of the module
+ // in the pipe we need a dt_dev_distort_backtransform_plus that
+ // can skip crop only. With the current version if toneequalizer
+ // is moved below rotation & perspective it will fail as we are
+ // then missing all the transform after tone-eq.
+ const double crop_order = dt_ioppr_get_iop_order(self->dev->iop_order_list, "crop", 0);
+
+ float pts[2] = { c_x, c_y };
+
+ // only a forward backtransform as the buffer already contains all the transforms
+ // done before toneequal and we are speaking of on-screen cursor coordinates.
+ // also we do transform only after crop as crop does change roi for the whole pipe
+ // and so it is already part of the preview buffer cached in this implementation.
+ dt_dev_distort_backtransform_plus(darktable.develop, darktable.develop->preview_pipe, crop_order,
+ DT_DEV_TRANSFORM_DIR_FORW_EXCL, pts, 1);
+ *x = pts[0];
+ *y = pts[1];
+}
+
+
+__DT_CLONE_TARGETS__
+static float get_luminance_from_buffer(const float *const buffer,
+ const size_t width,
+ const size_t height,
+ const size_t x,
+ const size_t y)
+{
+ // Get the weighted average luminance of the 3×3 pixels region centered in (x, y)
+ // x and y are ratios in [0, 1] of the width and height
+
+ if(y >= height || x >= width) return NAN;
+
+ const size_t y_abs[4] DT_ALIGNED_PIXEL =
+ { MAX(y, 1) - 1, // previous line
+ y, // center line
+ MIN(y + 1, height - 1), // next line
+ y }; // padding for vectorization
+
+ float luminance = 0.0f;
+ if(x > 1 && x < width - 2)
+ {
+ // no clamping needed on x, which allows us to vectorize
+ // apply the convolution
+ for(int i = 0; i < 3; ++i)
+ {
+ const size_t y_i = y_abs[i];
+ for_each_channel(j)
+ luminance += buffer[width * y_i + x-1 + j] * gauss_kernel[i][j];
+ }
+ return luminance;
+ }
+
+ const size_t x_abs[4] DT_ALIGNED_PIXEL =
+ { MAX(x, 1) - 1, // previous column
+ x, // center column
+ MIN(x + 1, width - 1), // next column
+ x }; // padding for vectorization
+
+ // convolution
+ for(int i = 0; i < 3; ++i)
{
- dt_control_log(_("wait for the preview to finish recomputing"));
- return;
+ const size_t y_i = y_abs[i];
+ for_each_channel(j)
+ luminance += buffer[width * y_i + x_abs[j]] * gauss_kernel[i][j];
}
+ return luminance;
+}
- // The goal is to spread 90 % of the exposure histogram in the [-7, -1] EV
- dt_iop_gui_enter_critical_section(self);
- g->histogram_valid = FALSE;
- dt_iop_gui_leave_critical_section(self);
- update_histogram(self);
+// unify with get_luminance_from_buffer
+static float _luminance_from_thumb_preview_buf(dt_iop_module_t *self)
+{
+ dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
- // calculate contrast correction
- const float fd_new = exp2f(g->histogram_first_decile);
- const float ld_new = exp2f(g->histogram_last_decile);
- const float e = exp2f(p->exposure_boost);
- float c = exp2f(p->contrast_boost);
- // revert current transformation
- const float fd_old = ((fd_new - CONTRAST_FULCRUM) / c + CONTRAST_FULCRUM) / e;
- const float ld_old = ((ld_new - CONTRAST_FULCRUM) / c + CONTRAST_FULCRUM) / e;
+ const size_t c_x = g->cursor_pos_x;
+ const size_t c_y = g->cursor_pos_y;
- // calculate correction
- const float s1 = CONTRAST_FULCRUM - exp2f(-7.0);
- const float s2 = exp2f(-1.0) - CONTRAST_FULCRUM;
- const float mix = fd_old * s2 + ld_old * s1;
+ // get buffer x,y given the cursor position
+ int b_x = 0;
+ int b_y = 0;
- c = log2f(mix / (CONTRAST_FULCRUM * (ld_old - fd_old)) / c);
+ _get_point(self, c_x, c_y, &b_x, &b_y);
- // when adding contrast, blur filters modify the histogram in a way
- // difficult to predict here we implement a heuristic correction
- // based on a set of images and regression analysis
- if(p->details == DT_TONEEQ_EIGF && c > 0.0f)
- {
- const float correction = -0.0276f + 0.01823 * p->feathering + (0.7566f - 1.0f) * c;
- if(p->feathering < 5.0f)
- c += correction;
- else if(p->feathering < 10.0f)
- c += correction * (2.0f - p->feathering / 5.0f);
- }
- else if(p->details == DT_TONEEQ_GUIDED && c > 0.0f)
- c = 0.0235f + 1.1225f * c;
+ return get_luminance_from_buffer(g->preview_buf,
+ g->preview_buf_width,
+ g->preview_buf_height,
+ b_x,
+ b_y);
+}
- p->contrast_boost += c;
- // Update the GUI stuff
+void update_exposure_sliders(dt_iop_toneequalizer_gui_data_t *g, dt_iop_toneequalizer_params_t *p)
+{
+ // Params to GUI
++darktable.gui->reset;
- dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
+ dt_bauhaus_slider_set(g->noise, p->noise);
+ dt_bauhaus_slider_set(g->ultra_deep_blacks, p->ultra_deep_blacks);
+ dt_bauhaus_slider_set(g->deep_blacks, p->deep_blacks);
+ dt_bauhaus_slider_set(g->blacks, p->blacks);
+ dt_bauhaus_slider_set(g->shadows, p->shadows);
+ dt_bauhaus_slider_set(g->midtones, p->midtones);
+ dt_bauhaus_slider_set(g->highlights, p->highlights);
+ dt_bauhaus_slider_set(g->whites, p->whites);
+ dt_bauhaus_slider_set(g->speculars, p->speculars);
--darktable.gui->reset;
- invalidate_luminance_cache(self);
- dt_dev_add_history_item(darktable.develop, self, TRUE);
-
- // Unlock the colour picker so we can display our own custom cursor
- dt_iop_color_picker_reset(self, TRUE);
}
-static void show_luminance_mask_callback(GtkWidget *togglebutton,
- GdkEventButton *event,
- dt_iop_module_t *self)
+static gboolean in_mask_editing(dt_iop_module_t *self)
{
- if(darktable.gui->reset) return;
- dt_iop_request_focus(self);
-
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
-
- dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
-
- // if blend module is displaying mask do not display it here
- if(self->request_mask_display)
- {
- dt_control_log(_("cannot display masks when the blending mask is displayed"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), FALSE);
- g->mask_display = FALSE;
- return;
- }
- else
- g->mask_display = !g->mask_display;
-
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
-// dt_dev_reprocess_center(self->dev);
- dt_iop_refresh_center(self);
-
- // Unlock the colour picker so we can display our own custom cursor
- dt_iop_color_picker_reset(self, TRUE);
+ const dt_develop_t *dev = self->dev;
+ return dev->form_gui && dev->form_visible;
}
-/***
- * GUI Interactivity
- **/
-
static void switch_cursors(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -2039,6 +2644,7 @@ static void switch_cursors(dt_iop_module_t *self)
}
}
+
int mouse_moved(dt_iop_module_t *self,
const float pzx,
const float pzy,
@@ -2051,6 +2657,7 @@ int mouse_moved(dt_iop_module_t *self,
const dt_develop_t *dev = self->dev;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ dt_iop_toneequalizer_params_t *const p = self->params;
if(g == NULL) return 0;
@@ -2078,8 +2685,10 @@ int mouse_moved(dt_iop_module_t *self,
dt_iop_gui_leave_critical_section(self);
// store the actual exposure too, to spare I/O op
- if(g->cursor_valid && !dev->full.pipe->processing && g->luminance_valid)
- g->cursor_exposure = log2f(_luminance_from_module_buffer(self));
+ if(g->cursor_valid && !dev->full.pipe->processing && g->luminance_valid) {
+ const float lum = log2f(_luminance_from_thumb_preview_buf(self));
+ g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+ }
switch_cursors(self);
@@ -2116,7 +2725,7 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
dt_iop_toneequalizer_gui_data_t *g,
dt_iop_toneequalizer_params_t *p)
{
- // Apply an exposure offset optimized smoothly over all the exposure channels,
+ // Apply an exposure offset optimized smoothly over all the exposure NUM_SLIDERS,
// taking user instruction to apply exposure_offset EV at control_exposure EV,
// and commit the new params is the solution is valid.
@@ -2126,20 +2735,20 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
const float std = gaussian_denom(blending_sigma);
if(g->user_param_valid)
{
- for(int i = 0; i < CHANNELS; ++i)
+ for(int i = 0; i < NUM_SLIDERS; ++i)
g->temp_user_params[i] *=
exp2f(gaussian_func(centers_params[i] - control_exposure, std) * exposure_offset);
}
// Get the new weights for the radial-basis approximation
- float factors[CHANNELS] DT_ALIGNED_ARRAY;
- dt_simd_memcpy(g->temp_user_params, factors, CHANNELS);
+ float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
+ dt_simd_memcpy(g->temp_user_params, factors, NUM_SLIDERS);
if(g->user_param_valid)
- g->user_param_valid = pseudo_solve(g->interpolation_matrix, factors, CHANNELS, PIXEL_CHAN, TRUE);
+ g->user_param_valid = pseudo_solve(g->interpolation_matrix, factors, NUM_SLIDERS, NUM_OCTAVES, TRUE);
if(!g->user_param_valid)
dt_control_log(_("the interpolation is unstable, decrease the curve smoothing"));
- // Compute new user params for channels and store them locally
+ // Compute new user params for NUM_SLIDERS and store them locally
if(g->user_param_valid)
g->user_param_valid = compute_channels_factors(factors, g->temp_user_params, g->sigma);
if(!g->user_param_valid) dt_control_log(_("some parameters are out-of-bounds"));
@@ -2149,11 +2758,11 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
if(commit)
{
// Accept the solution
- dt_simd_memcpy(factors, g->factors, PIXEL_CHAN);
- g->lut_valid = FALSE;
+ dt_simd_memcpy(factors, g->factors, NUM_OCTAVES);
+ g->gui_curve_valid = FALSE;
// Convert the linear temp parameters to log gains and commit
- float gains[CHANNELS] DT_ALIGNED_ARRAY;
+ float gains[NUM_SLIDERS] DT_ALIGNED_ARRAY;
compute_channels_gains(g->temp_user_params, gains);
commit_channels_gains(gains, p);
}
@@ -2161,7 +2770,7 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
{
// Reset the GUI copy of user params
get_channels_factors(factors, p);
- dt_simd_memcpy(factors, g->temp_user_params, CHANNELS);
+ dt_simd_memcpy(factors, g->temp_user_params, NUM_SLIDERS);
g->user_param_valid = TRUE;
}
@@ -2189,6 +2798,9 @@ int scrolled(dt_iop_module_t *self,
if(in_mask_editing(self)) return 0;
+ // ALT/Option should work for zooming
+ if (dt_modifier_is(state, GDK_MOD1_MASK)) return 0;
+
// if GUI buffers not ready, exit but still handle the cursor
dt_iop_gui_enter_critical_section(self);
@@ -2204,7 +2816,9 @@ int scrolled(dt_iop_module_t *self,
// re-read the exposure in case it has changed
dt_iop_gui_enter_critical_section(self);
- g->cursor_exposure = log2f(_luminance_from_module_buffer(self));
+
+ const float lum = log2f(_luminance_from_thumb_preview_buf(self));
+ g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
dt_iop_gui_leave_critical_section(self);
@@ -2221,7 +2835,7 @@ int scrolled(dt_iop_module_t *self,
const float offset = step * ((float)increment);
- // Get the desired correction on exposure channels
+ // Get the desired correction on exposure NUM_SLIDERS
dt_iop_gui_enter_critical_section(self);
const gboolean commit = set_new_params_interactive(g->cursor_exposure, offset,
g->sigma * g->sigma / 2.0f, g, p);
@@ -2240,10 +2854,12 @@ int scrolled(dt_iop_module_t *self,
return 1;
}
-/***
- * GTK/Cairo drawings and custom widgets
- **/
+ /****************************************************************************
+ *
+ * GTK/Cairo drawings and custom widgets
+ *
+ ****************************************************************************/
static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
GtkWidget *widget,
dt_iop_toneequalizer_gui_data_t *const restrict g);
@@ -2277,6 +2893,7 @@ void cairo_draw_hatches(cairo_t *cr,
}
}
+
static void get_shade_from_luminance(cairo_t *cr,
const float luminance,
const float alpha)
@@ -2349,6 +2966,7 @@ void gui_post_expose(dt_iop_module_t *self,
dt_develop_t *dev = self->dev;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ dt_iop_toneequalizer_params_t *p = self->params;
// if we are editing masks, do not display controls
if(in_mask_editing(self)) return;
@@ -2369,8 +2987,10 @@ void gui_post_expose(dt_iop_module_t *self,
return;
// re-read the exposure in case it has changed
- if(g->luminance_valid && self->enabled)
- g->cursor_exposure = log2f(_luminance_from_module_buffer(self));
+ if(g->luminance_valid && self->enabled) {
+ const float lum = log2f(_luminance_from_thumb_preview_buf(self));
+ g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+ }
dt_iop_gui_enter_critical_section(self);
@@ -2491,7 +3111,7 @@ void gui_post_expose(dt_iop_module_t *self,
const float radius_threshold = 0.45f;
g->area_active_node = -1;
if(g->cursor_valid)
- for(int i = 0; i < CHANNELS; ++i)
+ for(int i = 0; i < NUM_SLIDERS; ++i)
{
const float delta_x = fabsf(g->cursor_exposure - centers_params[i]);
if(delta_x < radius_threshold)
@@ -2502,12 +3122,13 @@ void gui_post_expose(dt_iop_module_t *self,
}
}
+
static void _develop_distort_callback(gpointer instance,
dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
- if(!g->distort_signal_actif) return;
+ if(!g->distort_signal_active) return;
/* disable the distort signal now to avoid recursive call on this signal as we are
about to reprocess the preview pipe which has some module doing distortion. */
@@ -2520,28 +3141,32 @@ static void _develop_distort_callback(gpointer instance,
dt_dev_reprocess_preview(darktable.develop);
}
+
static void _set_distort_signal(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
- if(self->enabled && !g->distort_signal_actif)
+ if(self->enabled && !g->distort_signal_active)
{
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_DISTORT, _develop_distort_callback);
- g->distort_signal_actif = TRUE;
+ g->distort_signal_active = TRUE;
}
}
+
static void _unset_distort_signal(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
- if(g->distort_signal_actif)
+ if(g->distort_signal_active)
{
DT_CONTROL_SIGNAL_DISCONNECT(_develop_distort_callback, self);
- g->distort_signal_actif = FALSE;
+ g->distort_signal_active = FALSE;
}
}
+
void gui_focus(dt_iop_module_t *self, gboolean in)
{
+ printf("gui_focus %d\n", in);
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_gui_enter_critical_section(self);
g->has_focus = in;
@@ -2580,6 +3205,8 @@ static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
if(g->cst)
cairo_surface_destroy(g->cst);
+
+ g->allocation.height -= DT_RESIZE_HANDLE_SIZE;
g->cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
g->allocation.width, g->allocation.height);
@@ -2640,10 +3267,10 @@ static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
float value = -8.0f;
- for(int k = 0; k < CHANNELS; k++)
+ for(int k = 0; k < NUM_SLIDERS; k++)
{
const float xn =
- (((float)k) / ((float)(CHANNELS - 1))) * g->graph_width - g->sign_width;
+ (((float)k) / ((float)(NUM_SLIDERS - 1))) * g->graph_width - g->sign_width;
snprintf(text, sizeof(text), "%+.0f", value);
pango_layout_set_text(g->layout, text, -1);
@@ -2720,8 +3347,8 @@ static inline void init_nodes_x(dt_iop_toneequalizer_gui_data_t *g)
if(!g->valid_nodes_x && g->graph_width > 0)
{
- for(int i = 0; i < CHANNELS; ++i)
- g->nodes_x[i] = (((float)i) / ((float)(CHANNELS - 1))) * g->graph_width;
+ for(int i = 0; i < NUM_SLIDERS; ++i)
+ g->nodes_x[i] = (((float)i) / ((float)(NUM_SLIDERS - 1))) * g->graph_width;
g->valid_nodes_x = TRUE;
}
}
@@ -2734,7 +3361,7 @@ static inline void init_nodes_y(dt_iop_toneequalizer_gui_data_t *g)
if(g->user_param_valid && g->graph_height > 0)
{
- for(int i = 0; i < CHANNELS; ++i)
+ for(int i = 0; i < NUM_SLIDERS; ++i)
g->nodes_y[i] = // assumes factors in [-2 ; 2] EV
(0.5 - log2f(g->temp_user_params[i]) / 4.0) * g->graph_height;
g->valid_nodes_y = TRUE;
@@ -2742,12 +3369,110 @@ static inline void init_nodes_y(dt_iop_toneequalizer_gui_data_t *g)
}
+static inline void interpolate_gui_color(GdkRGBA a, GdkRGBA b, float t, GdkRGBA *out)
+{
+ float t_clamp = fast_clamp(t, 0.0f, 1.0f);
+ out->red = a.red + t_clamp * (b.red - a.red);
+ out->green = a.green + t_clamp * (b.green - a.green);
+ out->blue = a.blue + t_clamp * (b.blue - a.blue);
+ out->alpha = a.alpha + t_clamp * (b.alpha - a.alpha);
+}
+
+
+static inline void compute_gui_curve_colors(dt_iop_module_t *self)
+{
+ dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ dt_iop_toneequalizer_params_t *p = self->params;
+
+ const float *const restrict curve = g->gui_curve;
+ GdkRGBA *const restrict colors = g->gui_curve_colors;
+ const gboolean filter_active = (p->filter != DT_TONEEQ_NONE);
+ const float ev_dx = g->image_EV_per_UI_sample;
+
+ const GdkRGBA standard = darktable.bauhaus->graph_fg;
+ const GdkRGBA warning = {0.75, 0.5, 0.0, 1.0};
+ const GdkRGBA error = {1.0, 0.0, 0.0, 1.0};
+ GdkRGBA temp_color = {0.0, 0.0, 0.0, 1.0};
+
+ const int shadows_limit = (int)UI_HISTO_SAMPLES * 0.3333;
+ const int highlights_limit = (int)UI_HISTO_SAMPLES * 0.6666;
+
+ // printf("ev_dx=%f filter_active=%d details=%d\n", ev_dx, filter_active, p->details);
+
+ if (!g->gui_histogram_valid || !g->gui_curve_valid) {
+ // the module is not completely initialized, set all colors to standard
+ for(int k = 0; k < UI_HISTO_SAMPLES; k++)
+ colors[k] = standard;
+ return;
+ };
+
+ colors[0] = standard;
+ for(int k = 1; k < UI_HISTO_SAMPLES; k++)
+ {
+ float ev_dy = (curve[k] - curve[k - 1]);
+ float steepness = ev_dy / ev_dx;
+ colors[k] = standard;
+
+ if(filter_active && k < shadows_limit && curve[k] < 0.0f)
+ {
+ // Lower shadows with filter active, this does not provide the local
+ // contrast that the user probably expects.
+ const float x_dist = ((float)(shadows_limit - k) / (float)UI_HISTO_SAMPLES) * 8.0f;
+ const float color_dist = fminf(x_dist, -curve[k]);
+ interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ colors[k] = temp_color;
+ }
+ else if(filter_active && k > highlights_limit && curve[k] > 0.0f)
+ {
+ // Raise highlights with filter active, this does not provide the local
+ // contrast that the user probably expects.
+ const float x_dist = ((float)(k - highlights_limit) / (float)UI_HISTO_SAMPLES) * 8.0f;
+ const float color_dist = fminf(x_dist, curve[k]);
+ interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ colors[k] = temp_color;
+ }
+ else if(!filter_active && k < shadows_limit && curve[k] > 0.0f)
+ {
+ // Raise shadows without filter, this leads to a loss of contrast.
+ const float x_dist = ((float)(shadows_limit - k) / (float)UI_HISTO_SAMPLES) * 8.0f;
+ const float color_dist = fminf(x_dist, curve[k]);
+ interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ colors[k] = temp_color;
+ }
+ else if(!filter_active && k > highlights_limit && curve[k] < 0.0f)
+ {
+ // Lower highlights without filter, this leads to a loss of contrast.
+ const float x_dist = ((float)(k - highlights_limit) / (float)UI_HISTO_SAMPLES) * 8.0f;
+ const float color_dist = fminf(x_dist, -curve[k]);
+ interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ colors[k] = temp_color;
+ }
+
+ // Too steep downward slopes.
+ // These warnings take precedence, even if the segment was already
+ // colored, we overwrite the colors here.
+ if(steepness < -0.5f && steepness > -1.0f)
+ {
+ colors[k] = warning;
+ }
+ else if(steepness <= -1.0f)
+ {
+ colors[k] = error;
+ }
+
+ // printf("curve[%d]=%f ev_dx=%f ev_dy=%f steepness=%f colors[%d]=%f\n", k, curve[k], ev_dx, ev_dy, steepness, k, colors[k].red);
+ }
+}
+
+
static gboolean area_draw(GtkWidget *widget,
cairo_t *cr,
dt_iop_module_t *self)
{
// Draw the widget equalizer view
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+ dt_iop_toneequalizer_params_t *p = self->params;
+
if(g == NULL) return FALSE;
// Init or refresh the drawing cache
@@ -2767,8 +3492,11 @@ static gboolean area_draw(GtkWidget *widget,
dt_iop_gui_leave_critical_section(self);
// Refresh cached UI elements
- update_histogram(self);
- update_curve_lut(self);
+ update_gui_histogram(self);
+ curve_interpolation(self);
+
+ // The colors depend on the histogram and the curve
+ compute_gui_curve_colors(self);
// Draw graph background
cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(0.5));
@@ -2788,26 +3516,52 @@ static gboolean area_draw(GtkWidget *widget,
cairo_line_to(g->cr, g->graph_width, 0.5 * g->graph_height);
cairo_stroke(g->cr);
- if(g->histogram_valid && self->enabled)
+ if(g->gui_histogram_valid && self->enabled)
{
- // draw the inset histogram
+ float histo_height;
+ if (g->two_histograms_display)
+ histo_height = 0.5 * g->graph_height;
+ else
+ histo_height = g->graph_height;
+
+ // draw the mask histogram background
set_color(g->cr, darktable.bauhaus->inset_histogram);
cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(4.0));
cairo_move_to(g->cr, 0, g->graph_height);
- for(int k = 0; k < UI_SAMPLES; k++)
+ for(int k = 0; k < UI_HISTO_SAMPLES; k++)
{
// the x range is [-8;+0] EV
- const float x_temp = (8.0 * (float)k / (float)(UI_SAMPLES - 1)) - 8.0;
- const float y_temp = (float)(g->histogram[k]) / (float)(g->max_histogram) * 0.96;
+ const float x_temp = (8.0 * (float)k / (float)(UI_HISTO_SAMPLES - 1)) - 8.0;
+ const float y_temp = fast_clamp((float)(g->histogram[k]) / (float)(g->max_histogram), -1.0f, 1.0f) * 0.96;
cairo_line_to(g->cr, (x_temp + 8.0) * g->graph_width / 8.0,
- (1.0 - y_temp) * g->graph_height );
+ (g->graph_height - y_temp * histo_height));
}
cairo_line_to(g->cr, g->graph_width, g->graph_height);
cairo_close_path(g->cr);
cairo_fill(g->cr);
- if(g->histogram_last_decile > -0.1f)
+ // optionally draw the image histogram upside-down in the top half
+ if (g->two_histograms_display)
+ {
+ cairo_move_to(g->cr, 0, 0);
+
+ for(int k = 0; k < UI_HISTO_SAMPLES; k++)
+ {
+ // the x range is [-8;+0] EV
+ const float x_temp = (8.0 * (float)k / (float)(UI_HISTO_SAMPLES - 1)) - 8.0;
+ const float y_temp = fast_clamp((float)(g->image_histogram[k]) / (float)(g->max_image_histogram), -1.0f, 1.0f) * 0.96;
+ cairo_line_to(g->cr, (x_temp + 8.0) * g->graph_width / 8.0,
+ y_temp * histo_height);
+ //if (k % 5 == 0)
+ // printf("g->image_histogram[%d] = %d, max_image_histogram = %d\n", k, g->image_histogram[k], g->max_image_histogram);
+ }
+ cairo_line_to(g->cr, g->graph_width, 0);
+ cairo_close_path(g->cr);
+ cairo_fill(g->cr);
+ }
+
+ if(post_scale_shift(g->histogram_last_decile, p->post_scale, p->post_shift) > -0.1f)
{
// histogram overflows controls in highlights : display warning
cairo_save(g->cr);
@@ -2819,7 +3573,7 @@ static gboolean area_draw(GtkWidget *widget,
cairo_restore(g->cr);
}
- if(g->histogram_first_decile < -7.9f)
+ if(post_scale_shift(g->histogram_first_decile, p->post_scale, p->post_shift) < -7.9f)
{
// histogram overflows controls in lowlights : display warning
cairo_save(g->cr);
@@ -2832,23 +3586,36 @@ static gboolean area_draw(GtkWidget *widget,
}
}
- if(g->lut_valid)
+ if(g->gui_curve_valid)
{
// draw the interpolation curve
- set_color(g->cr, darktable.bauhaus->graph_fg);
- cairo_move_to(g->cr, 0, g->gui_lut[0] * g->graph_height);
+
cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(3));
+ float x_draw, y_draw;
- for(int k = 1; k < UI_SAMPLES; k++)
+ // The coloring of the curve makes it necessary to draw it as individual segments.
+ // However this led to aliasing artifacts, therefore we draw overlapping segments
+ // from k-1 to k+1.
+ for(int k = 1; k < UI_HISTO_SAMPLES - 1; k++)
{
- // the x range is [-8;+0] EV
- const float x_temp = (8.0f * (((float)k) / ((float)(UI_SAMPLES - 1)))) - 8.0f;
- const float y_temp = g->gui_lut[k];
+ set_color(g->cr, g->gui_curve_colors[k]);
+
+ // Map [0;UI_HISTO_SAMPLES] to [0;1] and then to g->graph_width.
+ x_draw = ((float)(k - 1) / (float)(UI_HISTO_SAMPLES - 1)) * g->graph_width;
+ // Map [-2;+2] EV to graph_height, with graph pixel 0 at the top
+ y_draw = (0.5f - g->gui_curve[k - 1] / 4.0f) * g->graph_height;
+
+ cairo_move_to(g->cr, x_draw, y_draw);
+
+ // Map [0;UI_HISTO_SAMPLES] to [0;1] and then to g->graph_width.
+ x_draw = ((float)(k+1) / (float)(UI_HISTO_SAMPLES - 1)) * g->graph_width;
+ // Map [-2;+2] EV to graph_height, with graph pixel 0 at the top
+ y_draw = (0.5f - g->gui_curve[k+1] / 4.0f) * g->graph_height;
- cairo_line_to(g->cr, (x_temp + 8.0f) * g->graph_width / 8.0f,
- y_temp * g->graph_height );
+ cairo_line_to(g->cr, x_draw, y_draw);
+ cairo_stroke(g->cr);
}
- cairo_stroke(g->cr);
+
}
dt_iop_gui_enter_critical_section(self);
@@ -2862,7 +3629,7 @@ static gboolean area_draw(GtkWidget *widget,
if(g->user_param_valid)
{
// draw nodes positions
- for(int k = 0; k < CHANNELS; k++)
+ for(int k = 0; k < NUM_SLIDERS; k++)
{
const float xn = g->nodes_x[k];
const float yn = g->nodes_y[k];
@@ -2895,9 +3662,9 @@ static gboolean area_draw(GtkWidget *widget,
{
const float radius = g->sigma * g->graph_width / 8.0f / sqrtf(2.0f);
cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(1.5));
- const float y =
- g->gui_lut[(int)CLAMP(((UI_SAMPLES - 1) * g->area_x / g->graph_width),
- 0, UI_SAMPLES - 1)];
+ const float y = 0.5f -
+ g->gui_curve[(int)CLAMP(((UI_HISTO_SAMPLES - 1) * g->area_x / g->graph_width),
+ 0, UI_HISTO_SAMPLES - 1)] / 4.0f;
cairo_arc(g->cr, g->area_x, y * g->graph_height, radius, 0, 2. * M_PI);
set_color(g->cr, darktable.bauhaus->graph_fg);
cairo_stroke(g->cr);
@@ -2908,13 +3675,13 @@ static gboolean area_draw(GtkWidget *widget,
float x_pos = (g->cursor_exposure + 8.0f) / 8.0f * g->graph_width;
- if(x_pos > g->graph_width || x_pos < 0.0f)
+ if(x_pos >= g->graph_width || x_pos <= 0.0f)
{
// exposure at current position is outside [-8; 0] EV :
// bound it in the graph limits and show it in orange
cairo_set_source_rgb(g->cr, 0.75, 0.50, 0.);
cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(3));
- x_pos = (x_pos < 0.0f) ? 0.0f : g->graph_width;
+ x_pos = (x_pos <= 0.0f) ? 0.0f : g->graph_width;
}
else
{
@@ -2935,6 +3702,7 @@ static gboolean area_draw(GtkWidget *widget,
return TRUE;
}
+
static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
cairo_t *crf,
dt_iop_module_t *self)
@@ -2942,7 +3710,7 @@ static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
// Draw the widget equalizer view
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
- update_histogram(self);
+ update_gui_histogram(self);
GtkAllocation allocation;
gtk_widget_get_allocation(widget, &allocation);
@@ -2958,7 +3726,7 @@ static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
dt_iop_gui_enter_critical_section(self);
- if(g->histogram_valid)
+ if(g->gui_histogram_valid)
{
// draw histogram span
const float left = (g->histogram_first_decile + 8.0f) / 8.0f;
@@ -2972,7 +3740,7 @@ static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
// draw average bar
set_color(cr, darktable.bauhaus->graph_fg);
cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
- const float average = (g->histogram_average + 8.0f) / 8.0f;
+ const float average = ((g->histogram_first_decile + g->histogram_last_decile) / 2.0f + 8.0f) / 8.0f;
cairo_move_to(cr, average * allocation.width, 0.0);
cairo_line_to(cr, average * allocation.width, allocation.height);
cairo_stroke(cr);
@@ -3112,7 +3880,7 @@ static gboolean area_motion_notify(GtkWidget *widget,
const float offset = (-event->y + g->area_y) / g->graph_height * 4.0f;
const float cursor_exposure = g->area_x / g->graph_width * 8.0f - 8.0f;
- // Get the desired correction on exposure channels
+ // Get the desired correction on exposure NUM_SLIDERS
g->area_dragging = set_new_params_interactive(cursor_exposure, offset,
g->sigma * g->sigma / 2.0f, g, p);
dt_iop_gui_leave_critical_section(self);
@@ -3131,7 +3899,7 @@ static gboolean area_motion_notify(GtkWidget *widget,
if(g->valid_nodes_x)
{
const float radius_threshold = fabsf(g->nodes_x[1] - g->nodes_x[0]) * 0.45f;
- for(int i = 0; i < CHANNELS; ++i)
+ for(int i = 0; i < NUM_SLIDERS; ++i)
{
const float delta_x = fabsf(g->area_x - g->nodes_x[i]);
if(delta_x < radius_threshold)
@@ -3181,6 +3949,7 @@ static gboolean area_button_release(GtkWidget *widget,
return FALSE;
}
+
static gboolean area_scroll(GtkWidget *widget,
GdkEventScroll *event,
gpointer user_data)
@@ -3204,6 +3973,7 @@ static gboolean notebook_button_press(GtkWidget *widget,
return FALSE;
}
+
GSList *mouse_actions(dt_iop_module_t *self)
{
GSList *lm = NULL;
@@ -3219,13 +3989,14 @@ GSList *mouse_actions(dt_iop_module_t *self)
return lm;
}
+
/**
* Post pipe events
**/
-
static void _develop_ui_pipe_started_callback(gpointer instance,
dt_iop_module_t *self)
{
+ printf("ui pipe started callback\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
switch_cursors(self);
@@ -3250,6 +4021,7 @@ static void _develop_ui_pipe_started_callback(gpointer instance,
static void _develop_preview_pipe_finished_callback(gpointer instance,
dt_iop_module_t *self)
{
+ printf("preview pipe finished callback\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
@@ -3268,11 +4040,13 @@ static void _develop_preview_pipe_finished_callback(gpointer instance,
static void _develop_ui_pipe_finished_callback(gpointer instance,
dt_iop_module_t *self)
{
+ printf("ui pipe finished callback\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
switch_cursors(self);
}
+
void gui_reset(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -3297,9 +4071,75 @@ void gui_init(dt_iop_module_t *self)
g->notebook = dt_ui_notebook_new(¬ebook_def);
dt_action_define_iop(self, NULL, N_("page"), GTK_WIDGET(g->notebook), ¬ebook_def);
- // Simple view
+ // Curve view (former "advanced" page)
+ self->widget = dt_ui_notebook_page(g->notebook, N_("curve"), NULL);
+ gtk_widget_set_vexpand(GTK_WIDGET(self->widget), TRUE);
+
+ // g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
+
+ g->area = GTK_DRAWING_AREA(dt_ui_resize_wrap(NULL,
+ 0,
+ "plugins/darkroom/toneequal/graphheight"));
+
+ GtkWidget *wrapper = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // for CSS size
+ gtk_box_pack_start(GTK_BOX(wrapper), GTK_WIDGET(g->area), TRUE, TRUE, 0);
+ g_object_set_data(G_OBJECT(wrapper), "iop-instance", self);
+ gtk_widget_set_name(GTK_WIDGET(wrapper), "toneeqgraph");
+ dt_action_define_iop(self, NULL, N_("graph"), GTK_WIDGET(wrapper), NULL);
+ gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(wrapper), TRUE, TRUE, 0);
+ gtk_widget_add_events(GTK_WIDGET(g->area),
+ GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+ gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
+ g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(area_draw), self);
+ g_signal_connect(G_OBJECT(g->area), "button-press-event",
+ G_CALLBACK(area_button_press), self);
+ g_signal_connect(G_OBJECT(g->area), "button-release-event",
+ G_CALLBACK(area_button_release), self);
+ g_signal_connect(G_OBJECT(g->area), "leave-notify-event",
+ G_CALLBACK(area_enter_leave_notify), self);
+ g_signal_connect(G_OBJECT(g->area), "enter-notify-event",
+ G_CALLBACK(area_enter_leave_notify), self);
+ g_signal_connect(G_OBJECT(g->area), "motion-notify-event",
+ G_CALLBACK(area_motion_notify), self);
+ g_signal_connect(G_OBJECT(g->area), "scroll-event",
+ G_CALLBACK(area_scroll), self);
+ gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double-click to reset the curve"));
+
+ g->post_auto_align = dt_bauhaus_combobox_from_params(self, "post_auto_align");
+ gtk_widget_set_tooltip_text(g->post_auto_align, _("automatically set the mask exposure/contrast"));
+
+ g->post_scale = dt_bauhaus_slider_from_params(self, "post_scale");
+ dt_bauhaus_slider_set_soft_range(g->post_scale, -2.0, 2.0);
+ gtk_widget_set_tooltip_text
+ (g->post_scale,
+ _("set the mask contrast / scale the histogram"));
+
+ g->post_shift = dt_bauhaus_slider_from_params(self, "post_shift");
+ dt_bauhaus_slider_set_soft_range(g->post_shift, -4.0, 4.0);
+ gtk_widget_set_tooltip_text
+ (g->post_shift,
+ _("set the mask exposure / shift the histogram"));
+
+ g->smoothing = dt_bauhaus_slider_new_with_range(self, -2.33f, +1.67f, 0, 0.0f, 2);
+ dt_bauhaus_slider_set_soft_range(g->smoothing, -1.0f, 1.0f);
+ dt_bauhaus_widget_set_label(g->smoothing, NULL, N_("curve smoothing"));
+ gtk_widget_set_tooltip_text(g->smoothing,
+ _("positive values will produce more progressive tone transitions\n"
+ "but the curve might become oscillatory in some settings.\n"
+ "negative values will avoid oscillations and behave more robustly\n"
+ "but may produce brutal tone transitions and damage local contrast."));
+ gtk_box_pack_start(GTK_BOX(self->widget), g->smoothing, FALSE, FALSE, 0);
+ g_signal_connect(G_OBJECT(g->smoothing), "value-changed", G_CALLBACK(smoothing_callback), self);
+
- self->widget = dt_ui_notebook_page(g->notebook, N_("simple"), NULL);
+ // sliders section (former "simple" page)
+ dt_gui_new_collapsible_section(&g->sliders_section, "plugins/darkroom/toneequal/expand_sliders", _("sliders"),
+ GTK_BOX(self->widget), DT_ACTION(self));
+ gtk_widget_set_tooltip_text(g->sliders_section.expander, _("sliders"));
+
+ self->widget = GTK_WIDGET(g->sliders_section.container);
g->noise = dt_bauhaus_slider_from_params(self, "noise");
dt_bauhaus_slider_set_format(g->noise, _(" EV"));
@@ -3338,52 +4178,8 @@ void gui_init(dt_iop_module_t *self)
dt_bauhaus_widget_set_label(g->whites, N_("simple"), N_("-1 EV"));
dt_bauhaus_widget_set_label(g->speculars, N_("simple"), N_("+0 EV"));
- // Advanced view
-
- self->widget = dt_ui_notebook_page(g->notebook, N_("advanced"), NULL);
-
- g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
- GtkWidget *wrapper = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // for CSS size
- gtk_box_pack_start(GTK_BOX(wrapper), GTK_WIDGET(g->area), TRUE, TRUE, 0);
- g_object_set_data(G_OBJECT(wrapper), "iop-instance", self);
- gtk_widget_set_name(GTK_WIDGET(wrapper), "toneeqgraph");
- dt_action_define_iop(self, NULL, N_("graph"), GTK_WIDGET(wrapper), NULL);
- gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(wrapper), TRUE, TRUE, 0);
- gtk_widget_add_events(GTK_WIDGET(g->area),
- GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
- | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
- | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
- gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
- g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(area_draw), self);
- g_signal_connect(G_OBJECT(g->area), "button-press-event",
- G_CALLBACK(area_button_press), self);
- g_signal_connect(G_OBJECT(g->area), "button-release-event",
- G_CALLBACK(area_button_release), self);
- g_signal_connect(G_OBJECT(g->area), "leave-notify-event",
- G_CALLBACK(area_enter_leave_notify), self);
- g_signal_connect(G_OBJECT(g->area), "enter-notify-event",
- G_CALLBACK(area_enter_leave_notify), self);
- g_signal_connect(G_OBJECT(g->area), "motion-notify-event",
- G_CALLBACK(area_motion_notify), self);
- g_signal_connect(G_OBJECT(g->area), "scroll-event",
- G_CALLBACK(area_scroll), self);
- gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double-click to reset the curve"));
-
- g->smoothing = dt_bauhaus_slider_new_with_range(self, -2.33f, +1.67f, 0, 0.0f, 2);
- dt_bauhaus_slider_set_soft_range(g->smoothing, -1.0f, 1.0f);
- dt_bauhaus_widget_set_label(g->smoothing, NULL, N_("curve smoothing"));
- gtk_widget_set_tooltip_text
- (g->smoothing,
- _("positive values will produce more progressive tone transitions\n"
- "but the curve might become oscillatory in some settings.\n"
- "negative values will avoid oscillations and behave more robustly\n"
- "but may produce brutal tone transitions and damage local contrast."));
- gtk_box_pack_start(GTK_BOX(self->widget), g->smoothing, FALSE, FALSE, 0);
- g_signal_connect(G_OBJECT(g->smoothing), "value-changed",
- G_CALLBACK(smoothing_callback), self);
// Masking options
-
self->widget = dt_ui_notebook_page(g->notebook, N_("masking"), NULL);
g->method = dt_bauhaus_combobox_from_params(self, "method");
@@ -3431,12 +4227,17 @@ void gui_init(dt_iop_module_t *self)
"lower values give smoother gradients and better smoothing\n"
"but may lead to inaccurate edges taping and halos"));
- gtk_box_pack_start(GTK_BOX(self->widget),
- dt_ui_section_label_new(C_("section", "mask post-processing")),
- FALSE, FALSE, 0);
+ // gtk_box_pack_start(GTK_BOX(self->widget),
+ // dt_ui_section_label_new(C_("section", "mask post-processing")),
+ // FALSE, FALSE, 0);
+ dt_gui_new_collapsible_section(&g->advanced_masking_section, "plugins/darkroom/toneequal/expand_advanced_masking",
+ _("mask pre-processing"), GTK_BOX(self->widget), DT_ACTION(self));
+ gtk_widget_set_tooltip_text(g->advanced_masking_section.expander, _("advanced masking"));
+
+ self->widget = GTK_WIDGET(g->advanced_masking_section.container);
g->bar = GTK_DRAWING_AREA(gtk_drawing_area_new());
- gtk_widget_set_size_request(GTK_WIDGET(g->bar), -1, 4);
+ gtk_widget_set_size_request(GTK_WIDGET(g->bar), -1, 40);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->bar), TRUE, TRUE, 0);
gtk_widget_set_can_focus(GTK_WIDGET(g->bar), TRUE);
g_signal_connect(G_OBJECT(g->bar), "draw",
@@ -3460,7 +4261,7 @@ void gui_init(dt_iop_module_t *self)
dt_bauhaus_slider_set_format(g->exposure_boost, _(" EV"));
gtk_widget_set_tooltip_text
(g->exposure_boost,
- _("use this to slide the mask average exposure along channels\n"
+ _("use this to slide the mask average exposure along NUM_SLIDERS\n"
"for a better control of the exposure correction with the available nodes."));
dt_bauhaus_widget_set_quad(g->exposure_boost, self, dtgtk_cairo_paint_wand, FALSE, auto_adjust_exposure_boost,
_("auto-adjust the average exposure"));
@@ -3472,11 +4273,25 @@ void gui_init(dt_iop_module_t *self)
(g->contrast_boost,
_("use this to counter the averaging effect of the guided filter\n"
"and dilate the mask contrast around -4EV\n"
- "this allows to spread the exposure histogram over more channels\n"
+ "this allows to spread the exposure histogram over more NUM_SLIDERS\n"
"for a better control of the exposure correction."));
dt_bauhaus_widget_set_quad(g->contrast_boost, self, dtgtk_cairo_paint_wand, FALSE, auto_adjust_contrast_boost,
_("auto-adjust the contrast"));
+ GtkWidget *histo_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_box_pack_start(GTK_BOX(histo_box),
+ dt_ui_label_new(_("show image histogram in graph")), TRUE, TRUE, 0);
+ g->show_two_histograms = dt_iop_togglebutton_new
+ (self, NULL,
+ N_("display the image histogram together with mask histogram"), NULL, G_CALLBACK(show_two_histograms_callback),
+ FALSE, 0, 0, dtgtk_cairo_paint_showmask, histo_box);
+ dt_gui_add_class(g->show_two_histograms, "dt_transparent_background");
+ dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->show_two_histograms),
+ dtgtk_cairo_paint_showmask, 0, NULL);
+ dt_gui_add_class(g->show_two_histograms, "dt_bauhaus_alignment");
+ gtk_box_pack_start(GTK_BOX(self->widget), histo_box, FALSE, FALSE, 0);
+
+
// start building top level widget
self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
@@ -3507,6 +4322,7 @@ void gui_init(dt_iop_module_t *self)
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_HISTORY_CHANGE, _develop_ui_pipe_started_callback);
}
+
void gui_cleanup(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -3515,8 +4331,8 @@ void gui_cleanup(dt_iop_module_t *self)
dt_conf_set_int("plugins/darkroom/toneequal/gui_page",
gtk_notebook_get_current_page (g->notebook));
- dt_free_align(g->thumb_preview_buf);
- dt_free_align(g->full_preview_buf);
+ dt_free_align(g->preview_buf);
+ dt_free_align(g->full_buf);
if(g->desc) pango_font_description_free(g->desc);
if(g->layout) g_object_unref(g->layout);
From 078d734e5181d1f493417840c5b0234e0aa04f3c Mon Sep 17 00:00:00 2001
From: Marc Fouquet
Date: Sun, 6 Apr 2025 14:04:47 +0200
Subject: [PATCH 2/6] Fixes, hopefully including the MacOS build problem. Some
cosmetic consistency changes.
---
src/iop/toneequal.c | 48 +++++++++++++++++++++++----------------------
1 file changed, 25 insertions(+), 23 deletions(-)
diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c
index 790d15b908a2..457d30ad3181 100644
--- a/src/iop/toneequal.c
+++ b/src/iop/toneequal.c
@@ -69,7 +69,7 @@
* smooth contiguous region (quantization parameter), but also to
* translate (exposure boost) and dilate (contrast boost) the exposure
* histogram through the control octaves, to center it on the control
- * view and make maximum use of the available NUM_SLIDERS.
+ * view and make maximum use of the available channels.
*
* Users should be aware that not all the available octaves will be
* useful on every pictures. Some automatic options will help them to
@@ -91,6 +91,8 @@
#include
#include
#include
+#include // Only needed for debug printf of hashes, TODO remove
+
#include "bauhaus/bauhaus.h"
#include "common/darktable.h"
@@ -306,8 +308,8 @@ typedef struct dt_iop_toneequalizer_gui_data_t
GtkDrawingArea *area, *bar;
GtkWidget *blending, *smoothing, *quantization;
GtkWidget *post_auto_align;
- GtkWidget *method;
- GtkWidget *details, *feathering, *contrast_boost, *iterations, *exposure_boost, *post_scale, *post_shift;
+ GtkWidget *lum_estimator;
+ GtkWidget *filter, *feathering, *contrast_boost, *iterations, *exposure_boost, *post_scale, *post_shift;
GtkNotebook *notebook;
dt_gui_collapsible_section_t sliders_section, advanced_masking_section;
GtkWidget *show_luminance_mask;
@@ -449,8 +451,8 @@ int legacy_params(dt_iop_module_t *self,
float quantization;
float contrast_boost;
float exposure_boost;
- dt_iop_toneequalizer_filter_t details;
- dt_iop_luminance_mask_method_t method;
+ dt_iop_toneequalizer_filter_t filter;
+ dt_iop_luminance_mask_method_t lum_estimator;
int iterations;
float post_scale;
float post_shift;
@@ -464,9 +466,9 @@ int legacy_params(dt_iop_module_t *self,
float noise, ultra_deep_blacks, deep_blacks, blacks;
float shadows, midtones, highlights, whites, speculars;
float blending, feathering, contrast_boost, exposure_boost;
- dt_iop_toneequalizer_filter_t details;
+ dt_iop_toneequalizer_filter_t filter;
int iterations;
- dt_iop_luminance_mask_method_t method;
+ dt_iop_luminance_mask_method_t lum_estimator;
} dt_iop_toneequalizer_params_v1_t;
const dt_iop_toneequalizer_params_v1_t *o = old_params;
@@ -488,9 +490,9 @@ int legacy_params(dt_iop_module_t *self,
n->contrast_boost = o->contrast_boost;
n->exposure_boost = o->exposure_boost;
- n->details = o->details;
+ n->filter = o->filter;
n->iterations = o->iterations;
- n->method = o->method;
+ n->lum_estimator = o->lum_estimator;
// V2 params
n->quantization = 0.0f;
@@ -515,8 +517,8 @@ int legacy_params(dt_iop_module_t *self,
float shadows; float midtones; float highlights; float whites;
float speculars; float blending; float smoothing; float feathering;
float quantization; float contrast_boost; float exposure_boost;
- dt_iop_toneequalizer_filter_t details;
- dt_iop_luminance_mask_method_t method;
+ dt_iop_toneequalizer_filter_t filter;
+ dt_iop_luminance_mask_method_t lum_estimator;
int iterations;
} dt_iop_toneequalizer_params_v2_t;
@@ -539,9 +541,9 @@ int legacy_params(dt_iop_module_t *self,
n->contrast_boost = o->contrast_boost;
n->exposure_boost = o->exposure_boost;
- n->details = o->details;
+ n->filter = o->filter;
n->iterations = o->iterations;
- n->method = o->method;
+ n->lum_estimator = o->lum_estimator;
// V2 params
n->quantization = o->quantization;
@@ -1380,7 +1382,7 @@ void toneeq_process(dt_iop_module_t *self,
const gboolean luminance_valid = g->luminance_valid;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process PIXELPIPE_PREVIEW: hash=%ld saved_hash=%ld luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
+ printf("toneeq_process PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
luminance_valid);
if(saved_upstream_hash != current_upstream_hash || !luminance_valid)
@@ -1465,7 +1467,7 @@ void toneeq_process(dt_iop_module_t *self,
const gboolean luminance_valid = g->luminance_valid;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process GUI FULL: hash=%ld saved_hash=%ld luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
+ printf("toneeq_process GUI FULL: hash=%"PRIu64" saved_hash=%"PRIu64" luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
luminance_valid);
// Re-compute if the upstream state has changed
@@ -2183,7 +2185,7 @@ void gui_changed(dt_iop_module_t *self,
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_toneequalizer_params_t *p = self->params;
- if(w == g->method
+ if(w == g->lum_estimator
|| w == g->blending
|| w == g->feathering
|| w == g->iterations
@@ -2191,7 +2193,7 @@ void gui_changed(dt_iop_module_t *self,
{
invalidate_luminance_cache(self);
}
- else if(w == g->details)
+ else if(w == g->filter)
{
invalidate_luminance_cache(self);
show_guiding_controls(self);
@@ -3397,7 +3399,7 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
const int shadows_limit = (int)UI_HISTO_SAMPLES * 0.3333;
const int highlights_limit = (int)UI_HISTO_SAMPLES * 0.6666;
- // printf("ev_dx=%f filter_active=%d details=%d\n", ev_dx, filter_active, p->details);
+ // printf("ev_dx=%f filter_active=%d filter=%d\n", ev_dx, filter_active, p->filter);
if (!g->gui_histogram_valid || !g->gui_curve_valid) {
// the module is not completely initialized, set all colors to standard
@@ -4182,16 +4184,16 @@ void gui_init(dt_iop_module_t *self)
// Masking options
self->widget = dt_ui_notebook_page(g->notebook, N_("masking"), NULL);
- g->method = dt_bauhaus_combobox_from_params(self, "method");
+ g->lum_estimator = dt_bauhaus_combobox_from_params(self, "lum_estimator");
gtk_widget_set_tooltip_text
- (g->method,
+ (g->lum_estimator,
_("preview the mask and chose the estimator that gives you the\n"
"higher contrast between areas to dodge and areas to burn"));
- g->details = dt_bauhaus_combobox_from_params(self, N_("details"));
- dt_bauhaus_widget_set_label(g->details, NULL, N_("preserve details"));
+ g->filter = dt_bauhaus_combobox_from_params(self, N_("filter"));
+ dt_bauhaus_widget_set_label(g->filter, NULL, N_("preserve details"));
gtk_widget_set_tooltip_text
- (g->details,
+ (g->filter,
_("'no' affects global and local contrast (safe if you only add contrast)\n"
"'guided filter' only affects global contrast and tries to preserve local contrast\n"
"'averaged guided filter' is a geometric mean of 'no' and 'guided filter' methods\n"
From 6750bfb9e6d17072d94ab7226b3d6a346d579d3a Mon Sep 17 00:00:00 2001
From: Marc Fouquet
Date: Thu, 1 May 2025 10:32:22 +0200
Subject: [PATCH 3/6] May 1st version that fixes most problems.
---
src/iop/toneequal.c | 1153 ++++++++++++++++++++++++++++---------------
1 file changed, 748 insertions(+), 405 deletions(-)
diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c
index 457d30ad3181..b116cc4973ba 100644
--- a/src/iop/toneequal.c
+++ b/src/iop/toneequal.c
@@ -73,7 +73,7 @@
*
* Users should be aware that not all the available octaves will be
* useful on every pictures. Some automatic options will help them to
- * optimize the luminance mask, performing histogram analys, mapping
+ * optimize the luminance mask, performing histogram analysis, mapping
* the average exposure to -4EV, and mapping the first and last
* deciles of the histogram on its average ± 4EV. These automatic
* helpers usually fail on X-Trans sensors, maybe because of bad
@@ -125,8 +125,64 @@
#include
#endif
+DT_MODULE_INTROSPECTION(3, dt_iop_toneequalizer_params_t)
+
+/****************************************************************************
+ *
+ * Debugging code, remove before release
+ *
+ ****************************************************************************/
+
+#include
+
+#define DEBUG_WRITE_BUFFERS TRUE
+
+int debug_write_buffer_to_file(const float *buffer, const char *tag, int width, int height, int channels,
+ dt_dev_pixelpipe_type_t const pipe)
+{
+ if (!DEBUG_WRITE_BUFFERS) return 0;
+
+ // Get current time in milliseconds
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ unsigned long long ms = (unsigned long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
+
+ // Create filename
+ char filename[256];
+ snprintf(filename, sizeof(filename), "%llu_%s_%d.buftxt", ms, tag, pipe);
+
+ // Open file
+ FILE *file = g_fopen(filename, "w");
+ if(!file) return -1;
+
+ // Write header
+ fprintf(file, "%d %d %d\n", width, height, channels);
+
+ // Write buffer contents
+ const size_t total = width * height * channels;
+ int line_pos = 0;
+
+ for(size_t i = 0; i < total; i++)
+ {
+ if(line_pos == 20)
+ { // Start new line every 20 numbers
+ fputc('\n', file);
+ line_pos = 0;
+ }
+
+ if(line_pos > 0)
+ {
+ fputc(' ', file); // Space between numbers
+ }
+
+ fprintf(file, "%.6f", buffer[i]);
+ line_pos++;
+ }
+
+ fclose(file);
+ return 0;
+}
-DT_MODULE_INTROSPECTION(2, dt_iop_toneequalizer_params_t)
/****************************************************************************
*
@@ -266,8 +322,6 @@ typedef struct dt_iop_toneequalizer_gui_data_t
// 14 int to pack - contiguous memory
gboolean mask_display;
int max_histogram;
- int buf_width;
- int buf_height;
int cursor_pos_x;
int cursor_pos_y;
int pipe_order;
@@ -275,10 +329,10 @@ typedef struct dt_iop_toneequalizer_gui_data_t
// 6 uint64 to pack - contiguous-ish memory
dt_hash_t full_upstream_hash;
dt_hash_t preview_upstream_hash;
- dt_hash_t sync_hash;
size_t preview_buf_width, preview_buf_height;
size_t full_buf_width, full_buf_height;
+ int full_buf_x, full_buf_y; // top left corner of the main window
// Heap arrays, 64 bits-aligned, unknown length
float *preview_buf; // For performance and to get the mask luminance under the mouse cursor
@@ -289,17 +343,23 @@ typedef struct dt_iop_toneequalizer_gui_data_t
float sigma;
// stats for the mask histogram
- float histogram_first_decile;
- float histogram_last_decile;
+ // preview mask deciles are used by the contrast/exposure boost magic wands
+ float prv_histogram_first_decile;
+ float prv_histogram_last_decile;
- // automatic values for post scale/shift from PREVIEW thread
+ // full mask deciles are used to calculate auto post scale/shift
+ float full_histogram_first_decile;
+ float full_histogram_last_decile;
+
+ // automatic values for post scale/shift
float post_scale_value;
float post_shift_value;
- // stats for the image histogram
+ // image histogram
+ // used to optionally draw this as a second histogram and also to color the curve
float image_histogram_first_decile;
float image_histogram_last_decile;
- int max_image_histogram;
+ int image_max_histogram;
float image_EV_per_UI_sample;
gboolean two_histograms_display;
@@ -311,7 +371,7 @@ typedef struct dt_iop_toneequalizer_gui_data_t
GtkWidget *lum_estimator;
GtkWidget *filter, *feathering, *contrast_boost, *iterations, *exposure_boost, *post_scale, *post_shift;
GtkNotebook *notebook;
- dt_gui_collapsible_section_t sliders_section, advanced_masking_section;
+ dt_gui_collapsible_section_t sliders_section, guided_filter_section, advanced_masking_section;
GtkWidget *show_luminance_mask;
GtkWidget *show_two_histograms;
@@ -355,8 +415,11 @@ typedef struct dt_iop_toneequalizer_gui_data_t
gboolean has_focus; // TRUE if the widget has the focus from GTK
// Flags for buffer caches invalidation
- gboolean luminance_valid; // TRUE if the luminance cache is ready,
- // hires_histogram and deciles are valid
+ gboolean prv_luminance_valid; // TRUE if the preview luminance cache is ready,
+ // hires_histogram and prv deciles are valid
+ gboolean full_luminance_valid;// TRUE if the full luminance cache is ready,
+ // full deciles are valid
+
gboolean gui_histogram_valid; // TRUE if the histogram cache and stats are ready
gboolean graph_valid; // TRUE if the UI graph view is ready
@@ -371,6 +434,18 @@ typedef struct dt_iop_toneequalizer_gui_data_t
gboolean distort_signal_active;
} dt_iop_toneequalizer_gui_data_t;
+// All post_scale/post_shift variables and what they are good for
+//
+// params, data
+// post_scale, post_shift, post_auto_align:
+// regular module parameters
+//
+// gui_data
+// post_scale, post_shift, post_auto_align
+// widgets to enter user data
+// post_scale_value, post_shift_value
+// used to cache calculated values in PIXELPIPE_FULL
+
/* the signal DT_SIGNAL_DEVELOP_DISTORT is used to refresh the internal
cached image buffer used for the on-canvas luminance picker. */
static void _set_distort_signal(dt_iop_module_t *self);
@@ -498,14 +573,9 @@ int legacy_params(dt_iop_module_t *self,
n->quantization = 0.0f;
n->smoothing = sqrtf(2.0f);
- // V3 params
- n->post_scale = 0.0f;
- n->post_shift = 0.0f;
- n->post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
-
*new_params = n;
*new_params_size = sizeof(dt_iop_toneequalizer_params_v3_t);
- *new_version = 3;
+ *new_version = 1;
return 0;
}
@@ -524,30 +594,7 @@ int legacy_params(dt_iop_module_t *self,
const dt_iop_toneequalizer_params_v2_t *o = old_params;
dt_iop_toneequalizer_params_v3_t *n = malloc(sizeof(dt_iop_toneequalizer_params_v3_t));
-
- // V1 params
- n->noise = o->noise;
- n->ultra_deep_blacks = o->ultra_deep_blacks;
- n->deep_blacks = o->deep_blacks;
- n->blacks = o->blacks;
- n->shadows = o->shadows;
- n->midtones = o->midtones;
- n->highlights = o->highlights;
- n->whites = o->whites;
- n->speculars = o->speculars;
-
- n->blending = o->blending;
- n->feathering = o->feathering;
- n->contrast_boost = o->contrast_boost;
- n->exposure_boost = o->exposure_boost;
-
- n->filter = o->filter;
- n->iterations = o->iterations;
- n->lum_estimator = o->lum_estimator;
-
- // V2 params
- n->quantization = o->quantization;
- n->smoothing = o->smoothing;
+ memcpy(n, o, sizeof(dt_iop_toneequalizer_params_v2_t));
// V3 params
n->post_scale = 0.0f;
@@ -760,7 +807,8 @@ static inline void compute_hires_histogram_and_stats(const float *const restrict
float *last_decile,
dt_dev_pixelpipe_type_t const debug_pipe)
{
- printf("compute_hires_histogram_and_stats pipe=%d num_elem=%ld\n", debug_pipe, num_elem);
+ // printf("compute_hires_histogram_and_stats pipe=%d num_elem=%ld\n", debug_pipe, num_elem);
+
// The GUI histogram comprises 8 EV (UI_HISTO_SAMPLES, -8 to 0).
// The high resolution histogram extends this to an exta 8 EV before and
// 8EV after, for a total of 24.
@@ -821,41 +869,6 @@ static inline void compute_hires_histogram_and_stats(const float *const restrict
*last_decile = (temp_ev_range * ((float)last_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
}
-<<<<<<< HEAD
-static void hash_set_get(const dt_hash_t *hash_in,
- dt_hash_t *hash_out,
- dt_pthread_mutex_t *lock)
-{
- // Set or get a hash in a struct the thread-safe way
- dt_pthread_mutex_lock(lock);
- *hash_out = *hash_in;
- dt_pthread_mutex_unlock(lock);
-}
-
-
-static void invalidate_luminance_cache(dt_iop_module_t *const self)
-{
- // Invalidate the private luminance cache and histogram when
- // the luminance mask extraction parameters have changed
- dt_iop_toneequalizer_gui_data_t *const restrict g = self->gui_data;
-
- dt_iop_gui_enter_critical_section(self);
- g->max_histogram = 1;
- g->luminance_valid = FALSE;
- g->histogram_valid = FALSE;
- g->thumb_preview_hash = DT_INVALID_HASH;
- g->ui_preview_hash = DT_INVALID_HASH;
- dt_iop_gui_leave_critical_section(self);
- dt_iop_refresh_all(self);
-}
-
-// gaussian-ish kernel - sum is == 1.0f so we don't care much about actual coeffs
-static const dt_colormatrix_t gauss_kernel =
- { { 0.076555024f, 0.124401914f, 0.076555024f },
- { 0.124401914f, 0.196172249f, 0.124401914f },
- { 0.076555024f, 0.124401914f, 0.076555024f } };
-=======
->>>>>>> acabda0cbe (2025-04-06 preview version with post-shift, post_scale, auto-align and curve coloring.)
__DT_CLONE_TARGETS__
static inline void compute_luminance_mask(const float *const restrict in,
@@ -864,13 +877,35 @@ static inline void compute_luminance_mask(const float *const restrict in,
const size_t height,
const dt_iop_toneequalizer_data_t *const d,
const gboolean compute_image_stats, // Optionally get the histogram of the image
- int hires_histogram[HIRES_HISTO_SAMPLES], // only for compute_image_stats
+ int hires_histogram[HIRES_HISTO_SAMPLES], // only for compute_image_stats
float *first_decile, // only for compute_image_stats
float *last_decile, // only for compute_image_stats
dt_dev_pixelpipe_type_t const debug_pipe)
{
- printf("compute_luminance_mask pipe=%d width=%ld height=%ld first luminance=%f, compute stats=%d\n", debug_pipe, width, height, luminance[0], compute_image_stats);
+ printf("compute_luminance_mask pipe=%d width=%ld height=%ld first luminance=%f estimator=%d "
+ "exposure_boost=%f contrast_boost=%f radius=%d feathering=%f iterations=%d scale=%f quantization=%f\n",
+ debug_pipe, width, height, luminance[0], d->lum_estimator,
+ d->exposure_boost, d->contrast_boost, d->radius, d->feathering,
+ d->iterations, d->scale, d->quantization);
+
+ // int n = 10;
+ // printf("compute_luminance_mask row 0 START in=%f %f %f %f %f %f %f %f %f %f %f %f\n",
+ // in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7], in[8], in[9], in[10], in[11]);
+ // printf("compute_luminance_mask row %d START in=%f %f %f %f %f %f %f %f %f %f %f %f\n", n,
+ // in[0 + n*4*width], in[1 + n*4*width], in[2 + n*4*width], in[3 + n*4*width], in[4 + n*4*width],
+ // in[5 + n*4*width], in[6 + n*4*width], in[7 + n*4*width], in[8 + n*4*width], in[9 + n*4*width],
+ // in[10 + n*4*width], in[11 + n*4*width]);
+
+ // int offset = ((width / 2) / 4) * 4; // int division to get the offset %4 aligned
+ // printf("compute_luminance_mask row %d MIDDLE in=%f %f %f %f %f %f %f %f %f %f %f %f\n", n,
+ // in[0 + n*4*width + offset], in[1 + n*4*width + offset], in[2 + n*4*width + offset], in[3 + n*4*width + offset], in[4 + n*4*width + offset],
+ // in[5 + n*4*width + offset], in[6 + n*4*width + offset], in[7 + n*4*width + offset], in[8 + n*4*width + offset], in[9 + n*4*width + offset],
+ // in[10 + n*4*width + offset], in[11 + n*4*width + offset]);
+
+ // debug_write_buffer_to_file(in, "luminance_mask", width, height, 4, debug_pipe);
+
const int num_elem = width * height;
+
switch(d->filter)
{
case(DT_TONEEQ_NONE):
@@ -879,7 +914,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
break;
}
@@ -889,7 +924,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_GEOMEAN, d->scale, d->quantization,
exp2f(-14.0f), 4.0f);
@@ -908,7 +943,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height, d->lum_estimator, d->exposure_boost,
CONTRAST_FULCRUM, d->contrast_boost);
if (compute_image_stats)
- compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_LINEAR, d->scale, d->quantization,
exp2f(-14.0f), 4.0f);
@@ -921,7 +956,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_eigf_surface_blur(luminance, width, height,
d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_GEOMEAN, d->scale,
@@ -934,7 +969,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height, d->lum_estimator, d->exposure_boost,
CONTRAST_FULCRUM, d->contrast_boost);
if (compute_image_stats)
- compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_eigf_surface_blur(luminance, width, height,
d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_LINEAR, d->scale,
@@ -947,10 +982,12 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(in, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
break;
}
}
+
+ // debug_write_buffer_to_file(luminance, "luminance_mask", width, height, 1, debug_pipe);
}
@@ -969,10 +1006,10 @@ static inline float post_scale_shift(const float v, const float post_scale, cons
// This is similar to the auto-buttons for exposure/contrast boost.
// However it runs automatically in the pipe, so it does not need to be
// triggered by the user each time the upstream exposure changes.
-void compute_auto_post_scale_shift(float *post_scale, float *post_shift,
- dt_iop_toneequalizer_post_auto_align_t post_auto_align,
+void compute_auto_post_scale_shift(dt_iop_toneequalizer_post_auto_align_t post_auto_align,
float histogram_first_decile,
float histogram_last_decile,
+ float *post_scale, float *post_shift,
dt_dev_pixelpipe_type_t const debug_pipe
)
{
@@ -980,8 +1017,8 @@ void compute_auto_post_scale_shift(float *post_scale, float *post_shift,
const float last_decile_target = -1.0f;
const float pivot = -4.0f; // for scaling
- printf("compute_auto_post_shift_scale: Pipe=%d old post_scale=%f post_shift=%f histogram_first_decile=%f histogram_last_decile=%f\n",
- debug_pipe, *post_scale, *post_shift, histogram_first_decile, histogram_last_decile);
+ printf("compute_auto_post_shift_scale: Pipe=%d post_auto_align=%d old post_scale=%f post_shift=%f histogram_first_decile=%f histogram_last_decile=%f\n",
+ debug_pipe, post_auto_align, *post_scale, *post_shift, histogram_first_decile, histogram_last_decile);
switch(post_auto_align)
{
@@ -993,7 +1030,7 @@ void compute_auto_post_scale_shift(float *post_scale, float *post_shift,
case(DT_TONEEQ_ALIGN_LEFT):
{
// auto-align at shadows
- // the histogram might be scaled around pivot
+ // the histogram can be scaled around pivot
const float scaled_first_decile = (histogram_first_decile - pivot) * exp2f(*post_scale) + pivot;
*post_shift = first_decile_target - scaled_first_decile;
break;
@@ -1126,7 +1163,8 @@ static void compute_correction_lut(float *restrict lut, const float sigma,
const float post_scale, const float post_shift,
dt_dev_pixelpipe_type_t const debug_pipe)
{
- printf("compute_correction_lut pipe=%d, post_scale=%f, post_shift=%f\n", debug_pipe, post_scale, post_shift);
+ // printf("compute_correction_lut pipe=%d, post_scale=%f, post_shift=%f\n", debug_pipe, post_scale, post_shift);
+
const float gauss_denom = gaussian_denom(sigma);
assert(NUM_OCTAVES == 8);
@@ -1162,11 +1200,11 @@ static inline void apply_toneequalizer(const float *const restrict in,
const dt_iop_toneequalizer_data_t *const d,
dt_dev_pixelpipe_type_t const debug_pipe)
{
- printf("apply_toneequalizer pipe=%d first luminance=%f roi_in %d %d %d %d roi_out %d %d %d %d post_scale=%f, post_shift=%f\n",
- debug_pipe, luminance[0],
- roi_in->x, roi_in->y, roi_in->width, roi_in->height,
- roi_out->x, roi_out->y, roi_out->width, roi_out->height,
- d->post_scale, d->post_shift);
+ // printf("apply_toneequalizer pipe=%d first luminance=%f roi_in %d %d %d %d roi_out %d %d %d %d post_scale=%f, post_shift=%f\n",
+ // debug_pipe, luminance[0],
+ // roi_in->x, roi_in->y, roi_in->width, roi_in->height,
+ // roi_out->x, roi_out->y, roi_out->width, roi_out->height,
+ // d->post_scale, d->post_shift);
const size_t npixels = (size_t)roi_in->width * roi_in->height;
const float* restrict lut = d->correction_lut;
const float lutres = LUT_RESOLUTION;
@@ -1189,7 +1227,7 @@ static inline void apply_toneequalizer(const float *const restrict in,
// we keep this version for further reference (e.g. for implementing
// a gpu version)
-// TODO MF: Remove? This is no longer correct anyways.
+// TODO MF: Remove? This is no longer identical to the LUT version.
__DT_CLONE_TARGETS__
static inline void apply_toneequalizer(const float *const restrict in,
const float *const restrict luminance,
@@ -1229,6 +1267,150 @@ static inline void apply_toneequalizer(const float *const restrict in,
#endif // USE_LUT
+float adjust_radius_to_scale(const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, int full_width, int full_height)
+{
+ dt_iop_toneequalizer_data_t *const d = piece->data;
+
+ // Get the scaled window radius for the box average
+ // This should be relative to the current full image dimensions.
+ // roi.width/height refer to a segment instead of the full image, so these
+ // values are not useful for us.
+ const int max_size = (full_width > full_height) ? full_width : full_height;
+ const float diameter = d->blending * max_size * roi->scale;
+ const int radius = (int)((diameter - 1.0f) / ( 2.0f));
+ return radius;
+}
+
+
+static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
+ const float *const restrict in, const dt_iop_roi_t *const roi_in, const dt_iop_toneequalizer_data_t *const d,
+ float *first_decile, float *last_decile)
+{
+ // A stable method of calculating the decile values. This is designed to
+ // produce the exact same results in PIXELPIPE_FULL and PIXELPIPE_EXPORT.
+ // When working with small images (thumbnail), the results may deviate,
+ // but we don't care in this case.
+
+ // Assumption: We see the whole image, not just a part of it.
+
+ dt_iop_roi_t roi = {
+ .x = 0,
+ .y = 0,
+ .width = -1,
+ .height = 900,
+ .scale = (float)roi.height / (float)roi_in->height
+ };
+
+ gboolean downscale;
+
+ if (roi.scale < 1.0f)
+ {
+ // We have been given a downscaled version already
+ roi.scale = 1.0f;
+ roi.width = roi_in->width;
+ roi.height = roi_in->height;
+
+ downscale = FALSE;
+ }
+ else
+ {
+ roi.width = (int)((float)roi_in->width * roi.scale);
+ downscale = TRUE;
+ }
+
+ int num_elem = roi.width * roi.height;
+ float *luminance = dt_alloc_align_float(num_elem);
+
+ dt_iop_toneequalizer_data_t data;
+ memcpy(&data, d, sizeof(dt_iop_toneequalizer_data_t));
+ data.radius = adjust_radius_to_scale(piece, &roi, roi_in->width, roi_in->height);
+
+ printf("compute_mask_stats_from_full_image: roi_in %d %d %d %d scale %f roi %d %d %d %d scale %f radius %d\n",
+ roi_in->x, roi_in->y, roi_in->width, roi_in->height, roi_in->scale,
+ roi.x, roi.y, roi.width, roi.height, roi.scale, data.radius);
+
+ // If the image is big, we downscale it and compute our mask from the small version.
+ // If the image was small already, this is not necessary.
+ if (downscale) {
+ float *mask_input = dt_alloc_align_float(4 * num_elem);
+ dt_iop_clip_and_zoom(mask_input, in, &roi, roi_in);
+ compute_luminance_mask(mask_input, luminance,
+ roi.width, roi.height, &data,
+ FALSE, NULL, NULL, NULL,
+ piece->pipe->type);
+ dt_free_align(mask_input);
+ }
+ else
+ {
+ compute_luminance_mask(in, luminance,
+ roi.width, roi.height, &data,
+ FALSE, NULL, NULL, NULL,
+ piece->pipe->type);
+ }
+
+ int* hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
+ compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
+ first_decile, last_decile,
+ piece->pipe->type);
+
+ dt_free_align(hires_histogram);
+ dt_free_align(luminance);
+}
+
+
+
+/***
+ * PROCESS
+ *
+ * General steps:
+ * 1. Compute the luminance mask
+ * 2. Compute the histogram of the luminance mask. In this step
+ * we also get the 5th and 95th percentiles.
+ * 3. Optionally compute auto align -> post scale and shift
+ * 4. Compute a correction LUT based on the user's curve in the GUI
+ * and optionally post scale/shift
+ * 5. Correct the image based on curve and luminance mask
+ *
+ * The guided filter is scale dependent, so the results with differently
+ * sized input images will be different.
+ *
+ * In this version communication between different pipes has been
+ * eliminated, so there is no more need to sync and wait for other threads.
+ *
+ * We can be in one of four pipelines:
+ *
+ * DT_DEV_PIXELPIPE_PREVIEW (4)
+ * - In this case the input is a slightly blurry version of the full image,
+ * which is about 900px high. The fact that the input is blurry makes
+ * guided filter calculations deviate even more from the other pipes.
+ * - We cache the calculated mask in g. This version of the mask is used to
+ * determine the luminance at the mouse cursor position for interactive
+ * editing. It is also cached, so it does not need to be re-calculated
+ * as long as nothing changes upstream.
+ * - The histogram is cached in g and used for the GUI.
+ * - The deciles are calculated and stored in g. These are needed for
+ * the exposure/contrast boost magic wands.
+ * - Post scale/shift are calculated with local data. They are off, but this
+ * image is only displayed as a small preview, so no one will notice.
+ * DT_DEV_PIXELPIPE_FULL (2)
+ * - The input image is whatever is displayed in the main view. This can be
+ * a segment of the full image if the user has zoomed in.
+ * - The muminance mask for this pipe is also cached, so it does not need to
+ * be re-calculated as long as nothing changes upstream. This also allows
+ * for switching to the mask view (greyscale) quickly.
+ * - A segment is not suitable for calculating post scale/shift, for this
+ * a histogram of the full image is needed. As a workaround, we request the
+ * full image as our region of interest (roi), when the user requsted post
+ * scale/shift calculation. This allows us to calculate values that are
+ * exactly the same as in the export.
+ * DT_DEV_PIXELPIPE_EXPORT (1)
+ * - Input is the full image, so post shift/scale calculation is accurate.
+ * OTHER (i.e. DT_DEV_PIXELPIPE_THUMBNAIL)
+ * - The input image is a scaled-down version of the full image. The guided
+ * filter and also post scale/shift calculation will deviate, but
+ * the output is displayed very small, so no one will notice.
+ ***/
+
__DT_CLONE_TARGETS__
static
void toneeq_process(dt_iop_module_t *self,
@@ -1244,26 +1426,33 @@ void toneeq_process(dt_iop_module_t *self,
const float *const restrict in = (float *const)ivoid;
float *const restrict out = (float *const)ovoid;
- const size_t width = roi_in->width;
- const size_t height = roi_in->height;
- const size_t num_elem = width * height;
+ const size_t num_elem = roi_out->width * roi_out->height;
// Sanity checks
- if(width < 1 || height < 1) return;
+ if(roi_in->width < 1 || roi_in->height < 1) return;
if(roi_in->width < roi_out->width || roi_in->height < roi_out->height)
return; // input should be at least as large as output
if(piece->colors != 4) return; // we need RGB signal
- // This will be local memory or global cache stored in g
+ // If we requested the full image, this will store the region
+ // that we need to render for the display.
+ float * restrict in_cropped = NULL;
+
+ // This will be either local memory or global cache stored in g
float *restrict luminance = NULL;
int *hires_histogram = NULL;
- // Remember to free stuff that is allocated here
+ // Remember to free local buffers that are allocated in this function
gboolean local_luminance = FALSE;
gboolean local_hires_hist = FALSE;
- printf("toneeq_process, piece type %d, post_align=%d, post_scale=%f, post_shift=%f, roi with=%d, roi_height=%d\n",
- piece->pipe->type, d->post_auto_align, d->post_scale, d->post_shift, roi_in->width, roi_in->height);
+ // Debugging
+ printf("toneeq_process: pipe=%d piece->buf_in->width=%d piece->buf_in->height=%d iwidth=%d iheight=%d roi_in x=%d y=%d w=%d h=%d s=%f roi_out x=%d y=%d w=%d h=%d s=%f radius=%d mode=%d\n",
+ piece->pipe->type, piece->buf_in.width, piece->buf_in.height, piece->iwidth, piece->iheight,
+ roi_in->x, roi_in->y, roi_in->width, roi_in->height, roi_in->scale,
+ roi_out->x, roi_out->y, roi_out->width, roi_out->height, roi_out->scale,
+ d->radius, darktable.color_profiles->mode);
+ // debug_write_buffer_to_file(in, "toneeq_in", width, height, 4, piece->pipe->type);
/**************************************************************************
* Initialization
@@ -1274,15 +1463,12 @@ void toneeq_process(dt_iop_module_t *self,
if(g->pipe_order != piece->module->iop_order)
{
dt_iop_gui_enter_critical_section(self);
-<<<<<<< HEAD
- g->ui_preview_hash = DT_INVALID_HASH;
- g->thumb_preview_hash = DT_INVALID_HASH;
-=======
- g->full_upstream_hash = DT_INVALID_CACHEHASH;
- g->preview_upstream_hash = DT_INVALID_CACHEHASH;
->>>>>>> acabda0cbe (2025-04-06 preview version with post-shift, post_scale, auto-align and curve coloring.)
g->pipe_order = piece->module->iop_order;
- g->luminance_valid = FALSE;
+
+ g->full_upstream_hash = DT_INVALID_HASH;
+ g->preview_upstream_hash = DT_INVALID_HASH;
+ g->prv_luminance_valid = FALSE;
+ g->full_luminance_valid = FALSE;
g->gui_histogram_valid = FALSE;
dt_iop_gui_leave_critical_section(self);
}
@@ -1290,21 +1476,18 @@ void toneeq_process(dt_iop_module_t *self,
if(piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW)
{
// For DT_DEV_PIXELPIPE_PREVIEW, we need to cache the luminace mask
- // and the hires histogram for the GUI thread.
+ // and the hires histogram for GUI.
// Locks are required since GUI reads and writes on that buffer.
- // TODO MF: Is the above comment correct? Except for gui_cache_init
- // there seems to be no place where the GUI writes.
-
// Re-allocate a new buffer if the thumb preview size has changed
dt_iop_gui_enter_critical_section(self);
- if(g->preview_buf_width != width || g->preview_buf_height != height)
+ if(g->preview_buf_width != roi_out->width || g->preview_buf_height != roi_out->width)
{
dt_free_align(g->preview_buf);
g->preview_buf = dt_alloc_align_float(num_elem);
- g->preview_buf_width = width;
- g->preview_buf_height = height;
- g->luminance_valid = FALSE;
+ g->preview_buf_width = roi_out->width;
+ g->preview_buf_height = roi_out->height;
+ g->prv_luminance_valid = FALSE;
}
luminance = g->preview_buf;
@@ -1317,15 +1500,23 @@ void toneeq_process(dt_iop_module_t *self,
// For DT_DEV_PIXELPIPE_FULL, we cache the luminance mask for performance
// but it's not accessed from GUI
// no need for threads lock since no other function is writing/reading that buffer
- // This is also used to quickly switch between the mask display and the main view.
// Re-allocate a new buffer if the full preview size has changed
- if(g->full_buf_width != width || g->full_buf_height != height)
+ if(g->full_buf_width != roi_out->width || g->full_buf_height != roi_out->height)
{
dt_free_align(g->full_buf);
g->full_buf = dt_alloc_align_float(num_elem);
- g->full_buf_width = width;
- g->full_buf_height = height;
+ g->full_buf_width = roi_out->width;
+ g->full_buf_height = roi_out->height;
+ g->full_luminance_valid = FALSE; // TODO: critial needed for this value?
+ }
+
+ // handle scrolling of the main area
+ if (g->full_buf_x != roi_out->x || g->full_buf_y != roi_out->y)
+ {
+ g->full_buf_x = roi_out->x;
+ g->full_buf_y = roi_out->y;
+ g->full_luminance_valid = FALSE;
}
luminance = g->full_buf;
@@ -1333,7 +1524,7 @@ void toneeq_process(dt_iop_module_t *self,
hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
local_hires_hist = TRUE;
}
- else
+ else // Should not happen. GUI, but neither PREVIEW nor FULL.
{
luminance = dt_alloc_align_float(num_elem);
local_luminance = TRUE;
@@ -1343,7 +1534,7 @@ void toneeq_process(dt_iop_module_t *self,
}
else
{
- // no interactive editing/caching : just allocate a local temp buffer
+ // no interactive editing/caching: just allocate local temp buffers
luminance = dt_alloc_align_float(num_elem);
local_luminance = TRUE;
hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
@@ -1358,40 +1549,48 @@ void toneeq_process(dt_iop_module_t *self,
}
// The values from d are set by the user.
- // If the user requested auto-alignment, these will be changed.
+ // If the user requested auto-alignment, these will be changed later.
float post_scale = d->post_scale;
float post_shift = d->post_shift;
+ d->radius = adjust_radius_to_scale(piece, roi_in, piece->iwidth, piece->iheight);
+
+ printf("toneeq_process (%d) after adjust_radius_to_scale: d->radius=%d roi in %d %d %d %d scale %f iwidth %d iheight %d\n", piece->pipe->type, d->radius,
+ roi_in->x, roi_in->y, roi_in->width, roi_in->height, roi_in->scale,
+ piece->iwidth, piece->iheight);
+
/**************************************************************************
* Compute the luminance mask
**************************************************************************/
if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW))
{
- // PREVIEW sees the whole image (at a lower resolution).
- // PREVIEW needs to store the luminance mask, hires_histogram and deciles
- // for GUI access.
- // PREVIEW also computes post_scale and post_shift for FULL.
+ // DT_DEV_PIXELPIPE_PREVIEW:
+ // - Sees the whole image, but at a lower resolution and blurry.
+ // - Needs to store the luminance mask, hires_histogram and deciles
+ // for GUI.
- // We use the upstream hash to check if the upstream pipe has changed,
- // which requires us to re-compute this pipe's luminance mask.
+ // We use the upstream hash to check if the upstream pipe has changed.
+ // If this is the case, we need to re-compute the luminance mask.
const dt_hash_t current_upstream_hash
= dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
dt_iop_gui_enter_critical_section(self);
const dt_hash_t saved_upstream_hash = g->preview_upstream_hash;
- const gboolean luminance_valid = g->luminance_valid;
+ const gboolean prv_luminance_valid = g->prv_luminance_valid;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
- luminance_valid);
+ printf("toneeq_process GUI PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" prv_luminance_valid=%d\n",
+ current_upstream_hash, saved_upstream_hash, prv_luminance_valid);
- if(saved_upstream_hash != current_upstream_hash || !luminance_valid)
+ if(saved_upstream_hash != current_upstream_hash || !prv_luminance_valid)
{
/* compute only if upstream pipe state has changed */
+
dt_iop_gui_enter_critical_section(self);
g->preview_upstream_hash = current_upstream_hash;
g->gui_histogram_valid = FALSE;
- compute_luminance_mask(in, luminance, width, height, d,
+
+ compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d,
TRUE, // Also compute an image (not mask!) histogram for coloring the curve
g->image_hires_histogram,
&g->image_histogram_first_decile, &g->image_histogram_last_decile,
@@ -1399,124 +1598,188 @@ void toneeq_process(dt_iop_module_t *self,
// Histogram and deciles
compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
- &g->histogram_first_decile, &g->histogram_last_decile,
+ &g->prv_histogram_first_decile, &g->prv_histogram_last_decile,
piece->pipe->type);
// GUI can assume that mask, histogram and deciles are valid
- g->luminance_valid = TRUE;
-
- compute_auto_post_scale_shift(&post_scale, &post_shift,
- d->post_auto_align,
- g->histogram_first_decile, g->histogram_last_decile,
- piece->pipe->type);
-
- // save for FULL
- g->post_scale_value = post_scale;
- g->post_shift_value = post_shift;
-
+ g->prv_luminance_valid = TRUE;
dt_iop_gui_leave_critical_section(self);
}
- else
- {
- // No need to re-compute mask, histogram, deciles.
- // We re-use stored deciles for auto alignment.
- dt_iop_gui_enter_critical_section(self);
- compute_auto_post_scale_shift(&post_scale, &post_shift,
- d->post_auto_align,
- g->histogram_first_decile, g->histogram_last_decile,
- piece->pipe->type);
+ dt_iop_gui_enter_critical_section(self);
+ // If requested by the user, pipe-local values for post scale/shift are calculated.
+ // These are off, but they are only used for the small preview image.
+ compute_auto_post_scale_shift(d->post_auto_align,
+ g->prv_histogram_first_decile, g->prv_histogram_last_decile,
+ &post_scale, &post_shift,
+ piece->pipe->type);
+ dt_iop_gui_leave_critical_section(self);
- // for FULL
- g->post_scale_value = post_scale;
- g->post_shift_value = post_shift;
+ printf("toneeq_process PIXELPIPE_PREVIEW (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f d->post_shift=%f final post_scale=%f final post_shift=%f hash=%"PRIu64" saved_hash=%"PRIu64" prv_luminance_valid=%d \n",
+ piece->pipe->type,
+ roi_in->width, roi_in->height,
+ d->post_auto_align, d->post_scale, d->post_shift,
+ post_scale, post_shift,
+ current_upstream_hash, saved_upstream_hash, prv_luminance_valid
+ );
- dt_iop_gui_leave_critical_section(self);
- }
// TODO MF: Not completely sure in which cases this must be called.
// Assumption is once per output image change by this module and
// only when the GUI is active.
dt_dev_pixelpipe_cache_invalidate_later(piece->pipe, self->iop_order);
- // Sync hash to make FULL wait if it needs auto-alignment
- // TODO MF: Is it necessary to do this in two steps to prevent DT getting stuck in a race condition?
- const dt_hash_t sync_hash = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL);
- dt_iop_gui_enter_critical_section(self);
- g->sync_hash = sync_hash;
- dt_iop_gui_leave_critical_section(self);
-
}
else if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL))
{
- // FULL may only see a part of the image if the user has zoomed in.
- // We need to compute a luminance mask for this pipe and cache it for
- // quick reuse (i.e. if the user only changes the curve).
- // But we can not compute statistics like histograms or deciles here,
- // so we re-use values that PREVIEW has stored in g.
-
- // We use the upstream hash to check if the upstream pipe has changed,
- // which requires us to re-compute this pipe's luminance mask.
- // TODO: Is this correct? We take the same hash as in PREVIEW, using self->dev->preview_pipe,
- // assuming that this can also be used in FULL to detect upstream changes.
- const dt_hash_t current_upstream_hash
- = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
- dt_iop_gui_enter_critical_section(self);
- const dt_hash_t saved_upstream_hash = g->full_upstream_hash;
- const gboolean luminance_valid = g->luminance_valid;
- dt_iop_gui_leave_critical_section(self);
+ if(roi_in->width == piece->buf_in.width && roi_in->height == piece->buf_in.height && fabs(roi_in->scale - 1.0f) < 0.00001)
+ {
+ // We are in PIXELPIPE_FULL and have the full image as our roi.
- printf("toneeq_process GUI FULL: hash=%"PRIu64" saved_hash=%"PRIu64" luminance_valid=%d\n", current_upstream_hash, saved_upstream_hash,
- luminance_valid);
+ // get a crop of the image to be able to display the requested output
+ in_cropped = dt_alloc_align_float(4 * num_elem);
+ dt_iop_clip_and_zoom(in_cropped, in, roi_out, roi_in); // out, in, roi_out, roi_in
+
+ // We use the upstream hash to check if the upstream pipe has changed.
+ // If this is the case, we need to re-compute the luminance mask.
+ // TODO MF: Is it correct to use a hash of the preview pipe here?
+ const dt_hash_t current_upstream_hash
+ = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
- // Re-compute if the upstream state has changed
- if(current_upstream_hash != saved_upstream_hash || !luminance_valid)
- {
- /* compute only if upstream pipe state has changed */
dt_iop_gui_enter_critical_section(self);
- g->full_upstream_hash = current_upstream_hash;
+ const dt_hash_t saved_upstream_hash = g->full_upstream_hash;
+ const gboolean full_luminance_valid = g->full_luminance_valid;
dt_iop_gui_leave_critical_section(self);
- compute_luminance_mask(in, luminance, width, height, d,
- FALSE, NULL, NULL, NULL,
- piece->pipe->type);
- }
+ printf("toneeq_process GUI PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" full_luminance_valid=%d\n",
+ current_upstream_hash, saved_upstream_hash, full_luminance_valid);
- if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM)
- {
- printf("toneeq_process GUI FULL: waiting for sync\n");
- // Wait for PREVIEW to calculate automatic post scale/shift
- if(!dt_dev_sync_pixelpipe_hash(self->dev, piece->pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, &self->gui_lock, &g->sync_hash)) {
- dt_control_log(_("inconsistent output"));
- printf("toneeq_process GUI FULL: sync failed\n");
- }
- else
+ if(saved_upstream_hash != current_upstream_hash || !full_luminance_valid)
{
- printf("toneeq_process GUI FULL: synced\n");
+ /* compute only if upstream pipe state has changed */
+
+ dt_iop_gui_enter_critical_section(self);
+ g->full_upstream_hash = current_upstream_hash;
+
+ // adjust radius to crop
+ d->radius = adjust_radius_to_scale(piece, roi_out, piece->iwidth, piece->iheight);
+
+ // Luminance mask, but no image histogram
+ compute_luminance_mask(in_cropped, luminance, roi_out->width, roi_out->height, d,
+ FALSE, NULL, NULL, NULL,
+ piece->pipe->type);
+
+ // Calculate post scale/shift using the full image (same calculation as
+ // during export)
+ compute_mask_stats_from_full_image(piece, in, roi_in, d,
+ &g->full_histogram_first_decile, &g->full_histogram_last_decile);
+
+ g->full_luminance_valid = TRUE;
+ dt_iop_gui_leave_critical_section(self);
}
+ compute_auto_post_scale_shift(d->post_auto_align,
+ g->full_histogram_first_decile, g->full_histogram_last_decile,
+ &post_scale, &post_shift,
+ piece->pipe->type);
+
dt_iop_gui_enter_critical_section(self);
- post_scale = g->post_scale_value;
- post_shift = g->post_shift_value;
+ g->post_scale_value = post_scale;
+ g->post_shift_value = post_shift;
dt_iop_gui_leave_critical_section(self);
+
+ printf("toneeq_process GUI PIXELPIPE_FULL (%d), FULL ROI: roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
+ "d->post_shift=%f final post_scale=%f final post_shift=%f\n",
+ piece->pipe->type, roi_in->width, roi_in->height, d->post_auto_align, d->post_scale, d->post_shift,
+ post_scale, post_shift);
+ }
+ else // not full image as roi
+ {
+ // FULL may only see a part of the image if the user has zoomed in.
+ // We need to compute a luminance mask for this pipe and cache it for
+ // quick reuse (i.e. if the user only changes the curve).
+ // But we can not compute deciles here.
+
+ // roi_in and roi_out must be the same
+ assert(roi_in->x == roi_out->x
+ && roi_in->y == roi_out->y
+ && roi_in->width == roi_out->width
+ && roi_in->height == roi_out->height
+ && roi_in->scale == roi_out->scale);
+
+ // Upstream change detection
+ const dt_hash_t current_upstream_hash
+ = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
+
+ dt_iop_gui_enter_critical_section(self);
+ const dt_hash_t saved_upstream_hash = g->full_upstream_hash;
+ const gboolean full_luminance_valid = g->full_luminance_valid;
+ dt_iop_gui_leave_critical_section(self);
+
+ printf("toneeq_process GUI PIXELPIPE_FULL: hash=%" PRIu64 " saved_hash=%" PRIu64 " full_luminance_valid=%d\n",
+ current_upstream_hash, saved_upstream_hash, full_luminance_valid);
+
+ // If the upstream state has changed, re-compute the mask of the displayed image part
+ if(current_upstream_hash != saved_upstream_hash || !full_luminance_valid)
+ {
+ /* compute only if upstream pipe state has changed */
+ dt_iop_gui_enter_critical_section(self);
+ g->full_upstream_hash = current_upstream_hash;
+ dt_iop_gui_leave_critical_section(self);
+
+ compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d, FALSE, NULL, NULL, NULL, piece->pipe->type);
+ g->full_luminance_valid = TRUE;
+ }
+
+ if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM) {
+ // Calculate post scale/shift using previously saved deciles of the full mask
+
+ dt_iop_gui_enter_critical_section(self);
+ float histogram_first_decile = g->full_histogram_first_decile;
+ float histogram_last_decile = g->full_histogram_last_decile;
+ dt_iop_gui_leave_critical_section(self);
+
+ compute_auto_post_scale_shift(d->post_auto_align,
+ histogram_first_decile, histogram_last_decile,
+ &post_scale, &post_shift,
+ piece->pipe->type);
+ }
+
+ printf("toneeq_process GUI PIXELPIPE_FULL (%d), no full roi: roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
+ "d->post_shift=%f final post_scale=%f final post_shift=%f\n",
+ piece->pipe->type, roi_in->width, roi_in->height, d->post_auto_align, d->post_scale, d->post_shift,
+ post_scale, post_shift);
}
- } else {
- // no caching path : compute no matter what
- // TODO MF: Post scale/shift are calculated with local data.
- // Not guraranteed to be identical to what PREVIEW saw.
+ }
+ else
+ {
+ // No caching path: compute no matter what
+ // We are in PIXELPIPE_EXPORT or PIXELPIPE_THUMBNAIL
- compute_luminance_mask(in, luminance, width, height, d,
+ compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d,
FALSE, NULL, NULL, NULL,
piece->pipe->type);
- float histogram_first_decile, histogram_last_decile;
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
- &histogram_first_decile, &histogram_last_decile,
- piece->pipe->type);
+ // Compute post shift/scale the same way as in PIXELPIPE_FULL.
+ float histogram_first_decile = 0;
+ float histogram_last_decile = 0;
- compute_auto_post_scale_shift(&post_scale, &post_shift, d->post_auto_align,
- histogram_first_decile, histogram_last_decile, piece->pipe->type);
+ if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM) {
+ compute_mask_stats_from_full_image(piece, in, roi_in, d,
+ &histogram_first_decile, &histogram_last_decile);
+ }
+ compute_auto_post_scale_shift(d->post_auto_align,
+ histogram_first_decile, histogram_last_decile,
+ &post_scale, &post_shift,
+ piece->pipe->type);
+
+ printf("toneeq_process NO GUI PIXELPIPE (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
+ "d->post_shift=%f final post_scale=%f final post_shift=%f\n",
+ piece->pipe->type,
+ roi_in->width, roi_in->height,
+ d->post_auto_align, d->post_scale, d->post_shift,
+ post_scale, post_shift);
}
/**************************************************************************
@@ -1524,20 +1787,40 @@ void toneeq_process(dt_iop_module_t *self,
**************************************************************************/
if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) && g->mask_display)
{
- display_luminance_mask(in, luminance, out,
- roi_in, roi_out,
- post_scale, post_shift,
- piece->pipe->type);
+ if (in_cropped) { // not elegant, but necessary because buffers are "restrict"
+ display_luminance_mask(in_cropped, luminance, out, roi_out, roi_out,
+ post_scale, post_shift, piece->pipe->type);
+ }
+ else
+ {
+ display_luminance_mask(in, luminance, out, roi_in, roi_out, post_scale,
+ post_shift, piece->pipe->type);
+ }
piece->pipe->mask_display = DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
}
else
{
- compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
- post_scale, post_shift,
- piece->pipe->type);
- apply_toneequalizer(in, luminance, out,
- roi_in, roi_out,
- d, piece->pipe->type);
+ if (in_cropped) {
+ printf("toneeq_process: in_cropped roi %d %d %d %d\n",
+ roi_out->x, roi_out->y, roi_out->width, roi_out->height);
+ compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+ post_scale, post_shift,
+ piece->pipe->type);
+ apply_toneequalizer(in_cropped, luminance, out,
+ roi_out, roi_out,
+ d, piece->pipe->type);
+ }
+ else
+ {
+ printf("toneeq_process: in roi %d %d %d %d\n",
+ roi_in->x, roi_in->y, roi_in->width, roi_in->height);
+ compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+ post_scale, post_shift,
+ piece->pipe->type);
+ apply_toneequalizer(in, luminance, out,
+ roi_in, roi_out,
+ d, piece->pipe->type);
+ }
}
/**************************************************************************
@@ -1549,6 +1832,9 @@ void toneeq_process(dt_iop_module_t *self,
if(local_hires_hist) {
dt_free_align(hires_histogram);
}
+ if (in_cropped) {
+ dt_free_align(in_cropped);
+ }
}
@@ -1610,16 +1896,37 @@ void modify_roi_in(dt_iop_module_t *self,
const dt_iop_roi_t *roi_out,
dt_iop_roi_t *roi_in)
{
- // Pad the zoomed-in view to avoid weird stuff with local averages
- // at the borders of the preview
-
dt_iop_toneequalizer_data_t *const d = piece->data;
+ dt_iop_toneequalizer_gui_data_t *const g = self->gui_data;
- // Get the scaled window radius for the box average
- const int max_size = (piece->iwidth > piece->iheight) ? piece->iwidth : piece->iheight;
- const float diameter = d->blending * max_size * roi_in->scale;
- const int radius = (int)((diameter - 1.0f) / ( 2.0f));
- d->radius = radius;
+ // If we are in PIXELPIPE_FULL with GUI, the upstream image or mask
+ // parameters have changed and the user requested auto post scale/shift,
+ // we need the full image as our roi.
+ if(self->dev->gui_attached && piece->pipe->type & DT_DEV_PIXELPIPE_FULL)
+ {
+ const dt_hash_t current_upstream_hash
+ = dt_dev_hash_plus(self->dev, self->dev->preview_pipe, self->iop_order, DT_DEV_TRANSFORM_DIR_BACK_EXCL);
+
+ dt_iop_gui_enter_critical_section(self);
+ const dt_hash_t saved_upstream_hash = g->full_upstream_hash;
+ gboolean full_luminance_valid = g->full_luminance_valid;
+ dt_iop_gui_leave_critical_section(self);
+
+ // Auto-align is on and either has the upstream image changed or the user
+ // modified some option that requires re-calculation of the mask
+ if(d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM
+ && (current_upstream_hash != saved_upstream_hash || !full_luminance_valid))
+ {
+ // We need the full image as our roi
+ roi_in->x = 0;
+ roi_in->y = 0;
+ roi_in->width = piece->buf_in.width;
+ roi_in->height = piece->buf_in.height;
+ roi_in->scale = 1.0f;
+
+ printf("modify_roi_in: full image for auto-alignment\n");
+ }
+ }
}
@@ -1763,13 +2070,8 @@ static void gui_cache_init(dt_iop_module_t *self)
if(g == NULL) return;
dt_iop_gui_enter_critical_section(self);
-<<<<<<< HEAD
- g->ui_preview_hash = DT_INVALID_HASH;
- g->thumb_preview_hash = DT_INVALID_HASH;
-=======
- g->full_upstream_hash = DT_INVALID_CACHEHASH;
- g->preview_upstream_hash = DT_INVALID_CACHEHASH;
->>>>>>> acabda0cbe (2025-04-06 preview version with post-shift, post_scale, auto-align and curve coloring.)
+ g->full_upstream_hash = DT_INVALID_HASH;
+ g->preview_upstream_hash = DT_INVALID_HASH;
g->max_histogram = 1;
g->scale = 1.0f;
g->sigma = sqrtf(2.0f);
@@ -1777,7 +2079,8 @@ static void gui_cache_init(dt_iop_module_t *self)
g->image_EV_per_UI_sample = 0.00001; // In case no value is calculated yet, use something small, but not 0
g->interpolation_valid = FALSE; // TRUE if the interpolation_matrix is ready
- g->luminance_valid = FALSE; // TRUE if the luminance cache is ready
+ g->prv_luminance_valid = FALSE; // TRUE if the luminance cache is ready
+ g->full_luminance_valid = FALSE; // TRUE if the luminance cache is ready
g->gui_histogram_valid = FALSE; // TRUE if the histogram cache and stats are ready
g->gui_curve_valid = FALSE; // TRUE if the gui_curve_lut is ready
g->graph_valid = FALSE; // TRUE if the UI graph view is ready
@@ -1798,6 +2101,8 @@ static void gui_cache_init(dt_iop_module_t *self)
g->full_buf = NULL;
g->full_buf_width = 0;
g->full_buf_height = 0;
+ g->full_buf_x = 0;
+ g->full_buf_y = 0;
g->desc = NULL;
g->layout = NULL;
@@ -1818,9 +2123,10 @@ static void invalidate_luminance_cache(dt_iop_module_t *const self)
dt_iop_toneequalizer_gui_data_t *const restrict g = self->gui_data;
dt_iop_gui_enter_critical_section(self);
- g->luminance_valid = FALSE;
- g->preview_upstream_hash = DT_INVALID_CACHEHASH;
- g->full_upstream_hash = DT_INVALID_CACHEHASH;
+ g->prv_luminance_valid = FALSE;
+ g->preview_upstream_hash = DT_INVALID_HASH;
+ g->full_luminance_valid = FALSE;
+ g->full_upstream_hash = DT_INVALID_HASH;
g->gui_histogram_valid = FALSE;
g->max_histogram = 1;
@@ -1951,8 +2257,8 @@ void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_
dt_iop_toneequalizer_data_t *d = piece->data;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
- printf("commit_params pipe=%d align=%d p->post_scale=%f p->post_shift=%f d->post_scale=%f d->post_shift=%f\n",
- pipe->type, p->post_auto_align, p->post_scale, p->post_shift, d->post_scale, d->post_shift);
+ // printf("commit_params pipe=%d align=%d p->post_scale=%f p->post_shift=%f d->post_scale=%f d->post_shift=%f\n",
+ // pipe->type, p->post_auto_align, p->post_scale, p->post_shift, d->post_scale, d->post_shift);
// Trivial params passing
d->lum_estimator = p->lum_estimator;
@@ -1972,7 +2278,6 @@ void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_
d->contrast_boost = exp2f(p->contrast_boost);
d->exposure_boost = exp2f(p->exposure_boost);
- // printf("commit_params: post_auto_align %d\n", p->post_auto_align);
d->post_auto_align = p->post_auto_align;
d->post_scale = p->post_scale;
d->post_shift = p->post_shift;
@@ -2021,7 +2326,7 @@ static inline void compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLES
float histogram_shift,
int *max_histogram)
{
- printf("compute_gui_histogram\n");
+ printf("compute_gui_histogram scale=%f shift=%f\n", histogram_scale, histogram_shift);
// (Re)init the histogram
memset(histogram, 0, sizeof(int) * UI_HISTO_SAMPLES);
@@ -2060,10 +2365,20 @@ static inline void update_gui_histogram(dt_iop_module_t *const self)
if(g == NULL) return;
dt_iop_gui_enter_critical_section(self);
- if(!g->gui_histogram_valid && g->luminance_valid)
+ if(!g->gui_histogram_valid && g->prv_luminance_valid && g->full_luminance_valid)
{
- compute_auto_post_scale_shift(&p->post_scale, &p->post_shift, p->post_auto_align, g->histogram_first_decile, g->histogram_last_decile, 999);
- compute_gui_histogram(g->hires_histogram, g->histogram, p->post_scale, p->post_shift, &g->max_histogram);
+ // TODO MF: Pixelpipe full might not be ready yet, in this case we
+ // don't have auto-align values. Right now I have no idea
+ // how to solve this, other to draw no histogram at all.
+
+ compute_auto_post_scale_shift(p->post_auto_align,
+ g->full_histogram_first_decile, g->full_histogram_last_decile,
+ &p->post_scale, &p->post_shift,
+ 999);
+
+ compute_gui_histogram(g->hires_histogram, g->histogram,
+ p->post_scale, p->post_shift,
+ &g->max_histogram);
// Computation of "image_EV_per_UI_sample"
// The graph shows 8EV, but when we align the histogram, we consider 6EV [-7; -1] ("target")
@@ -2076,12 +2391,12 @@ static inline void update_gui_histogram(dt_iop_module_t *const self)
// The histogram shows mask EV, but for evaluating curve steepness, we need image EVs
const float mask_to_image = (g->image_histogram_last_decile - g->image_histogram_first_decile)
- / (g->histogram_last_decile - g->histogram_first_decile);
+ / (g->prv_histogram_last_decile - g->prv_histogram_first_decile);
g->image_EV_per_UI_sample = (mask_EV_of_target * mask_to_image * target_to_full) / (float)UI_HISTO_SAMPLES;
if (g->show_two_histograms)
- compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->max_image_histogram);
+ compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->image_max_histogram);
g->gui_histogram_valid = TRUE;
}
@@ -2213,12 +2528,16 @@ void gui_changed(dt_iop_module_t *self,
{
// We may have switched from a more automatic to a less automatic mode.
// Copy the automatically determined parameters to the GUI sliders.
+
+ p->post_scale = g->post_scale_value;
+ p->post_shift = g->post_shift_value;
+
++darktable.gui->reset;
dt_bauhaus_slider_set(g->post_scale, p->post_scale);
dt_bauhaus_slider_set(g->post_shift, p->post_shift);
--darktable.gui->reset;
- invalidate_lut_and_histogram(self);
+ invalidate_luminance_cache(self);
show_guiding_controls(self);
}
}
@@ -2271,7 +2590,7 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
return;
}
- if(!g->luminance_valid || self->dev->full.pipe->processing || !g->gui_histogram_valid)
+ if(!g->prv_luminance_valid || self->dev->full.pipe->processing || !g->gui_histogram_valid)
{
dt_control_log(_("wait for the preview to finish recomputing"));
return;
@@ -2288,8 +2607,8 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
update_gui_histogram(self);
// calculate exposure correction
- const float fd_new = exp2f(g->histogram_first_decile);
- const float ld_new = exp2f(g->histogram_last_decile);
+ const float fd_new = exp2f(g->prv_histogram_first_decile);
+ const float ld_new = exp2f(g->prv_histogram_last_decile);
const float e = exp2f(p->exposure_boost);
const float c = exp2f(p->contrast_boost);
// revert current transformation
@@ -2336,7 +2655,7 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
return;
}
- if(!g->luminance_valid || self->dev->full.pipe->processing || !g->gui_histogram_valid)
+ if(!g->prv_luminance_valid || self->dev->full.pipe->processing || !g->gui_histogram_valid)
{
dt_control_log(_("wait for the preview to finish recomputing"));
return;
@@ -2350,8 +2669,8 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
update_gui_histogram(self);
// calculate contrast correction
- const float fd_new = exp2f(g->histogram_first_decile);
- const float ld_new = exp2f(g->histogram_last_decile);
+ const float fd_new = exp2f(g->prv_histogram_first_decile);
+ const float ld_new = exp2f(g->prv_histogram_last_decile);
const float e = exp2f(p->exposure_boost);
float c = exp2f(p->contrast_boost);
// revert current transformation
@@ -2687,7 +3006,7 @@ int mouse_moved(dt_iop_module_t *self,
dt_iop_gui_leave_critical_section(self);
// store the actual exposure too, to spare I/O op
- if(g->cursor_valid && !dev->full.pipe->processing && g->luminance_valid) {
+ if(g->cursor_valid && !dev->full.pipe->processing && g->prv_luminance_valid) {
const float lum = log2f(_luminance_from_thumb_preview_buf(self));
g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
}
@@ -2807,7 +3126,7 @@ int scrolled(dt_iop_module_t *self,
dt_iop_gui_enter_critical_section(self);
const gboolean fail = !g->cursor_valid
- || !g->luminance_valid
+ || !g->prv_luminance_valid
|| !g->interpolation_valid
|| !g->user_param_valid
|| dev->full.pipe->processing
@@ -2989,7 +3308,7 @@ void gui_post_expose(dt_iop_module_t *self,
return;
// re-read the exposure in case it has changed
- if(g->luminance_valid && self->enabled) {
+ if(g->prv_luminance_valid && self->enabled) {
const float lum = log2f(_luminance_from_thumb_preview_buf(self));
g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
}
@@ -3005,7 +3324,7 @@ void gui_post_expose(dt_iop_module_t *self,
float correction = 0.0f;
float exposure_out = 0.0f;
float luminance_out = 0.0f;
- if(g->luminance_valid && self->enabled)
+ if(g->prv_luminance_valid && self->enabled)
{
// Get the corresponding exposure
exposure_in = g->cursor_exposure;
@@ -3080,7 +3399,7 @@ void gui_post_expose(dt_iop_module_t *self,
pango_cairo_context_set_resolution(pango_layout_get_context(layout), darktable.gui->dpi);
// Build text object
- if(g->luminance_valid && self->enabled)
+ if(g->prv_luminance_valid && self->enabled)
snprintf(text, sizeof(text), _("%+.1f EV"), exposure_in);
else
snprintf(text, sizeof(text), "? EV");
@@ -3107,7 +3426,7 @@ void gui_post_expose(dt_iop_module_t *self,
pango_font_description_free(desc);
g_object_unref(layout);
- if(g->luminance_valid && self->enabled)
+ if(g->prv_luminance_valid && self->enabled)
{
// Search for nearest node in graph and highlight it
const float radius_threshold = 0.45f;
@@ -3494,7 +3813,7 @@ static gboolean area_draw(GtkWidget *widget,
dt_iop_gui_leave_critical_section(self);
// Refresh cached UI elements
- update_gui_histogram(self);
+ update_gui_histogram(self); // TODO MF: Delay until both PREVIEW and FULL pixelpipes are ready
curve_interpolation(self);
// The colors depend on the histogram and the curve
@@ -3552,18 +3871,18 @@ static gboolean area_draw(GtkWidget *widget,
{
// the x range is [-8;+0] EV
const float x_temp = (8.0 * (float)k / (float)(UI_HISTO_SAMPLES - 1)) - 8.0;
- const float y_temp = fast_clamp((float)(g->image_histogram[k]) / (float)(g->max_image_histogram), -1.0f, 1.0f) * 0.96;
+ const float y_temp = fast_clamp((float)(g->image_histogram[k]) / (float)(g->image_max_histogram), -1.0f, 1.0f) * 0.96;
cairo_line_to(g->cr, (x_temp + 8.0) * g->graph_width / 8.0,
y_temp * histo_height);
//if (k % 5 == 0)
- // printf("g->image_histogram[%d] = %d, max_image_histogram = %d\n", k, g->image_histogram[k], g->max_image_histogram);
+ // printf("g->image_histogram[%d] = %d, image_max_histogram = %d\n", k, g->image_histogram[k], g->image_max_histogram);
}
cairo_line_to(g->cr, g->graph_width, 0);
cairo_close_path(g->cr);
cairo_fill(g->cr);
}
- if(post_scale_shift(g->histogram_last_decile, p->post_scale, p->post_shift) > -0.1f)
+ if(post_scale_shift(g->prv_histogram_last_decile, p->post_scale, p->post_shift) > -0.1f)
{
// histogram overflows controls in highlights : display warning
cairo_save(g->cr);
@@ -3575,7 +3894,7 @@ static gboolean area_draw(GtkWidget *widget,
cairo_restore(g->cr);
}
- if(post_scale_shift(g->histogram_first_decile, p->post_scale, p->post_shift) < -7.9f)
+ if(post_scale_shift(g->prv_histogram_first_decile, p->post_scale, p->post_shift) < -7.9f)
{
// histogram overflows controls in lowlights : display warning
cairo_save(g->cr);
@@ -3705,73 +4024,73 @@ static gboolean area_draw(GtkWidget *widget,
}
-static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
- cairo_t *crf,
- dt_iop_module_t *self)
-{
- // Draw the widget equalizer view
- dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
-
- update_gui_histogram(self);
-
- GtkAllocation allocation;
- gtk_widget_get_allocation(widget, &allocation);
- cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
- allocation.width, allocation.height);
- cairo_t *cr = cairo_create(cst);
-
- // draw background
- set_color(cr, darktable.bauhaus->graph_bg);
- cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
- cairo_fill_preserve(cr);
- cairo_clip(cr);
-
- dt_iop_gui_enter_critical_section(self);
-
- if(g->gui_histogram_valid)
- {
- // draw histogram span
- const float left = (g->histogram_first_decile + 8.0f) / 8.0f;
- const float right = (g->histogram_last_decile + 8.0f) / 8.0f;
- const float width = (right - left);
- set_color(cr, darktable.bauhaus->inset_histogram);
- cairo_rectangle(cr, left * allocation.width, 0,
- width * allocation.width, allocation.height);
- cairo_fill(cr);
-
- // draw average bar
- set_color(cr, darktable.bauhaus->graph_fg);
- cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
- const float average = ((g->histogram_first_decile + g->histogram_last_decile) / 2.0f + 8.0f) / 8.0f;
- cairo_move_to(cr, average * allocation.width, 0.0);
- cairo_line_to(cr, average * allocation.width, allocation.height);
- cairo_stroke(cr);
-
- // draw clipping bars
- cairo_set_source_rgb(cr, 0.75, 0.50, 0);
- cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(6));
- if(g->histogram_first_decile < -7.9f)
- {
- cairo_move_to(cr, DT_PIXEL_APPLY_DPI(3), 0.0);
- cairo_line_to(cr, DT_PIXEL_APPLY_DPI(3), allocation.height);
- cairo_stroke(cr);
- }
- if(g->histogram_last_decile > - 0.1f)
- {
- cairo_move_to(cr, allocation.width - DT_PIXEL_APPLY_DPI(3), 0.0);
- cairo_line_to(cr, allocation.width - DT_PIXEL_APPLY_DPI(3), allocation.height);
- cairo_stroke(cr);
- }
- }
-
- dt_iop_gui_leave_critical_section(self);
-
- cairo_set_source_surface(crf, cst, 0, 0);
- cairo_paint(crf);
- cairo_destroy(cr);
- cairo_surface_destroy(cst);
- return TRUE;
-}
+// static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
+// cairo_t *crf,
+// dt_iop_module_t *self)
+// {
+// // Draw the widget equalizer view
+// dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+
+// update_gui_histogram(self);
+
+// GtkAllocation allocation;
+// gtk_widget_get_allocation(widget, &allocation);
+// cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
+// allocation.width, allocation.height);
+// cairo_t *cr = cairo_create(cst);
+
+// // draw background
+// set_color(cr, darktable.bauhaus->graph_bg);
+// cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
+// cairo_fill_preserve(cr);
+// cairo_clip(cr);
+
+// dt_iop_gui_enter_critical_section(self);
+
+// if(g->gui_histogram_valid)
+// {
+// // draw histogram span
+// const float left = (g->histogram_first_decile + 8.0f) / 8.0f;
+// const float right = (g->histogram_last_decile + 8.0f) / 8.0f;
+// const float width = (right - left);
+// set_color(cr, darktable.bauhaus->inset_histogram);
+// cairo_rectangle(cr, left * allocation.width, 0,
+// width * allocation.width, allocation.height);
+// cairo_fill(cr);
+
+// // draw average bar
+// set_color(cr, darktable.bauhaus->graph_fg);
+// cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
+// const float average = ((g->histogram_first_decile + g->histogram_last_decile) / 2.0f + 8.0f) / 8.0f;
+// cairo_move_to(cr, average * allocation.width, 0.0);
+// cairo_line_to(cr, average * allocation.width, allocation.height);
+// cairo_stroke(cr);
+
+// // draw clipping bars
+// cairo_set_source_rgb(cr, 0.75, 0.50, 0);
+// cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(6));
+// if(g->histogram_first_decile < -7.9f)
+// {
+// cairo_move_to(cr, DT_PIXEL_APPLY_DPI(3), 0.0);
+// cairo_line_to(cr, DT_PIXEL_APPLY_DPI(3), allocation.height);
+// cairo_stroke(cr);
+// }
+// if(g->histogram_last_decile > - 0.1f)
+// {
+// cairo_move_to(cr, allocation.width - DT_PIXEL_APPLY_DPI(3), 0.0);
+// cairo_line_to(cr, allocation.width - DT_PIXEL_APPLY_DPI(3), allocation.height);
+// cairo_stroke(cr);
+// }
+// }
+
+// dt_iop_gui_leave_critical_section(self);
+
+// cairo_set_source_surface(crf, cst, 0, 0);
+// cairo_paint(crf);
+// cairo_destroy(cr);
+// cairo_surface_destroy(cst);
+// return TRUE;
+// }
static gboolean area_enter_leave_notify(GtkWidget *widget,
@@ -4035,7 +4354,7 @@ static void _develop_preview_pipe_finished_callback(gpointer instance,
switch_cursors(self);
gtk_widget_queue_draw(GTK_WIDGET(g->area));
- gtk_widget_queue_draw(GTK_WIDGET(g->bar));
+ // gtk_widget_queue_draw(GTK_WIDGET(g->bar));
}
@@ -4045,6 +4364,7 @@ static void _develop_ui_pipe_finished_callback(gpointer instance,
printf("ui pipe finished callback\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
+
switch_cursors(self);
}
@@ -4069,14 +4389,6 @@ void gui_init(dt_iop_module_t *self)
gui_cache_init(self);
- static dt_action_def_t notebook_def = { };
- g->notebook = dt_ui_notebook_new(¬ebook_def);
- dt_action_define_iop(self, NULL, N_("page"), GTK_WIDGET(g->notebook), ¬ebook_def);
-
- // Curve view (former "advanced" page)
- self->widget = dt_ui_notebook_page(g->notebook, N_("curve"), NULL);
- gtk_widget_set_vexpand(GTK_WIDGET(self->widget), TRUE);
-
// g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
g->area = GTK_DRAWING_AREA(dt_ui_resize_wrap(NULL,
@@ -4088,7 +4400,7 @@ void gui_init(dt_iop_module_t *self)
g_object_set_data(G_OBJECT(wrapper), "iop-instance", self);
gtk_widget_set_name(GTK_WIDGET(wrapper), "toneeqgraph");
dt_action_define_iop(self, NULL, N_("graph"), GTK_WIDGET(wrapper), NULL);
- gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(wrapper), TRUE, TRUE, 0);
+ //gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(wrapper), TRUE, TRUE, 0);
gtk_widget_add_events(GTK_WIDGET(g->area),
GDK_POINTER_MOTION_MASK | darktable.gui->scroll_mask
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
@@ -4109,6 +4421,14 @@ void gui_init(dt_iop_module_t *self)
G_CALLBACK(area_scroll), self);
gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double-click to reset the curve"));
+ static dt_action_def_t notebook_def = { };
+ g->notebook = dt_ui_notebook_new(¬ebook_def);
+ dt_action_define_iop(self, NULL, N_("page"), GTK_WIDGET(g->notebook), ¬ebook_def);
+
+ // Main view (former "advanced" page)
+ self->widget = dt_ui_notebook_page(g->notebook, N_("main"), NULL);
+ gtk_widget_set_vexpand(GTK_WIDGET(self->widget), TRUE);
+
g->post_auto_align = dt_bauhaus_combobox_from_params(self, "post_auto_align");
gtk_widget_set_tooltip_text(g->post_auto_align, _("automatically set the mask exposure/contrast"));
@@ -4124,6 +4444,8 @@ void gui_init(dt_iop_module_t *self)
(g->post_shift,
_("set the mask exposure / shift the histogram"));
+ self->widget = dt_ui_notebook_page(g->notebook, N_("curve"), NULL);
+
g->smoothing = dt_bauhaus_slider_new_with_range(self, -2.33f, +1.67f, 0, 0.0f, 2);
dt_bauhaus_slider_set_soft_range(g->smoothing, -1.0f, 1.0f);
dt_bauhaus_widget_set_label(g->smoothing, NULL, N_("curve smoothing"));
@@ -4135,12 +4457,21 @@ void gui_init(dt_iop_module_t *self)
gtk_box_pack_start(GTK_BOX(self->widget), g->smoothing, FALSE, FALSE, 0);
g_signal_connect(G_OBJECT(g->smoothing), "value-changed", G_CALLBACK(smoothing_callback), self);
-
// sliders section (former "simple" page)
dt_gui_new_collapsible_section(&g->sliders_section, "plugins/darkroom/toneequal/expand_sliders", _("sliders"),
GTK_BOX(self->widget), DT_ACTION(self));
gtk_widget_set_tooltip_text(g->sliders_section.expander, _("sliders"));
+ // TODO MF dirty hack:
+ // dt_gui_new_collapsible_section always uses gtk_box_pack_end to align the collapsible
+ // section at the bottom of the parent container.
+ // I want the collapsible section to be aligned at the top, therefore I remove it from the
+ // parent again and pack it manually.
+ g_object_ref(g->sliders_section.expander);
+ gtk_container_remove(GTK_CONTAINER(self->widget), g->sliders_section.expander);
+ gtk_box_pack_start(GTK_BOX(self->widget), g->sliders_section.expander, FALSE, FALSE, 0);
+ g_object_unref(g->sliders_section.expander);
+
self->widget = GTK_WIDGET(g->sliders_section.container);
g->noise = dt_bauhaus_slider_from_params(self, "noise");
@@ -4183,72 +4514,81 @@ void gui_init(dt_iop_module_t *self)
// Masking options
self->widget = dt_ui_notebook_page(g->notebook, N_("masking"), NULL);
+ GtkWidget *masking_page = self->widget;
+
+ // guided filter section
+ dt_gui_new_collapsible_section(&g->guided_filter_section, "plugins/darkroom/toneequal/expand_sliders",
+ _("guided filter"), GTK_BOX(masking_page), DT_ACTION(self));
+ gtk_widget_set_tooltip_text(g->guided_filter_section.expander, _("guided filter"));
+
+ // Hack to make the collapsible section align at the top
+ g_object_ref(g->guided_filter_section.expander);
+ gtk_container_remove(GTK_CONTAINER(masking_page), g->guided_filter_section.expander);
+ gtk_box_pack_start(GTK_BOX(masking_page), g->guided_filter_section.expander, FALSE, FALSE, 0);
+ g_object_unref(g->guided_filter_section.expander);
+
+ self->widget = GTK_WIDGET(g->guided_filter_section.container);
g->lum_estimator = dt_bauhaus_combobox_from_params(self, "lum_estimator");
- gtk_widget_set_tooltip_text
- (g->lum_estimator,
- _("preview the mask and chose the estimator that gives you the\n"
- "higher contrast between areas to dodge and areas to burn"));
+ gtk_widget_set_tooltip_text(g->lum_estimator, _("preview the mask and chose the estimator that gives you the\n"
+ "higher contrast between areas to dodge and areas to burn"));
g->filter = dt_bauhaus_combobox_from_params(self, N_("filter"));
dt_bauhaus_widget_set_label(g->filter, NULL, N_("preserve details"));
- gtk_widget_set_tooltip_text
- (g->filter,
- _("'no' affects global and local contrast (safe if you only add contrast)\n"
- "'guided filter' only affects global contrast and tries to preserve local contrast\n"
- "'averaged guided filter' is a geometric mean of 'no' and 'guided filter' methods\n"
- "'EIGF' (exposure-independent guided filter) is a guided filter that is"
- " exposure-independent, it smooths shadows and highlights the same way"
- " (contrary to guided filter which smooths less the highlights)\n"
- "'averaged EIGF' is a geometric mean of 'no' and 'exposure-independent"
- " guided filter' methods"));
+ gtk_widget_set_tooltip_text(
+ g->filter, _("'no' affects global and local contrast (safe if you only add contrast)\n"
+ "'guided filter' only affects global contrast and tries to preserve local contrast\n"
+ "'averaged guided filter' is a geometric mean of 'no' and 'guided filter' methods\n"
+ "'EIGF' (exposure-independent guided filter) is a guided filter that is"
+ " exposure-independent, it smooths shadows and highlights the same way"
+ " (contrary to guided filter which smooths less the highlights)\n"
+ "'averaged EIGF' is a geometric mean of 'no' and 'exposure-independent"
+ " guided filter' methods"));
g->iterations = dt_bauhaus_slider_from_params(self, "iterations");
dt_bauhaus_slider_set_soft_max(g->iterations, 5);
- gtk_widget_set_tooltip_text
- (g->iterations,
- _("number of passes of guided filter to apply\n"
- "helps diffusing the edges of the filter at the expense of speed"));
+ gtk_widget_set_tooltip_text(g->iterations, _("number of passes of guided filter to apply\n"
+ "helps diffusing the edges of the filter at the expense of speed"));
g->blending = dt_bauhaus_slider_from_params(self, "blending");
dt_bauhaus_slider_set_soft_range(g->blending, 1.0, 45.0);
dt_bauhaus_slider_set_format(g->blending, "%");
- gtk_widget_set_tooltip_text
- (g->blending,
- _("diameter of the blur in percent of the largest image size\n"
- "warning: big values of this parameter can make the darkroom\n"
- "preview much slower if denoise profiled is used."));
+ gtk_widget_set_tooltip_text(g->blending, _("diameter of the blur in percent of the largest image size\n"
+ "warning: big values of this parameter can make the darkroom\n"
+ "preview much slower if denoise profiled is used."));
g->feathering = dt_bauhaus_slider_from_params(self, "feathering");
dt_bauhaus_slider_set_soft_range(g->feathering, 0.1, 50.0);
- gtk_widget_set_tooltip_text
- (g->feathering,
- _("precision of the feathering:\n"
- "higher values force the mask to follow edges more closely\n"
- "but may void the effect of the smoothing\n"
- "lower values give smoother gradients and better smoothing\n"
- "but may lead to inaccurate edges taping and halos"));
-
- // gtk_box_pack_start(GTK_BOX(self->widget),
- // dt_ui_section_label_new(C_("section", "mask post-processing")),
- // FALSE, FALSE, 0);
+ gtk_widget_set_tooltip_text(g->feathering, _("precision of the feathering:\n"
+ "higher values force the mask to follow edges more closely\n"
+ "but may void the effect of the smoothing\n"
+ "lower values give smoother gradients and better smoothing\n"
+ "but may lead to inaccurate edges taping and halos"));
+
+ // Collapsible section mask pre-processing
dt_gui_new_collapsible_section(&g->advanced_masking_section, "plugins/darkroom/toneequal/expand_advanced_masking",
- _("mask pre-processing"), GTK_BOX(self->widget), DT_ACTION(self));
- gtk_widget_set_tooltip_text(g->advanced_masking_section.expander, _("advanced masking"));
+ _("mask pre-processing"), GTK_BOX(masking_page), DT_ACTION(self));
+ gtk_widget_set_tooltip_text(g->advanced_masking_section.expander, _("mask pre-processing"));
+
+ // Hack to make the collapsible section align at the top
+ g_object_ref(g->advanced_masking_section.expander);
+ gtk_container_remove(GTK_CONTAINER(masking_page), g->advanced_masking_section.expander);
+ gtk_box_pack_start(GTK_BOX(masking_page), g->advanced_masking_section.expander, FALSE, FALSE, 0);
+ g_object_unref(g->advanced_masking_section.expander);
self->widget = GTK_WIDGET(g->advanced_masking_section.container);
- g->bar = GTK_DRAWING_AREA(gtk_drawing_area_new());
- gtk_widget_set_size_request(GTK_WIDGET(g->bar), -1, 40);
- gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->bar), TRUE, TRUE, 0);
- gtk_widget_set_can_focus(GTK_WIDGET(g->bar), TRUE);
- g_signal_connect(G_OBJECT(g->bar), "draw",
- G_CALLBACK(_toneequalizer_bar_draw), self);
- gtk_widget_set_tooltip_text
- (GTK_WIDGET(g->bar),
- _("mask histogram span between the first and last deciles.\n"
- "the central line shows the average. orange bars appear at extrema"
- " if clipping occurs."));
+ // g->bar = GTK_DRAWING_AREA(gtk_drawing_area_new());
+ // gtk_widget_set_size_request(GTK_WIDGET(g->bar), -1, 40);
+ // gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->bar), TRUE, TRUE, 0);
+ // gtk_widget_set_can_focus(GTK_WIDGET(g->bar), TRUE);
+ // g_signal_connect(G_OBJECT(g->bar), "draw",
+ // G_CALLBACK(_toneequalizer_bar_draw), self);
+ // gtk_widget_set_tooltip_text
+ // (GTK_WIDGET(g->bar),
+ // _("mask histogram span between the first and last deciles.\n"
+ // "the central line shows the average. orange bars appear at extrema"
+ // " if clipping occurs."));
g->quantization = dt_bauhaus_slider_from_params(self, "quantization");
dt_bauhaus_slider_set_format(g->quantization, _(" EV"));
@@ -4280,6 +4620,7 @@ void gui_init(dt_iop_module_t *self)
dt_bauhaus_widget_set_quad(g->contrast_boost, self, dtgtk_cairo_paint_wand, FALSE, auto_adjust_contrast_boost,
_("auto-adjust the contrast"));
+
GtkWidget *histo_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start(GTK_BOX(histo_box),
dt_ui_label_new(_("show image histogram in graph")), TRUE, TRUE, 0);
@@ -4301,6 +4642,8 @@ void gui_init(dt_iop_module_t *self)
gtk_widget_show(gtk_notebook_get_nth_page(g->notebook, active_page));
gtk_notebook_set_current_page(g->notebook, active_page);
+ gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(wrapper), TRUE, TRUE, 0);
+
g_signal_connect(G_OBJECT(g->notebook), "button-press-event",
G_CALLBACK(notebook_button_press), self);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
From dfc3df6ba16789add1bf416a743909be2d2e8d2d Mon Sep 17 00:00:00 2001
From: Marc Fouquet
Date: Sun, 18 May 2025 10:32:37 +0200
Subject: [PATCH 4/6] Changed the points suggested by @TurboGit including:
- Removed unused code
- Changed descriptions for align left/right/center
- Made align "fully fit" the default
- Added underscores to the names of static functions
- Changed newlines after functions
- Removed some debug code and wrapped the rest in ifdefs
- Added hierarchical presets
- Added extra presets that use "fully fit" and a more linear curve (Compress Shadows/Highlights => v3 EIGF)
Graph:
- Graph resizing is now limited and no longer crashes Darktable :-)
- The handle displays correctly
- TODO: Dragging the graph still shows strange "scrolling up" behavior.
- TODO: There must still be some other scrolling-related bug. I once had the points in the graph move in an unexpected way when dragging inside the graph, but was not able to reproduce this.
Suggestion (not changed in code):
- Adding parentheses to expression in gtk.c line 4017 would make the code easier to read. The line breaks suggest that the ternary operator only refers to _resize_wrap_dragging.
Special cases to be tested:
- When exporting small/scaled-down versions of the image with auto shift/scale, is the result still sufficiently similar to the GUI image? In my test, the shift/scale values in the export pipeline were the same to a scale factor of 50% and deviated for smaller images.
---
data/darktableconfig.xml.in | 7 +
src/iop/toneequal.c | 1015 +++++++++++++++--------------------
2 files changed, 426 insertions(+), 596 deletions(-)
diff --git a/data/darktableconfig.xml.in b/data/darktableconfig.xml.in
index 3f6223e87e32..cebfc924a2db 100644
--- a/data/darktableconfig.xml.in
+++ b/data/darktableconfig.xml.in
@@ -3079,6 +3079,13 @@
height of color balance rgb graph in per cent
height of color balance rgb graph in per cent
+
+ plugins/darkroom/toneequal/graphheight
+ int
+ 300
+ height of tone equalizer graph in per cent
+ height of tone equalizer graph in per cent
+
plugins/darkroom/histogram/mode
diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c
index b116cc4973ba..3b9b8eea4434 100644
--- a/src/iop/toneequal.c
+++ b/src/iop/toneequal.c
@@ -91,7 +91,6 @@
#include
#include
#include
-#include // Only needed for debug printf of hashes, TODO remove
#include "bauhaus/bauhaus.h"
@@ -125,64 +124,12 @@
#include
#endif
-DT_MODULE_INTROSPECTION(3, dt_iop_toneequalizer_params_t)
-
-/****************************************************************************
- *
- * Debugging code, remove before release
- *
- ****************************************************************************/
-
-#include
-
-#define DEBUG_WRITE_BUFFERS TRUE
-
-int debug_write_buffer_to_file(const float *buffer, const char *tag, int width, int height, int channels,
- dt_dev_pixelpipe_type_t const pipe)
-{
- if (!DEBUG_WRITE_BUFFERS) return 0;
-
- // Get current time in milliseconds
- struct timeval tv;
- gettimeofday(&tv, NULL);
- unsigned long long ms = (unsigned long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
-
- // Create filename
- char filename[256];
- snprintf(filename, sizeof(filename), "%llu_%s_%d.buftxt", ms, tag, pipe);
-
- // Open file
- FILE *file = g_fopen(filename, "w");
- if(!file) return -1;
-
- // Write header
- fprintf(file, "%d %d %d\n", width, height, channels);
-
- // Write buffer contents
- const size_t total = width * height * channels;
- int line_pos = 0;
-
- for(size_t i = 0; i < total; i++)
- {
- if(line_pos == 20)
- { // Start new line every 20 numbers
- fputc('\n', file);
- line_pos = 0;
- }
-
- if(line_pos > 0)
- {
- fputc(' ', file); // Space between numbers
- }
-
- fprintf(file, "%.6f", buffer[i]);
- line_pos++;
- }
-
- fclose(file);
- return 0;
-}
+// #define MF_DEBUG
+#ifdef MF_DEBUG
+#include // Only needed for debug printf of hashes, TODO remove
+#endif
+DT_MODULE_INTROSPECTION(3, dt_iop_toneequalizer_params_t)
/****************************************************************************
*
@@ -197,7 +144,6 @@ int debug_write_buffer_to_file(const float *buffer, const char *tag, int width,
#define DT_TONEEQ_MIN_EV (-8.0f)
#define DT_TONEEQ_MAX_EV (0.0f)
-#define DT_TONEEQ_USE_LUT TRUE
#define HIRES_HISTO_MIN_EV -16.0f
#define HIRES_HISTO_MAX_EV 8.0f
@@ -251,9 +197,9 @@ typedef enum dt_iop_toneequalizer_filter_t
typedef enum dt_iop_toneequalizer_post_auto_align_t
{
DT_TONEEQ_ALIGN_CUSTOM = 0, // $DESCRIPTION: "custom"
- DT_TONEEQ_ALIGN_LEFT, // $DESCRIPTION: "auto-align at shadows"
- DT_TONEEQ_ALIGN_CENTER, // $DESCRIPTION: "auto-align at mid-tones"
- DT_TONEEQ_ALIGN_RIGHT, // $DESCRIPTION: "auto-align at highlights"
+ DT_TONEEQ_ALIGN_LEFT, // $DESCRIPTION: "at shadows"
+ DT_TONEEQ_ALIGN_CENTER, // $DESCRIPTION: "at mid-tones"
+ DT_TONEEQ_ALIGN_RIGHT, // $DESCRIPTION: "at highlights"
DT_TONEEQ_ALIGN_FIT, // $DESCRIPTION: "fully fit"
} dt_iop_toneequalizer_post_auto_align_t;
@@ -280,7 +226,7 @@ typedef struct dt_iop_toneequalizer_params_t
int iterations; // $MIN: 1 $MAX: 20 $DEFAULT: 1 $DESCRIPTION: "filter diffusion"
float post_scale; // $MIN: -3.0 $MAX: 3.0 $DEFAULT: 0.0 $DESCRIPTION: "mask contrast / scale histogram"
float post_shift; // $MIN: -4.0 $MAX: 4.0 $DEFAULT: 0.0 $DESCRIPTION: "mask brightness / shift histogram"
- dt_iop_toneequalizer_post_auto_align_t post_auto_align; // $DEFAULT: DT_TONEEQ_ALIGN_CUSTOM $DESCRIPTION: "auto align mask exposure"
+ dt_iop_toneequalizer_post_auto_align_t post_auto_align; // $DEFAULT: DT_TONEEQ_ALIGN_FIT $DESCRIPTION: "auto align mask exposure"
} dt_iop_toneequalizer_params_t;
@@ -380,6 +326,7 @@ typedef struct dt_iop_toneequalizer_gui_data_t
float sign_width;
float graph_width;
float graph_height;
+ float graph_w_gradients_height;
float gradient_left_limit;
float gradient_right_limit;
float gradient_top_limit;
@@ -462,13 +409,11 @@ const char *name()
return _("tone equalizer");
}
-
const char *aliases()
{
return _("tone curve|tone mapping|relight|background light|shadows highlights");
}
-
const char **description(dt_iop_module_t *self)
{
return dt_iop_set_description
@@ -479,19 +424,16 @@ const char **description(dt_iop_module_t *self)
_("quasi-linear, RGB, scene-referred"));
}
-
int default_group()
{
return IOP_GROUP_BASIC | IOP_GROUP_GRADING;
}
-
int flags()
{
return IOP_FLAGS_INCLUDE_IN_STYLES | IOP_FLAGS_SUPPORTS_BLENDING;
}
-
dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,
dt_dev_pixelpipe_t *pipe,
dt_dev_pixelpipe_iop_t *piece)
@@ -499,7 +441,6 @@ dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self,
return IOP_CS_RGB;
}
-
int legacy_params(dt_iop_module_t *self,
const void *const old_params,
const int old_version,
@@ -507,7 +448,9 @@ int legacy_params(dt_iop_module_t *self,
int32_t *new_params_size,
int *new_version)
{
+#ifdef MF_DEBUG
printf("legacy_params old_version=%d\n", old_version);
+#endif
typedef struct dt_iop_toneequalizer_params_v3_t
{
@@ -575,7 +518,7 @@ int legacy_params(dt_iop_module_t *self,
*new_params = n;
*new_params_size = sizeof(dt_iop_toneequalizer_params_v3_t);
- *new_version = 1;
+ *new_version = 3;
return 0;
}
@@ -616,7 +559,7 @@ int legacy_params(dt_iop_module_t *self,
* Presets
*
****************************************************************************/
-static void compress_shadows_highlight_preset_set_exposure_params
+static void _compress_shadows_highlight_preset_set_exposure_params
(dt_iop_toneequalizer_params_t* p,
const float step)
{
@@ -634,8 +577,26 @@ static void compress_shadows_highlight_preset_set_exposure_params
p->speculars = -step;
}
+static void _compress_shadows_highlight_linear_preset_set_exposure_params
+ (dt_iop_toneequalizer_params_t* p,
+ const float step)
+{
+ // this function is used to set the exposure params for the 4 "compress shadows
+ // highlights" presets, which use basically the same curve, centered around
+ // -4EV with an exposure compensation that puts middle-grey at -4EV.
+ p->noise = step;
+ p->ultra_deep_blacks = 15.f / 9.f * step;
+ p->deep_blacks = 10.f / 9.f * step;
+ p->blacks = 5.f / 9.f * step;
+ p->shadows = 0.0f;
+ p->midtones = -5.f / 9.f * step;
+ p->highlights = -10.f / 9.f * step;
+ p->whites = -15.f / 9.f * step;
+ p->speculars = -step;
+}
+
-static void dilate_shadows_highlight_preset_set_exposure_params
+static void _dilate_shadows_highlight_preset_set_exposure_params
(dt_iop_toneequalizer_params_t* p,
const float step)
{
@@ -652,7 +613,6 @@ static void dilate_shadows_highlight_preset_set_exposure_params
p->speculars = 15.f / 9.f * step;
}
-
void init_presets(dt_iop_module_so_t *self)
{
dt_iop_toneequalizer_params_t p;
@@ -690,14 +650,14 @@ void init_presets(dt_iop_module_so_t *self)
p.exposure_boost = 0.0f;
p.contrast_boost = 0.0f;
dt_gui_presets_add_generic
- (_("mask blending: all purposes"), self->op,
+ (_("mask blending | all purposes"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.blending = 1.0f;
p.feathering = 10.0f;
p.iterations = 3;
dt_gui_presets_add_generic
- (_("mask blending: people with backlight"), self->op,
+ (_("mask blending | people with backlight"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// Shadows/highlights presets
@@ -709,63 +669,93 @@ void init_presets(dt_iop_module_so_t *self)
p.iterations = 5;
p.quantization = 0.0f;
+ // Modified names to gentle / medium / strong, so the alphabetical
+ // sort order matches the order of strengths.
+
// slight modification to give higher compression
p.filter = DT_TONEEQ_EIGF;
p.feathering = 20.0f;
- compress_shadows_highlight_preset_set_exposure_params(&p, 0.65f);
+ _compress_shadows_highlight_preset_set_exposure_params(&p, 0.65f);
dt_gui_presets_add_generic
- (_("compress shadows/highlights (EIGF): strong"), self->op,
+ (_("compress shadows/highlights | classic EIGF | strong"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.filter = DT_TONEEQ_GUIDED;
p.feathering = 500.0f;
dt_gui_presets_add_generic
- (_("compress shadows/highlights (GF): strong"), self->op,
+ (_("compress shadows/highlights | classic GF | strong"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.filter = DT_TONEEQ_EIGF;
p.blending = 3.0f;
p.feathering = 7.0f;
p.iterations = 3;
- compress_shadows_highlight_preset_set_exposure_params(&p, 0.45f);
+ _compress_shadows_highlight_preset_set_exposure_params(&p, 0.45f);
dt_gui_presets_add_generic
- (_("compress shadows/highlights (EIGF): medium"), self->op,
+ (_("compress shadows/highlights | classic EIGF | medium"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.filter = DT_TONEEQ_GUIDED;
p.feathering = 500.0f;
dt_gui_presets_add_generic
- (_("compress shadows/highlights (GF): medium"), self->op,
+ (_("compress shadows/highlights | classic GF | medium"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.filter = DT_TONEEQ_EIGF;
p.blending = 5.0f;
p.feathering = 1.0f;
p.iterations = 1;
- compress_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
+ _compress_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
dt_gui_presets_add_generic
- (_("compress shadows/highlights (EIGF): soft"), self->op,
+ (_("compress shadows/highlights | classic EIGF | gentle"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
p.filter = DT_TONEEQ_GUIDED;
p.feathering = 500.0f;
dt_gui_presets_add_generic
- (_("compress shadows/highlights (GF): soft"), self->op,
+ (_("compress shadows/highlights | classic GF | gentle"), self->op,
+ self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
+
+ // New compress shadows & highlights with a more linear curve
+ // and auto-align "fully fit"
+ p.post_auto_align = DT_TONEEQ_ALIGN_FIT;
+ p.filter = DT_TONEEQ_EIGF;
+ p.feathering = 20.0f;
+ _compress_shadows_highlight_linear_preset_set_exposure_params(&p, 0.65f);
+ dt_gui_presets_add_generic
+ (_("compress shadows/highlights | v3 EIGF | strong"), self->op,
+ self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
+
+ p.blending = 3.0f;
+ p.feathering = 7.0f;
+ p.iterations = 3;
+ _compress_shadows_highlight_linear_preset_set_exposure_params(&p, 0.45f);
+ dt_gui_presets_add_generic
+ (_("compress shadows/highlights | v3 EIGF | medium"), self->op,
+ self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
+
+ p.blending = 5.0f;
+ p.feathering = 1.0f;
+ p.iterations = 1;
+ _compress_shadows_highlight_linear_preset_set_exposure_params(&p, 0.25f);
+ dt_gui_presets_add_generic
+ (_("compress shadows/highlights | v3 EIGF | gentle"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// build the 1D contrast curves that revert the local compression of
// contrast above
+ p.post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
p.filter = DT_TONEEQ_NONE;
- dilate_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
+ _dilate_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
dt_gui_presets_add_generic
- (_("contrast tone curve: soft"), self->op,
+ (_("contrast tone curve | gentle"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- dilate_shadows_highlight_preset_set_exposure_params(&p, 0.45f);
+ _dilate_shadows_highlight_preset_set_exposure_params(&p, 0.45f);
dt_gui_presets_add_generic
- (_("contrast tone curve: medium"), self->op,
+ (_("contrast tone curve | medium"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
- dilate_shadows_highlight_preset_set_exposure_params(&p, 0.65f);
+ _dilate_shadows_highlight_preset_set_exposure_params(&p, 0.65f);
dt_gui_presets_add_generic
- (_("contrast tone curve: strong"), self->op,
+ (_("contrast tone curve | strong"), self->op,
self->version(), &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_SCENE);
// relight
@@ -800,15 +790,13 @@ void init_presets(dt_iop_module_so_t *self)
*
****************************************************************************/
__DT_CLONE_TARGETS__
-static inline void compute_hires_histogram_and_stats(const float *const restrict luminance,
+static inline void _compute_hires_histogram_and_stats(const float *const restrict luminance,
int hires_histogram[HIRES_HISTO_SAMPLES],
const size_t num_elem,
float *first_decile,
float *last_decile,
dt_dev_pixelpipe_type_t const debug_pipe)
{
- // printf("compute_hires_histogram_and_stats pipe=%d num_elem=%ld\n", debug_pipe, num_elem);
-
// The GUI histogram comprises 8 EV (UI_HISTO_SAMPLES, -8 to 0).
// The high resolution histogram extends this to an exta 8 EV before and
// 8EV after, for a total of 24.
@@ -829,8 +817,6 @@ static inline void compute_hires_histogram_and_stats(const float *const restrict
hires_histogram[index] += 1;
}
- // printf("hires_histogram 0: %d 256: %d 512: %d 767: %d\n", hires_histogram[0], hires_histogram[256], hires_histogram[512], hires_histogram[767]);
-
const int first_decile_pop = (int)((float)num_elem * 0.05f);
const int last_decile_pop = (int)((float)num_elem * (1.0f - 0.95f));
int population = 0;
@@ -841,7 +827,6 @@ static inline void compute_hires_histogram_and_stats(const float *const restrict
// Scout the extended histogram bins looking for the
// absolute first and last non-zero values and for deciles.
// These would not be accurate with the gui histogram.
-
for(k = 0; k < HIRES_HISTO_SAMPLES; ++k)
{
population += hires_histogram[k];
@@ -862,16 +847,14 @@ static inline void compute_hires_histogram_and_stats(const float *const restrict
break;
}
}
- // printf("First pos: %d, Last pos: %d\n", first_pos, last_pos);
// Convert decile positions to exposures
*first_decile = (temp_ev_range * ((float)first_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
*last_decile = (temp_ev_range * ((float)last_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
}
-
__DT_CLONE_TARGETS__
-static inline void compute_luminance_mask(const float *const restrict in,
+static inline void _compute_luminance_mask(const float *const restrict in,
float *const restrict luminance,
const size_t width,
const size_t height,
@@ -882,27 +865,13 @@ static inline void compute_luminance_mask(const float *const restrict in,
float *last_decile, // only for compute_image_stats
dt_dev_pixelpipe_type_t const debug_pipe)
{
- printf("compute_luminance_mask pipe=%d width=%ld height=%ld first luminance=%f estimator=%d "
+#ifdef MF_DEBUG
+ printf("_compute_luminance_mask pipe=%d width=%ld height=%ld first luminance=%f estimator=%d "
"exposure_boost=%f contrast_boost=%f radius=%d feathering=%f iterations=%d scale=%f quantization=%f\n",
debug_pipe, width, height, luminance[0], d->lum_estimator,
d->exposure_boost, d->contrast_boost, d->radius, d->feathering,
d->iterations, d->scale, d->quantization);
-
- // int n = 10;
- // printf("compute_luminance_mask row 0 START in=%f %f %f %f %f %f %f %f %f %f %f %f\n",
- // in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7], in[8], in[9], in[10], in[11]);
- // printf("compute_luminance_mask row %d START in=%f %f %f %f %f %f %f %f %f %f %f %f\n", n,
- // in[0 + n*4*width], in[1 + n*4*width], in[2 + n*4*width], in[3 + n*4*width], in[4 + n*4*width],
- // in[5 + n*4*width], in[6 + n*4*width], in[7 + n*4*width], in[8 + n*4*width], in[9 + n*4*width],
- // in[10 + n*4*width], in[11 + n*4*width]);
-
- // int offset = ((width / 2) / 4) * 4; // int division to get the offset %4 aligned
- // printf("compute_luminance_mask row %d MIDDLE in=%f %f %f %f %f %f %f %f %f %f %f %f\n", n,
- // in[0 + n*4*width + offset], in[1 + n*4*width + offset], in[2 + n*4*width + offset], in[3 + n*4*width + offset], in[4 + n*4*width + offset],
- // in[5 + n*4*width + offset], in[6 + n*4*width + offset], in[7 + n*4*width + offset], in[8 + n*4*width + offset], in[9 + n*4*width + offset],
- // in[10 + n*4*width + offset], in[11 + n*4*width + offset]);
-
- // debug_write_buffer_to_file(in, "luminance_mask", width, height, 4, debug_pipe);
+#endif
const int num_elem = width * height;
@@ -914,7 +883,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
break;
}
@@ -924,7 +893,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_GEOMEAN, d->scale, d->quantization,
exp2f(-14.0f), 4.0f);
@@ -943,7 +912,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height, d->lum_estimator, d->exposure_boost,
CONTRAST_FULCRUM, d->contrast_boost);
if (compute_image_stats)
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_surface_blur(luminance, width, height, d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_LINEAR, d->scale, d->quantization,
exp2f(-14.0f), 4.0f);
@@ -956,7 +925,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_eigf_surface_blur(luminance, width, height,
d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_GEOMEAN, d->scale,
@@ -969,7 +938,7 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height, d->lum_estimator, d->exposure_boost,
CONTRAST_FULCRUM, d->contrast_boost);
if (compute_image_stats)
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
fast_eigf_surface_blur(luminance, width, height,
d->radius, d->feathering, d->iterations,
DT_GF_BLENDING_LINEAR, d->scale,
@@ -982,27 +951,25 @@ static inline void compute_luminance_mask(const float *const restrict in,
luminance_mask(in, luminance, width, height,
d->lum_estimator, d->exposure_boost, 0.0f, 1.0f);
if (compute_image_stats)
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem, first_decile, last_decile, debug_pipe);
break;
}
}
-
- // debug_write_buffer_to_file(luminance, "luminance_mask", width, height, 1, debug_pipe);
}
-
// This is similar to exposure/contrast boost.
// However it is applied AFTER the guided filter calculation, so it is much
// easier to control and does not mess with the detail detection of the
// guided filter.
-static inline float post_scale_shift(const float v, const float post_scale, const float post_shift)
+static inline float _post_scale_shift(const float v,
+ const float post_scale,
+ const float post_shift)
{
const float scale_exp = exp2f(post_scale);
// signifficant range -8..0, centering around the middle
return (v + 4.0f) * scale_exp - 4.0f + post_shift;
}
-
// This is similar to the auto-buttons for exposure/contrast boost.
// However it runs automatically in the pipe, so it does not need to be
// triggered by the user each time the upstream exposure changes.
@@ -1017,8 +984,10 @@ void compute_auto_post_scale_shift(dt_iop_toneequalizer_post_auto_align_t post_a
const float last_decile_target = -1.0f;
const float pivot = -4.0f; // for scaling
+#ifdef MF_DEBUG
printf("compute_auto_post_shift_scale: Pipe=%d post_auto_align=%d old post_scale=%f post_shift=%f histogram_first_decile=%f histogram_last_decile=%f\n",
debug_pipe, post_auto_align, *post_scale, *post_shift, histogram_first_decile, histogram_last_decile);
+#endif
switch(post_auto_align)
{
@@ -1059,28 +1028,31 @@ void compute_auto_post_scale_shift(dt_iop_toneequalizer_post_auto_align_t post_a
break;
}
}
+#ifdef MF_DEBUG
printf("compute_auto_post_shift_scale: New post_scale=%f post_shift=%f\n", *post_scale, *post_shift);
+#endif
};
-
__DT_CLONE_TARGETS__
-static inline void display_luminance_mask(const float *const restrict in,
- const float *const restrict luminance,
- float *const restrict out,
- const dt_iop_roi_t *const roi_in,
- const dt_iop_roi_t *const roi_out,
- const float post_scale,
- const float post_shift,
- dt_dev_pixelpipe_type_t const debug_pipe)
+static inline void _display_luminance_mask(const float *const restrict in,
+ const float *const restrict luminance,
+ float *const restrict out,
+ const dt_iop_roi_t *const roi_in,
+ const dt_iop_roi_t *const roi_out,
+ const float post_scale,
+ const float post_shift,
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
const size_t offset_x = (roi_in->x < roi_out->x) ? -roi_in->x + roi_out->x : 0;
const size_t offset_y = (roi_in->y < roi_out->y) ? -roi_in->y + roi_out->y : 0;
+#ifdef MF_DEBUG
printf("display_luminance_mask pipe=%d offset_x=%ld offset_y=%ld roi_in %d %d %d %d roi_out %d %d %d %d, post_scale=%f, post_shift=%f\n",
debug_pipe, offset_x, offset_y,
roi_in->x, roi_in->y, roi_in->width, roi_in->height,
roi_out->x, roi_out->y, roi_out->width, roi_out->height,
post_scale, post_shift);
+#endif
// The output dimensions need to be smaller or equal to the input ones
// there is no logical reason they shouldn't, except some weird bug in the pipe
@@ -1102,7 +1074,7 @@ static inline void display_luminance_mask(const float *const restrict in,
// and add a "gamma" 2.0 for better legibility in shadows
const int lum_index = (i + offset_y) * in_width + (j + offset_x);
const float lum_log = log2f(luminance[lum_index]);
- const float lum_corrected = post_scale_shift(lum_log, post_scale, post_shift);
+ const float lum_corrected = _post_scale_shift(lum_log, post_scale, post_shift);
// IMHO it would be fine, to show the log version of the mask to the user.
// const float intensity =
@@ -1126,7 +1098,6 @@ static inline void display_luminance_mask(const float *const restrict in,
}
}
-
/***
* Exposure compensation computation
*
@@ -1137,7 +1108,7 @@ static inline void display_luminance_mask(const float *const restrict in,
***/
DT_OMP_DECLARE_SIMD()
__DT_CLONE_TARGETS__
-static float gaussian_denom(const float sigma)
+static float _gaussian_denom(const float sigma)
{
// Gaussian function denominator such that y = exp(- radius^2 / denominator)
// this is the constant factor of the exponential, so we don't need to recompute it
@@ -1145,27 +1116,28 @@ static float gaussian_denom(const float sigma)
return 2.0f * sigma * sigma;
}
-
DT_OMP_DECLARE_SIMD()
__DT_CLONE_TARGETS__
-static float gaussian_func(const float radius, const float denominator)
+static float _gaussian_func(const float radius,
+ const float denominator)
{
// Gaussian function without normalization
// this is the variable part of the exponential
- // the denominator should be evaluated with `gaussian_denom`
+ // the denominator should be evaluated with `_gaussian_denom`
// ahead of the array loop for optimal performance
return expf(- radius * radius / denominator);
}
-
-static void compute_correction_lut(float *restrict lut, const float sigma,
- const float *const restrict factors,
- const float post_scale, const float post_shift,
- dt_dev_pixelpipe_type_t const debug_pipe)
+static void _compute_correction_lut(float *restrict lut, const float sigma,
+ const float *const restrict factors,
+ const float post_scale, const float post_shift,
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
- // printf("compute_correction_lut pipe=%d, post_scale=%f, post_shift=%f\n", debug_pipe, post_scale, post_shift);
+#ifdef MF_DEBUG
+ // printf("_compute_correction_lut pipe=%d, post_scale=%f, post_shift=%f\n", debug_pipe, post_scale, post_shift);
+#endif
- const float gauss_denom = gaussian_denom(sigma);
+ const float gauss_denom = _gaussian_denom(sigma);
assert(NUM_OCTAVES == 8);
// TODO MF: Does the openmp still work here?
@@ -1175,36 +1147,36 @@ static void compute_correction_lut(float *restrict lut, const float sigma,
// build the correction for each pixel
// as the sum of the contribution of each luminance channelcorrection
const float exposure_uncorrected = (float)j / (float)LUT_RESOLUTION + DT_TONEEQ_MIN_EV; // [-8...0] EV
- const float exposure = fast_clamp(post_scale_shift(exposure_uncorrected, post_scale, post_shift),
+ const float exposure = fast_clamp(_post_scale_shift(exposure_uncorrected, post_scale, post_shift),
DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
float result = 0.0f;
for(int i = 0; i < NUM_OCTAVES; i++)
{
- result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
+ result += _gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
}
// the user-set correction is expected in [-2;+2] EV, so is the interpolated one
lut[j] = fast_clamp(result, 0.25f, 4.0f);
}
}
-
-#if DT_TONEEQ_USE_LUT
// this is the version currently used, as using a lut gives a
// big performance speedup on some cpus
__DT_CLONE_TARGETS__
-static inline void apply_toneequalizer(const float *const restrict in,
- const float *const restrict luminance,
- float *const restrict out,
- const dt_iop_roi_t *const roi_in,
- const dt_iop_roi_t *const roi_out,
- const dt_iop_toneequalizer_data_t *const d,
- dt_dev_pixelpipe_type_t const debug_pipe)
+static inline void _apply_toneequalizer(const float *const restrict in,
+ const float *const restrict luminance,
+ float *const restrict out,
+ const dt_iop_roi_t *const roi_in,
+ const dt_iop_roi_t *const roi_out,
+ const dt_iop_toneequalizer_data_t *const d,
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
- // printf("apply_toneequalizer pipe=%d first luminance=%f roi_in %d %d %d %d roi_out %d %d %d %d post_scale=%f, post_shift=%f\n",
+#ifdef MF_DEBUG
+ // printf("_apply_toneequalizer pipe=%d first luminance=%f roi_in %d %d %d %d roi_out %d %d %d %d post_scale=%f, post_shift=%f\n",
// debug_pipe, luminance[0],
// roi_in->x, roi_in->y, roi_in->width, roi_in->height,
// roi_out->x, roi_out->y, roi_out->width, roi_out->height,
// d->post_scale, d->post_shift);
+#endif
const size_t npixels = (size_t)roi_in->width * roi_in->height;
const float* restrict lut = d->correction_lut;
const float lutres = LUT_RESOLUTION;
@@ -1223,50 +1195,6 @@ static inline void apply_toneequalizer(const float *const restrict in,
}
}
-#else
-
-// we keep this version for further reference (e.g. for implementing
-// a gpu version)
-// TODO MF: Remove? This is no longer identical to the LUT version.
-__DT_CLONE_TARGETS__
-static inline void apply_toneequalizer(const float *const restrict in,
- const float *const restrict luminance,
- float *const restrict out,
- const dt_iop_roi_t *const roi_in,
- const dt_iop_roi_t *const roi_out,
- const dt_iop_toneequalizer_data_t *const d)
-{
- const size_t num_elem = roi_in->width * roi_in->height;
- const float *const restrict factors = d->factors;
- const float sigma = d->smoothing;
- const float gauss_denom = gaussian_denom(sigma);
-
- DT_OMP_FOR(shared(centers_ops))
- for(size_t k = 0; k < num_elem; ++k)
- {
- // build the correction for the current pixel
- // as the sum of the contribution of each luminance channelcorrection
- float result = 0.0f;
-
- // The radial-basis interpolation is valid in [-8; 0] EV and can
- // quickely diverge outside
- const float exposure = fast_clamp(log2f(luminance[k]), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
-
- DT_OMP_SIMD(aligned(luminance, centers_ops, factors:64) safelen(NUM_OCTAVES) reduction(+:result))
- for(int i = 0; i < NUM_OCTAVES; ++i)
- result += gaussian_func(exposure - centers_ops[i], gauss_denom) * factors[i];
-
- // the user-set correction is expected in [-2;+2] EV, so is the interpolated one
- const float correction = fast_clamp(result, 0.25f, 4.0f);
-
- // apply correction
- for_each_channel(c)
- out[4 * k + c] = correction * in[4 * k + c];
- }
-}
-#endif // USE_LUT
-
-
float adjust_radius_to_scale(const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, int full_width, int full_height)
{
dt_iop_toneequalizer_data_t *const d = piece->data;
@@ -1281,10 +1209,11 @@ float adjust_radius_to_scale(const dt_dev_pixelpipe_iop_t *piece, const dt_iop_r
return radius;
}
-
-static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
- const float *const restrict in, const dt_iop_roi_t *const roi_in, const dt_iop_toneequalizer_data_t *const d,
- float *first_decile, float *last_decile)
+static void _compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
+ const float *const restrict in,
+ const dt_iop_roi_t *const roi_in,
+ const dt_iop_toneequalizer_data_t *const d,
+ float *first_decile, float *last_decile)
{
// A stable method of calculating the decile values. This is designed to
// produce the exact same results in PIXELPIPE_FULL and PIXELPIPE_EXPORT.
@@ -1325,16 +1254,18 @@ static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
memcpy(&data, d, sizeof(dt_iop_toneequalizer_data_t));
data.radius = adjust_radius_to_scale(piece, &roi, roi_in->width, roi_in->height);
- printf("compute_mask_stats_from_full_image: roi_in %d %d %d %d scale %f roi %d %d %d %d scale %f radius %d\n",
+#ifdef MF_DEBUG
+ printf("_compute_mask_stats_from_full_image: roi_in %d %d %d %d scale %f roi %d %d %d %d scale %f radius %d\n",
roi_in->x, roi_in->y, roi_in->width, roi_in->height, roi_in->scale,
roi.x, roi.y, roi.width, roi.height, roi.scale, data.radius);
+#endif
// If the image is big, we downscale it and compute our mask from the small version.
// If the image was small already, this is not necessary.
if (downscale) {
float *mask_input = dt_alloc_align_float(4 * num_elem);
dt_iop_clip_and_zoom(mask_input, in, &roi, roi_in);
- compute_luminance_mask(mask_input, luminance,
+ _compute_luminance_mask(mask_input, luminance,
roi.width, roi.height, &data,
FALSE, NULL, NULL, NULL,
piece->pipe->type);
@@ -1342,14 +1273,14 @@ static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
}
else
{
- compute_luminance_mask(in, luminance,
+ _compute_luminance_mask(in, luminance,
roi.width, roi.height, &data,
FALSE, NULL, NULL, NULL,
piece->pipe->type);
}
int* hires_histogram = dt_alloc_align_int(HIRES_HISTO_SAMPLES);
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
first_decile, last_decile,
piece->pipe->type);
@@ -1357,8 +1288,6 @@ static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
dt_free_align(luminance);
}
-
-
/***
* PROCESS
*
@@ -1368,7 +1297,7 @@ static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
* we also get the 5th and 95th percentiles.
* 3. Optionally compute auto align -> post scale and shift
* 4. Compute a correction LUT based on the user's curve in the GUI
- * and optionally post scale/shift
+ * and post scale/shift
* 5. Correct the image based on curve and luminance mask
*
* The guided filter is scale dependent, so the results with differently
@@ -1389,13 +1318,13 @@ static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
* as long as nothing changes upstream.
* - The histogram is cached in g and used for the GUI.
* - The deciles are calculated and stored in g. These are needed for
- * the exposure/contrast boost magic wands.
+ * the original exposure/contrast boost magic wands.
* - Post scale/shift are calculated with local data. They are off, but this
* image is only displayed as a small preview, so no one will notice.
* DT_DEV_PIXELPIPE_FULL (2)
* - The input image is whatever is displayed in the main view. This can be
* a segment of the full image if the user has zoomed in.
- * - The muminance mask for this pipe is also cached, so it does not need to
+ * - The luminance mask for this pipe is also cached, so it does not need to
* be re-calculated as long as nothing changes upstream. This also allows
* for switching to the mask view (greyscale) quickly.
* - A segment is not suitable for calculating post scale/shift, for this
@@ -1410,10 +1339,9 @@ static void compute_mask_stats_from_full_image(dt_dev_pixelpipe_iop_t *piece,
* filter and also post scale/shift calculation will deviate, but
* the output is displayed very small, so no one will notice.
***/
-
__DT_CLONE_TARGETS__
static
-void toneeq_process(dt_iop_module_t *self,
+void _toneeq_process(dt_iop_module_t *self,
dt_dev_pixelpipe_iop_t *piece,
const void *const restrict ivoid,
void *const restrict ovoid,
@@ -1446,13 +1374,13 @@ void toneeq_process(dt_iop_module_t *self,
gboolean local_luminance = FALSE;
gboolean local_hires_hist = FALSE;
- // Debugging
- printf("toneeq_process: pipe=%d piece->buf_in->width=%d piece->buf_in->height=%d iwidth=%d iheight=%d roi_in x=%d y=%d w=%d h=%d s=%f roi_out x=%d y=%d w=%d h=%d s=%f radius=%d mode=%d\n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process: pipe=%d piece->buf_in->width=%d piece->buf_in->height=%d iwidth=%d iheight=%d roi_in x=%d y=%d w=%d h=%d s=%f roi_out x=%d y=%d w=%d h=%d s=%f radius=%d mode=%d\n",
piece->pipe->type, piece->buf_in.width, piece->buf_in.height, piece->iwidth, piece->iheight,
roi_in->x, roi_in->y, roi_in->width, roi_in->height, roi_in->scale,
roi_out->x, roi_out->y, roi_out->width, roi_out->height, roi_out->scale,
d->radius, darktable.color_profiles->mode);
- // debug_write_buffer_to_file(in, "toneeq_in", width, height, 4, piece->pipe->type);
+#endif
/**************************************************************************
* Initialization
@@ -1555,9 +1483,11 @@ void toneeq_process(dt_iop_module_t *self,
d->radius = adjust_radius_to_scale(piece, roi_in, piece->iwidth, piece->iheight);
- printf("toneeq_process (%d) after adjust_radius_to_scale: d->radius=%d roi in %d %d %d %d scale %f iwidth %d iheight %d\n", piece->pipe->type, d->radius,
+#ifdef MF_DEBUG
+ printf("_toneeq_process (%d) after adjust_radius_to_scale: d->radius=%d roi in %d %d %d %d scale %f iwidth %d iheight %d\n", piece->pipe->type, d->radius,
roi_in->x, roi_in->y, roi_in->width, roi_in->height, roi_in->scale,
piece->iwidth, piece->iheight);
+#endif
/**************************************************************************
* Compute the luminance mask
@@ -1579,8 +1509,10 @@ void toneeq_process(dt_iop_module_t *self,
const gboolean prv_luminance_valid = g->prv_luminance_valid;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process GUI PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" prv_luminance_valid=%d\n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process GUI PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" prv_luminance_valid=%d\n",
current_upstream_hash, saved_upstream_hash, prv_luminance_valid);
+#endif
if(saved_upstream_hash != current_upstream_hash || !prv_luminance_valid)
{
@@ -1590,14 +1522,14 @@ void toneeq_process(dt_iop_module_t *self,
g->preview_upstream_hash = current_upstream_hash;
g->gui_histogram_valid = FALSE;
- compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d,
+ _compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d,
TRUE, // Also compute an image (not mask!) histogram for coloring the curve
g->image_hires_histogram,
&g->image_histogram_first_decile, &g->image_histogram_last_decile,
piece->pipe->type);
// Histogram and deciles
- compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
+ _compute_hires_histogram_and_stats(luminance, hires_histogram, num_elem,
&g->prv_histogram_first_decile, &g->prv_histogram_last_decile,
piece->pipe->type);
@@ -1615,24 +1547,23 @@ void toneeq_process(dt_iop_module_t *self,
piece->pipe->type);
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process PIXELPIPE_PREVIEW (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f d->post_shift=%f final post_scale=%f final post_shift=%f hash=%"PRIu64" saved_hash=%"PRIu64" prv_luminance_valid=%d \n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process PIXELPIPE_PREVIEW (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f d->post_shift=%f final post_scale=%f final post_shift=%f hash=%"PRIu64" saved_hash=%"PRIu64" prv_luminance_valid=%d \n",
piece->pipe->type,
roi_in->width, roi_in->height,
d->post_auto_align, d->post_scale, d->post_shift,
post_scale, post_shift,
current_upstream_hash, saved_upstream_hash, prv_luminance_valid
);
-
+#endif
// TODO MF: Not completely sure in which cases this must be called.
// Assumption is once per output image change by this module and
// only when the GUI is active.
dt_dev_pixelpipe_cache_invalidate_later(piece->pipe, self->iop_order);
-
}
else if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL))
{
-
if(roi_in->width == piece->buf_in.width && roi_in->height == piece->buf_in.height && fabs(roi_in->scale - 1.0f) < 0.00001)
{
// We are in PIXELPIPE_FULL and have the full image as our roi.
@@ -1652,8 +1583,10 @@ void toneeq_process(dt_iop_module_t *self,
const gboolean full_luminance_valid = g->full_luminance_valid;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process GUI PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" full_luminance_valid=%d\n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process GUI PIXELPIPE_PREVIEW: hash=%"PRIu64" saved_hash=%"PRIu64" full_luminance_valid=%d\n",
current_upstream_hash, saved_upstream_hash, full_luminance_valid);
+#endif
if(saved_upstream_hash != current_upstream_hash || !full_luminance_valid)
{
@@ -1666,13 +1599,13 @@ void toneeq_process(dt_iop_module_t *self,
d->radius = adjust_radius_to_scale(piece, roi_out, piece->iwidth, piece->iheight);
// Luminance mask, but no image histogram
- compute_luminance_mask(in_cropped, luminance, roi_out->width, roi_out->height, d,
- FALSE, NULL, NULL, NULL,
- piece->pipe->type);
+ _compute_luminance_mask(in_cropped, luminance, roi_out->width, roi_out->height, d,
+ FALSE, NULL, NULL, NULL,
+ piece->pipe->type);
// Calculate post scale/shift using the full image (same calculation as
// during export)
- compute_mask_stats_from_full_image(piece, in, roi_in, d,
+ _compute_mask_stats_from_full_image(piece, in, roi_in, d,
&g->full_histogram_first_decile, &g->full_histogram_last_decile);
g->full_luminance_valid = TRUE;
@@ -1689,10 +1622,12 @@ void toneeq_process(dt_iop_module_t *self,
g->post_shift_value = post_shift;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process GUI PIXELPIPE_FULL (%d), FULL ROI: roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
+#ifdef MF_DEBUG
+ printf("_toneeq_process GUI PIXELPIPE_FULL (%d), FULL ROI: roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
"d->post_shift=%f final post_scale=%f final post_shift=%f\n",
piece->pipe->type, roi_in->width, roi_in->height, d->post_auto_align, d->post_scale, d->post_shift,
post_scale, post_shift);
+#endif
}
else // not full image as roi
{
@@ -1717,8 +1652,10 @@ void toneeq_process(dt_iop_module_t *self,
const gboolean full_luminance_valid = g->full_luminance_valid;
dt_iop_gui_leave_critical_section(self);
- printf("toneeq_process GUI PIXELPIPE_FULL: hash=%" PRIu64 " saved_hash=%" PRIu64 " full_luminance_valid=%d\n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process GUI PIXELPIPE_FULL: hash=%" PRIu64 " saved_hash=%" PRIu64 " full_luminance_valid=%d\n",
current_upstream_hash, saved_upstream_hash, full_luminance_valid);
+#endif
// If the upstream state has changed, re-compute the mask of the displayed image part
if(current_upstream_hash != saved_upstream_hash || !full_luminance_valid)
@@ -1728,7 +1665,7 @@ void toneeq_process(dt_iop_module_t *self,
g->full_upstream_hash = current_upstream_hash;
dt_iop_gui_leave_critical_section(self);
- compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d, FALSE, NULL, NULL, NULL, piece->pipe->type);
+ _compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d, FALSE, NULL, NULL, NULL, piece->pipe->type);
g->full_luminance_valid = TRUE;
}
@@ -1746,10 +1683,12 @@ void toneeq_process(dt_iop_module_t *self,
piece->pipe->type);
}
- printf("toneeq_process GUI PIXELPIPE_FULL (%d), no full roi: roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
+#ifdef MF_DEBUG
+ printf("_toneeq_process GUI PIXELPIPE_FULL (%d), no full roi: roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
"d->post_shift=%f final post_scale=%f final post_shift=%f\n",
piece->pipe->type, roi_in->width, roi_in->height, d->post_auto_align, d->post_scale, d->post_shift,
post_scale, post_shift);
+#endif
}
}
else
@@ -1757,7 +1696,7 @@ void toneeq_process(dt_iop_module_t *self,
// No caching path: compute no matter what
// We are in PIXELPIPE_EXPORT or PIXELPIPE_THUMBNAIL
- compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d,
+ _compute_luminance_mask(in, luminance, roi_out->width, roi_out->height, d,
FALSE, NULL, NULL, NULL,
piece->pipe->type);
@@ -1766,7 +1705,7 @@ void toneeq_process(dt_iop_module_t *self,
float histogram_last_decile = 0;
if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM) {
- compute_mask_stats_from_full_image(piece, in, roi_in, d,
+ _compute_mask_stats_from_full_image(piece, in, roi_in, d,
&histogram_first_decile, &histogram_last_decile);
}
compute_auto_post_scale_shift(d->post_auto_align,
@@ -1774,12 +1713,14 @@ void toneeq_process(dt_iop_module_t *self,
&post_scale, &post_shift,
piece->pipe->type);
- printf("toneeq_process NO GUI PIXELPIPE (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
+#ifdef MF_DEBUG
+ printf("_toneeq_process NO GUI PIXELPIPE (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
"d->post_shift=%f final post_scale=%f final post_shift=%f\n",
piece->pipe->type,
roi_in->width, roi_in->height,
d->post_auto_align, d->post_scale, d->post_shift,
post_scale, post_shift);
+#endif
}
/**************************************************************************
@@ -1788,12 +1729,12 @@ void toneeq_process(dt_iop_module_t *self,
if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) && g->mask_display)
{
if (in_cropped) { // not elegant, but necessary because buffers are "restrict"
- display_luminance_mask(in_cropped, luminance, out, roi_out, roi_out,
+ _display_luminance_mask(in_cropped, luminance, out, roi_out, roi_out,
post_scale, post_shift, piece->pipe->type);
}
else
{
- display_luminance_mask(in, luminance, out, roi_in, roi_out, post_scale,
+ _display_luminance_mask(in, luminance, out, roi_in, roi_out, post_scale,
post_shift, piece->pipe->type);
}
piece->pipe->mask_display = DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
@@ -1801,23 +1742,27 @@ void toneeq_process(dt_iop_module_t *self,
else
{
if (in_cropped) {
- printf("toneeq_process: in_cropped roi %d %d %d %d\n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process: in_cropped roi %d %d %d %d\n",
roi_out->x, roi_out->y, roi_out->width, roi_out->height);
- compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+#endif
+ _compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
post_scale, post_shift,
piece->pipe->type);
- apply_toneequalizer(in_cropped, luminance, out,
+ _apply_toneequalizer(in_cropped, luminance, out,
roi_out, roi_out,
d, piece->pipe->type);
}
else
{
- printf("toneeq_process: in roi %d %d %d %d\n",
+#ifdef MF_DEBUG
+ printf("_toneeq_process: in roi %d %d %d %d\n",
roi_in->x, roi_in->y, roi_in->width, roi_in->height);
- compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+#endif
+ _compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
post_scale, post_shift,
piece->pipe->type);
- apply_toneequalizer(in, luminance, out,
+ _apply_toneequalizer(in, luminance, out,
roi_in, roi_out,
d, piece->pipe->type);
}
@@ -1837,7 +1782,6 @@ void toneeq_process(dt_iop_module_t *self,
}
}
-
void process(dt_iop_module_t *self,
dt_dev_pixelpipe_iop_t *piece,
const void *const restrict ivoid,
@@ -1845,7 +1789,7 @@ void process(dt_iop_module_t *self,
const dt_iop_roi_t *const roi_in,
const dt_iop_roi_t *const roi_out)
{
- toneeq_process(self, piece, ivoid, ovoid, roi_in, roi_out);
+ _toneeq_process(self, piece, ivoid, ovoid, roi_in, roi_out);
}
@@ -1857,40 +1801,32 @@ void process(dt_iop_module_t *self,
void init_global(dt_iop_module_so_t *self)
{
- printf("toneequalizer init_global\n");
dt_iop_toneequalizer_global_data_t *gd = malloc(sizeof(dt_iop_toneequalizer_global_data_t));
self->data = gd;
}
-
void cleanup_global(dt_iop_module_so_t *self)
{
- printf("toneequalizer cleanup_global\n");
free(self->data);
self->data = NULL;
}
-
void init_pipe(dt_iop_module_t *self,
dt_dev_pixelpipe_t *pipe,
dt_dev_pixelpipe_iop_t *piece)
{
- printf("toneequalizer init_pipe, pipe %d\n", pipe->type);
piece->data = dt_calloc1_align_type(dt_iop_toneequalizer_data_t);
}
-
void cleanup_pipe(dt_iop_module_t *self,
dt_dev_pixelpipe_t *pipe,
dt_dev_pixelpipe_iop_t *piece)
{
- printf("toneequalizer cleanup_pipe, pipe %d\n", pipe->type);
dt_free_align(piece->data);
piece->data = NULL;
}
-
void modify_roi_in(dt_iop_module_t *self,
dt_dev_pixelpipe_iop_t *piece,
const dt_iop_roi_t *roi_out,
@@ -1924,7 +1860,9 @@ void modify_roi_in(dt_iop_module_t *self,
roi_in->height = piece->buf_in.height;
roi_in->scale = 1.0f;
+#ifdef MF_DEBUG
printf("modify_roi_in: full image for auto-alignment\n");
+#endif
}
}
}
@@ -1957,8 +1895,8 @@ void modify_roi_in(dt_iop_module_t *self,
* should be used in combination with a tone curve or filmic.
*
***/
-static void get_channels_gains(float factors[NUM_SLIDERS],
- const dt_iop_toneequalizer_params_t *p)
+static void _get_channels_gains(float factors[NUM_SLIDERS],
+ const dt_iop_toneequalizer_params_t *p)
{
assert(NUM_SLIDERS == 9);
@@ -1974,14 +1912,13 @@ static void get_channels_gains(float factors[NUM_SLIDERS],
factors[8] = p->speculars; // +0 EV
}
-
-static void get_channels_factors(float factors[NUM_SLIDERS],
- const dt_iop_toneequalizer_params_t *p)
+static void _get_channels_factors(float factors[NUM_SLIDERS],
+ const dt_iop_toneequalizer_params_t *p)
{
assert(NUM_SLIDERS == 9);
// Get user-set NUM_SLIDERS gains in EV (log2)
- get_channels_gains(factors, p);
+ _get_channels_gains(factors, p);
// Convert from EV offsets to linear factors
DT_OMP_SIMD(aligned(factors:64))
@@ -1989,30 +1926,28 @@ static void get_channels_factors(float factors[NUM_SLIDERS],
factors[c] = exp2f(factors[c]);
}
-
__DT_CLONE_TARGETS__
-static inline float pixel_correction(const float exposure,
- const float *const restrict factors,
- const float sigma)
+static inline float _pixel_correction(const float exposure,
+ const float *const restrict factors,
+ const float sigma)
{
// build the correction for the current pixel
// as the sum of the contribution of each luminance channel
float result = 0.0f;
- const float gauss_denom = gaussian_denom(sigma);
+ const float gauss_denom = _gaussian_denom(sigma);
const float expo = fast_clamp(exposure, DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
DT_OMP_SIMD(aligned(centers_ops, factors:64) safelen(NUM_OCTAVES) reduction(+:result))
for(int i = 0; i < NUM_OCTAVES; ++i)
- result += gaussian_func(expo - centers_ops[i], gauss_denom) * factors[i];
+ result += _gaussian_func(expo - centers_ops[i], gauss_denom) * factors[i];
return fast_clamp(result, 0.25f, 4.0f);
}
-
__DT_CLONE_TARGETS__
-static gboolean compute_channels_factors(const float factors[NUM_OCTAVES],
- float out[NUM_SLIDERS],
- const float sigma)
+static gboolean _compute_channels_factors(const float factors[NUM_OCTAVES],
+ float out[NUM_SLIDERS],
+ const float sigma)
{
// Input factors are the weights for the radial-basis curve
// approximation of user params Output factors are the gains of the
@@ -2023,17 +1958,16 @@ static gboolean compute_channels_factors(const float factors[NUM_OCTAVES],
DT_OMP_FOR_SIMD(aligned(factors, out, centers_params:64) firstprivate(centers_params))
for(int i = 0; i < NUM_SLIDERS; ++i)
{
- // Compute the new NUM_SLIDERS factors; pixel_correction clamps the factors, so we don't
+ // Compute the new NUM_SLIDERS factors; _pixel_correction clamps the factors, so we don't
// need to check for validity here
- out[i] = pixel_correction(centers_params[i], factors, sigma);
+ out[i] = _pixel_correction(centers_params[i], factors, sigma);
}
return TRUE;
}
-
__DT_CLONE_TARGETS__
-static void compute_channels_gains(const float in[NUM_SLIDERS],
- float out[NUM_SLIDERS])
+static void _compute_channels_gains(const float in[NUM_SLIDERS],
+ float out[NUM_SLIDERS])
{
// Helper function to compute the new NUM_SLIDERS gains (log) from the factors (linear)
assert(NUM_OCTAVES == 8);
@@ -2042,9 +1976,8 @@ static void compute_channels_gains(const float in[NUM_SLIDERS],
out[i] = log2f(in[i]);
}
-
-static void commit_channels_gains(const float factors[NUM_SLIDERS],
- dt_iop_toneequalizer_params_t *p)
+static void _commit_channels_gains(const float factors[NUM_SLIDERS],
+ dt_iop_toneequalizer_params_t *p)
{
p->noise = factors[0];
p->ultra_deep_blacks = factors[1];
@@ -2063,9 +1996,8 @@ static void commit_channels_gains(const float factors[NUM_SLIDERS],
* Cache invalidation and initialization
*
****************************************************************************/
-static void gui_cache_init(dt_iop_module_t *self)
+static void _gui_cache_init(dt_iop_module_t *self)
{
- printf("gui_cache_init\n");
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
@@ -2114,10 +2046,11 @@ static void gui_cache_init(dt_iop_module_t *self)
dt_iop_gui_leave_critical_section(self);
}
-
-static void invalidate_luminance_cache(dt_iop_module_t *const self)
+static void _invalidate_luminance_cache(dt_iop_module_t *const self)
{
- printf("invalidate_luminance_cache\n");
+#ifdef MF_DEBUG
+ printf("_invalidate_luminance_cache\n");
+#endif
// Invalidate the private luminance cache and histogram when
// the luminance mask extraction parameters have changed
dt_iop_toneequalizer_gui_data_t *const restrict g = self->gui_data;
@@ -2134,10 +2067,11 @@ static void invalidate_luminance_cache(dt_iop_module_t *const self)
dt_iop_refresh_all(self);
}
-
-static void invalidate_lut_and_histogram(dt_iop_module_t *const self)
+static void _invalidate_lut_and_histogram(dt_iop_module_t *const self)
{
- printf("invalidate_lut_and_histogram\n");
+#ifdef MF_DEBUG
+ printf("_invalidate_lut_and_histogram\n");
+#endif
dt_iop_toneequalizer_gui_data_t *const restrict g = self->gui_data;
dt_iop_gui_enter_critical_section(self);
@@ -2154,24 +2088,23 @@ static void invalidate_lut_and_histogram(dt_iop_module_t *const self)
* Curve Interpolation
*
****************************************************************************/
-static inline void build_interpolation_matrix(float A[NUM_SLIDERS * NUM_OCTAVES],
- const float sigma)
+static inline void _build_interpolation_matrix(float A[NUM_SLIDERS * NUM_OCTAVES],
+ const float sigma)
{
// Build the symmetrical definite positive part of the augmented matrix
// of the radial-basis interpolation weights
- const float gauss_denom = gaussian_denom(sigma);
+ const float gauss_denom = _gaussian_denom(sigma);
DT_OMP_SIMD(aligned(A, centers_ops, centers_params:64) collapse(2))
for(int i = 0; i < NUM_SLIDERS; ++i)
for(int j = 0; j < NUM_OCTAVES; ++j)
A[i * NUM_OCTAVES + j] =
- gaussian_func(centers_params[i] - centers_ops[j], gauss_denom);
+ _gaussian_func(centers_params[i] - centers_ops[j], gauss_denom);
}
-
__DT_CLONE_TARGETS__
-static inline void compute_gui_curve(dt_iop_toneequalizer_gui_data_t *g)
+static inline void _compute_gui_curve(dt_iop_toneequalizer_gui_data_t *g)
{
// Compute the curve of the exposure corrections in EV,
// offset and scale it for display in GUI widget graph
@@ -2188,13 +2121,12 @@ static inline void compute_gui_curve(dt_iop_toneequalizer_gui_data_t *g)
// build the inset graph curve LUT
// the x range is [-14;+2] EV
const float x = (8.0f * (((float)k) / ((float)(UI_HISTO_SAMPLES - 1)))) - 8.0f;
- // curve[k] = offset - log2f(pixel_correction(x, factors, sigma)) / scaling;
- curve[k] = log2f(pixel_correction(x, factors, sigma));
+ // curve[k] = offset - log2f(_pixel_correction(x, factors, sigma)) / scaling;
+ curve[k] = log2f(_pixel_correction(x, factors, sigma));
}
}
-
-static inline gboolean curve_interpolation(dt_iop_module_t *self)
+static inline gboolean _curve_interpolation(dt_iop_module_t *self)
{
dt_iop_toneequalizer_params_t *p = self->params;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -2207,7 +2139,7 @@ static inline gboolean curve_interpolation(dt_iop_module_t *self)
if(!g->interpolation_valid)
{
- build_interpolation_matrix(g->interpolation_matrix, g->sigma);
+ _build_interpolation_matrix(g->interpolation_matrix, g->sigma);
g->interpolation_valid = TRUE;
g->factors_valid = FALSE;
}
@@ -2215,7 +2147,7 @@ static inline gboolean curve_interpolation(dt_iop_module_t *self)
if(!g->user_param_valid)
{
float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
- get_channels_factors(factors, p);
+ _get_channels_factors(factors, p);
dt_simd_memcpy(factors, g->temp_user_params, NUM_SLIDERS);
g->user_param_valid = TRUE;
g->factors_valid = FALSE;
@@ -2234,7 +2166,7 @@ static inline gboolean curve_interpolation(dt_iop_module_t *self)
if(!g->gui_curve_valid && g->factors_valid)
{
- compute_gui_curve(g);
+ _compute_gui_curve(g);
g->gui_curve_valid = TRUE;
}
@@ -2257,8 +2189,10 @@ void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_
dt_iop_toneequalizer_data_t *d = piece->data;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+#ifdef MF_DEBUG
// printf("commit_params pipe=%d align=%d p->post_scale=%f p->post_shift=%f d->post_scale=%f d->post_shift=%f\n",
// pipe->type, p->post_auto_align, p->post_scale, p->post_shift, d->post_scale, d->post_shift);
+#endif
// Trivial params passing
d->lum_estimator = p->lum_estimator;
@@ -2294,7 +2228,7 @@ void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_
g->user_param_valid = FALSE; // force updating NUM_SLIDERS factors
dt_iop_gui_leave_critical_section(self);
- curve_interpolation(self);
+ _curve_interpolation(self);
dt_iop_gui_enter_critical_section(self);
dt_simd_memcpy(g->factors, d->factors, NUM_OCTAVES);
@@ -2304,10 +2238,10 @@ void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_
{
// No cache : Build / Solve interpolation matrix
float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
- get_channels_factors(factors, p);
+ _get_channels_factors(factors, p);
float A[NUM_SLIDERS * NUM_OCTAVES] DT_ALIGNED_ARRAY;
- build_interpolation_matrix(A, p->smoothing);
+ _build_interpolation_matrix(A, p->smoothing);
pseudo_solve(A, factors, NUM_SLIDERS, NUM_OCTAVES, FALSE);
dt_simd_memcpy(factors, d->factors, NUM_OCTAVES);
@@ -2320,13 +2254,15 @@ void commit_params(dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_
* GUI Helpers
*
****************************************************************************/
-static inline void compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLES],
- int histogram[UI_HISTO_SAMPLES],
- float histogram_scale,
- float histogram_shift,
- int *max_histogram)
+static inline void _compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLES],
+ int histogram[UI_HISTO_SAMPLES],
+ float histogram_scale,
+ float histogram_shift,
+ int *max_histogram)
{
- printf("compute_gui_histogram scale=%f shift=%f\n", histogram_scale, histogram_shift);
+#ifdef MF_DEBUG
+ printf("_compute_gui_histogram scale=%f shift=%f\n", histogram_scale, histogram_shift);
+#endif
// (Re)init the histogram
memset(histogram, 0, sizeof(int) * UI_HISTO_SAMPLES);
@@ -2337,10 +2273,10 @@ static inline void compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLES
for(size_t k = 0; k < HIRES_HISTO_SAMPLES; ++k)
{
// from [0...HIRES_HISTO_SAMPLES] to [-16...8EV]
- float EV = temp_ev_range * (float)k / (float)(HIRES_HISTO_SAMPLES - 1) + HIRES_HISTO_MIN_EV;
+ const float EV = temp_ev_range * (float)k / (float)(HIRES_HISTO_SAMPLES - 1) + HIRES_HISTO_MIN_EV;
// apply shift & scale to the EV value
- const float shift_scaled_EV = post_scale_shift(EV, histogram_scale, histogram_shift);
+ const float shift_scaled_EV = _post_scale_shift(EV, histogram_scale, histogram_shift);
// from [-8...0] EV to [0...UI_HISTO_SAMPLES]
const int i = CLAMP((int)(((shift_scaled_EV + 8.0f) / 8.0f) * (float)UI_HISTO_SAMPLES), 0, UI_HISTO_SAMPLES - 1);
@@ -2357,8 +2293,7 @@ static inline void compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLES
}
}
-
-static inline void update_gui_histogram(dt_iop_module_t *const self)
+static inline void _update_gui_histogram(dt_iop_module_t *const self)
{
dt_iop_toneequalizer_gui_data_t *const g = self->gui_data;
dt_iop_toneequalizer_params_t *const p = self->params;
@@ -2376,7 +2311,7 @@ static inline void update_gui_histogram(dt_iop_module_t *const self)
&p->post_scale, &p->post_shift,
999);
- compute_gui_histogram(g->hires_histogram, g->histogram,
+ _compute_gui_histogram(g->hires_histogram, g->histogram,
p->post_scale, p->post_shift,
&g->max_histogram);
@@ -2396,15 +2331,14 @@ static inline void update_gui_histogram(dt_iop_module_t *const self)
g->image_EV_per_UI_sample = (mask_EV_of_target * mask_to_image * target_to_full) / (float)UI_HISTO_SAMPLES;
if (g->show_two_histograms)
- compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->image_max_histogram);
+ _compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->image_max_histogram);
g->gui_histogram_valid = TRUE;
}
dt_iop_gui_leave_critical_section(self);
}
-
-static void show_guiding_controls(dt_iop_module_t *self)
+static void _show_guiding_controls(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
const dt_iop_toneequalizer_params_t *p = self->params;
@@ -2444,7 +2378,8 @@ static void show_guiding_controls(dt_iop_module_t *self)
}
}
- switch(p->post_auto_align) {
+ switch(p->post_auto_align)
+ {
case(DT_TONEEQ_ALIGN_LEFT):
case(DT_TONEEQ_ALIGN_CENTER):
case(DT_TONEEQ_ALIGN_RIGHT):
@@ -2476,27 +2411,30 @@ static void show_guiding_controls(dt_iop_module_t *self)
****************************************************************************/
void gui_update(dt_iop_module_t *self)
{
+#ifdef MF_DEBUG
printf("gui_update\n");
+#endif
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_toneequalizer_params_t *p = self->params;
dt_bauhaus_slider_set(g->smoothing, logf(p->smoothing) / logf(sqrtf(2.0f)) - 1.0f);
- show_guiding_controls(self);
+ _show_guiding_controls(self);
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
}
-
void gui_changed(dt_iop_module_t *self,
GtkWidget *w,
void *previous)
{
+#ifdef MF_DEBUG
printf("gui_changed\n");
+#endif
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_toneequalizer_params_t *p = self->params;
@@ -2506,23 +2444,23 @@ void gui_changed(dt_iop_module_t *self,
|| w == g->iterations
|| w == g->quantization)
{
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
}
else if(w == g->filter)
{
- invalidate_luminance_cache(self);
- show_guiding_controls(self);
+ _invalidate_luminance_cache(self);
+ _show_guiding_controls(self);
}
else if(w == g->contrast_boost
|| w == g->exposure_boost)
{
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
dt_bauhaus_widget_set_quad_active(w, FALSE);
}
else if (w == g->post_scale
|| w == g->post_shift)
{
- invalidate_lut_and_histogram(self);
+ _invalidate_lut_and_histogram(self);
}
else if (w == g->post_auto_align)
{
@@ -2537,13 +2475,13 @@ void gui_changed(dt_iop_module_t *self,
dt_bauhaus_slider_set(g->post_shift, p->post_shift);
--darktable.gui->reset;
- invalidate_luminance_cache(self);
- show_guiding_controls(self);
+ _invalidate_luminance_cache(self);
+ _show_guiding_controls(self);
}
}
-
-static void smoothing_callback(GtkWidget *slider, dt_iop_module_t *self)
+static void _smoothing_callback(GtkWidget *slider,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return;
dt_iop_toneequalizer_params_t *p = self->params;
@@ -2552,10 +2490,10 @@ static void smoothing_callback(GtkWidget *slider, dt_iop_module_t *self)
p->smoothing= powf(sqrtf(2.0f), 1.0f + dt_bauhaus_slider_get(slider));
float factors[NUM_SLIDERS] DT_ALIGNED_ARRAY;
- get_channels_factors(factors, p);
+ _get_channels_factors(factors, p);
// Solve the interpolation by least-squares to check the validity of the smoothing param
- if(!curve_interpolation(self))
+ if(!_curve_interpolation(self))
dt_control_log
(_("the interpolation is unstable, decrease the curve smoothing"));
@@ -2568,8 +2506,8 @@ static void smoothing_callback(GtkWidget *slider, dt_iop_module_t *self)
dt_iop_color_picker_reset(self, TRUE);
}
-
-static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
+static void _auto_adjust_exposure_boost(GtkWidget *quad,
+ dt_iop_module_t *self)
{
dt_iop_toneequalizer_params_t *p = self->params;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -2585,7 +2523,7 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
dt_bauhaus_slider_set(g->exposure_boost, p->exposure_boost);
--darktable.gui->reset;
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
dt_dev_add_history_item(darktable.develop, self, TRUE);
return;
}
@@ -2604,7 +2542,7 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
g->gui_histogram_valid = FALSE;
dt_iop_gui_leave_critical_section(self);
- update_gui_histogram(self);
+ _update_gui_histogram(self);
// calculate exposure correction
const float fd_new = exp2f(g->prv_histogram_first_decile);
@@ -2626,15 +2564,15 @@ static void auto_adjust_exposure_boost(GtkWidget *quad, dt_iop_module_t *self)
++darktable.gui->reset;
dt_bauhaus_slider_set(g->exposure_boost, p->exposure_boost);
--darktable.gui->reset;
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
dt_dev_add_history_item(darktable.develop, self, TRUE);
// Unlock the colour picker so we can display our own custom cursor
dt_iop_color_picker_reset(self, TRUE);
}
-
-static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
+static void _auto_adjust_contrast_boost(GtkWidget *quad,
+ dt_iop_module_t *self)
{
dt_iop_toneequalizer_params_t *p = self->params;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -2650,7 +2588,7 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
--darktable.gui->reset;
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
dt_dev_add_history_item(darktable.develop, self, TRUE);
return;
}
@@ -2666,7 +2604,7 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
g->gui_histogram_valid = FALSE;
dt_iop_gui_leave_critical_section(self);
- update_gui_histogram(self);
+ _update_gui_histogram(self);
// calculate contrast correction
const float fd_new = exp2f(g->prv_histogram_first_decile);
@@ -2704,17 +2642,16 @@ static void auto_adjust_contrast_boost(GtkWidget *quad, dt_iop_module_t *self)
++darktable.gui->reset;
dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
--darktable.gui->reset;
- invalidate_luminance_cache(self);
+ _invalidate_luminance_cache(self);
dt_dev_add_history_item(darktable.develop, self, TRUE);
// Unlock the colour picker so we can display our own custom cursor
dt_iop_color_picker_reset(self, TRUE);
}
-
-static void show_luminance_mask_callback(GtkWidget *togglebutton,
- GdkEventButton *event,
- dt_iop_module_t *self)
+static void _show_luminance_mask_callback(GtkWidget *togglebutton,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return;
dt_iop_request_focus(self);
@@ -2742,11 +2679,10 @@ static void show_luminance_mask_callback(GtkWidget *togglebutton,
dt_iop_color_picker_reset(self, TRUE);
}
-
// TODO MF: Remove this again? Two histograms are only useful for debugging.
-static void show_two_histograms_callback(GtkWidget *togglebutton,
- GdkEventButton *event,
- dt_iop_module_t *self)
+static void _show_two_histograms_callback(GtkWidget *togglebutton,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return;
dt_iop_request_focus(self);
@@ -2790,13 +2726,12 @@ static void _get_point(dt_iop_module_t *self, const int c_x, const int c_y, int
*y = pts[1];
}
-
__DT_CLONE_TARGETS__
-static float get_luminance_from_buffer(const float *const buffer,
- const size_t width,
- const size_t height,
- const size_t x,
- const size_t y)
+static float _get_luminance_from_buffer(const float *const buffer,
+ const size_t width,
+ const size_t height,
+ const size_t x,
+ const size_t y)
{
// Get the weighted average luminance of the 3×3 pixels region centered in (x, y)
// x and y are ratios in [0, 1] of the width and height
@@ -2839,8 +2774,7 @@ static float get_luminance_from_buffer(const float *const buffer,
return luminance;
}
-
-// unify with get_luminance_from_buffer
+// unify with _get_luminance_from_buffer
static float _luminance_from_thumb_preview_buf(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -2854,14 +2788,13 @@ static float _luminance_from_thumb_preview_buf(dt_iop_module_t *self)
_get_point(self, c_x, c_y, &b_x, &b_y);
- return get_luminance_from_buffer(g->preview_buf,
+ return _get_luminance_from_buffer(g->preview_buf,
g->preview_buf_width,
g->preview_buf_height,
b_x,
b_y);
}
-
void update_exposure_sliders(dt_iop_toneequalizer_gui_data_t *g, dt_iop_toneequalizer_params_t *p)
{
// Params to GUI
@@ -2878,15 +2811,13 @@ void update_exposure_sliders(dt_iop_toneequalizer_gui_data_t *g, dt_iop_toneequa
--darktable.gui->reset;
}
-
-static gboolean in_mask_editing(dt_iop_module_t *self)
+static gboolean _in_mask_editing(dt_iop_module_t *self)
{
const dt_develop_t *dev = self->dev;
return dev->form_gui && dev->form_visible;
}
-
-static void switch_cursors(dt_iop_module_t *self)
+static void _switch_cursors(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -2896,7 +2827,7 @@ static void switch_cursors(dt_iop_module_t *self)
GtkWidget *widget = dt_ui_main_window(darktable.gui->ui);
// if we are editing masks or using colour-pickers, do not display controls
- if(in_mask_editing(self)
+ if(_in_mask_editing(self)
|| dt_iop_canvas_not_sensitive(self->dev))
{
// display default cursor
@@ -2965,7 +2896,6 @@ static void switch_cursors(dt_iop_module_t *self)
}
}
-
int mouse_moved(dt_iop_module_t *self,
const float pzx,
const float pzy,
@@ -3008,15 +2938,14 @@ int mouse_moved(dt_iop_module_t *self,
// store the actual exposure too, to spare I/O op
if(g->cursor_valid && !dev->full.pipe->processing && g->prv_luminance_valid) {
const float lum = log2f(_luminance_from_thumb_preview_buf(self));
- g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+ g->cursor_exposure = fast_clamp(_post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
}
- switch_cursors(self);
+ _switch_cursors(self);
return 1;
}
-
int mouse_leave(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -3039,12 +2968,11 @@ int mouse_leave(dt_iop_module_t *self)
return 1;
}
-
-static inline gboolean set_new_params_interactive(const float control_exposure,
- const float exposure_offset,
- const float blending_sigma,
- dt_iop_toneequalizer_gui_data_t *g,
- dt_iop_toneequalizer_params_t *p)
+static inline gboolean _set_new_params_interactive(const float control_exposure,
+ const float exposure_offset,
+ const float blending_sigma,
+ dt_iop_toneequalizer_gui_data_t *g,
+ dt_iop_toneequalizer_params_t *p)
{
// Apply an exposure offset optimized smoothly over all the exposure NUM_SLIDERS,
// taking user instruction to apply exposure_offset EV at control_exposure EV,
@@ -3053,12 +2981,12 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
// Raise the user params accordingly to control correction and
// distance from cursor exposure to blend smoothly the desired
// correction
- const float std = gaussian_denom(blending_sigma);
+ const float std = _gaussian_denom(blending_sigma);
if(g->user_param_valid)
{
for(int i = 0; i < NUM_SLIDERS; ++i)
g->temp_user_params[i] *=
- exp2f(gaussian_func(centers_params[i] - control_exposure, std) * exposure_offset);
+ exp2f(_gaussian_func(centers_params[i] - control_exposure, std) * exposure_offset);
}
// Get the new weights for the radial-basis approximation
@@ -3071,7 +2999,7 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
// Compute new user params for NUM_SLIDERS and store them locally
if(g->user_param_valid)
- g->user_param_valid = compute_channels_factors(factors, g->temp_user_params, g->sigma);
+ g->user_param_valid = _compute_channels_factors(factors, g->temp_user_params, g->sigma);
if(!g->user_param_valid) dt_control_log(_("some parameters are out-of-bounds"));
const gboolean commit = g->user_param_valid;
@@ -3084,13 +3012,13 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
// Convert the linear temp parameters to log gains and commit
float gains[NUM_SLIDERS] DT_ALIGNED_ARRAY;
- compute_channels_gains(g->temp_user_params, gains);
- commit_channels_gains(gains, p);
+ _compute_channels_gains(g->temp_user_params, gains);
+ _commit_channels_gains(gains, p);
}
else
{
// Reset the GUI copy of user params
- get_channels_factors(factors, p);
+ _get_channels_factors(factors, p);
dt_simd_memcpy(factors, g->temp_user_params, NUM_SLIDERS);
g->user_param_valid = TRUE;
}
@@ -3098,7 +3026,6 @@ static inline gboolean set_new_params_interactive(const float control_exposure,
return commit;
}
-
int scrolled(dt_iop_module_t *self,
const float x,
const float y,
@@ -3117,7 +3044,7 @@ int scrolled(dt_iop_module_t *self,
if(!self->enabled)
if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
- if(in_mask_editing(self)) return 0;
+ if(_in_mask_editing(self)) return 0;
// ALT/Option should work for zooming
if (dt_modifier_is(state, GDK_MOD1_MASK)) return 0;
@@ -3139,7 +3066,7 @@ int scrolled(dt_iop_module_t *self,
dt_iop_gui_enter_critical_section(self);
const float lum = log2f(_luminance_from_thumb_preview_buf(self));
- g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+ g->cursor_exposure = fast_clamp(_post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
dt_iop_gui_leave_critical_section(self);
@@ -3158,7 +3085,7 @@ int scrolled(dt_iop_module_t *self,
// Get the desired correction on exposure NUM_SLIDERS
dt_iop_gui_enter_critical_section(self);
- const gboolean commit = set_new_params_interactive(g->cursor_exposure, offset,
+ const gboolean commit = _set_new_params_interactive(g->cursor_exposure, offset,
g->sigma * g->sigma / 2.0f, g, p);
dt_iop_gui_leave_critical_section(self);
@@ -3214,10 +3141,9 @@ void cairo_draw_hatches(cairo_t *cr,
}
}
-
-static void get_shade_from_luminance(cairo_t *cr,
- const float luminance,
- const float alpha)
+static void _get_shade_from_luminance(cairo_t *cr,
+ const float luminance,
+ const float alpha)
{
// TODO: fetch screen gamma from ICC display profile
const float gamma = 1.0f / 2.2f;
@@ -3225,22 +3151,21 @@ static void get_shade_from_luminance(cairo_t *cr,
cairo_set_source_rgba(cr, shade, shade, shade, alpha);
}
-
-static void draw_exposure_cursor(cairo_t *cr,
- const double pointerx,
- const double pointery,
- const double radius,
- const float luminance,
- const float zoom_scale,
- const int instances,
- const float alpha)
+static void _draw_exposure_cursor(cairo_t *cr,
+ const double pointerx,
+ const double pointery,
+ const double radius,
+ const float luminance,
+ const float zoom_scale,
+ const int instances,
+ const float alpha)
{
// Draw a circle cursor filled with a grey shade corresponding to a luminance value
// or hatches if the value is above the overexposed threshold
const double radius_z = radius / zoom_scale;
- get_shade_from_luminance(cr, luminance, alpha);
+ _get_shade_from_luminance(cr, luminance, alpha);
cairo_arc(cr, pointerx, pointery, radius_z, 0, 2 * M_PI);
cairo_fill_preserve(cr);
cairo_save(cr);
@@ -3257,10 +3182,9 @@ static void draw_exposure_cursor(cairo_t *cr,
cairo_restore(cr);
}
-
-static void match_color_to_background(cairo_t *cr,
- const float exposure,
- const float alpha)
+static void _match_color_to_background(cairo_t *cr,
+ const float exposure,
+ const float alpha)
{
float shade = 0.0f;
// TODO: put that as a preference in darktablerc
@@ -3271,10 +3195,9 @@ static void match_color_to_background(cairo_t *cr,
else
shade = (fmaxf(exposure / contrast, -5.0f) + 2.5f);
- get_shade_from_luminance(cr, exp2f(shade), alpha);
+ _get_shade_from_luminance(cr, exp2f(shade), alpha);
}
-
void gui_post_expose(dt_iop_module_t *self,
cairo_t *cr,
const float width,
@@ -3290,7 +3213,7 @@ void gui_post_expose(dt_iop_module_t *self,
dt_iop_toneequalizer_params_t *p = self->params;
// if we are editing masks, do not display controls
- if(in_mask_editing(self)) return;
+ if(_in_mask_editing(self)) return;
dt_iop_gui_enter_critical_section(self);
@@ -3310,7 +3233,7 @@ void gui_post_expose(dt_iop_module_t *self,
// re-read the exposure in case it has changed
if(g->prv_luminance_valid && self->enabled) {
const float lum = log2f(_luminance_from_thumb_preview_buf(self));
- g->cursor_exposure = fast_clamp(post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
+ g->cursor_exposure = fast_clamp(_post_scale_shift(lum, p->post_scale, p->post_shift), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
}
dt_iop_gui_enter_critical_section(self);
@@ -3331,7 +3254,7 @@ void gui_post_expose(dt_iop_module_t *self,
luminance_in = exp2f(exposure_in);
// Get the corresponding correction and compute resulting exposure
- correction = log2f(pixel_correction(exposure_in, g->factors, g->sigma));
+ correction = log2f(_pixel_correction(exposure_in, g->factors, g->sigma));
exposure_out = exposure_in + correction;
luminance_out = exp2f(exposure_out);
}
@@ -3347,7 +3270,7 @@ void gui_post_expose(dt_iop_module_t *self,
const double fill_width = DT_PIXEL_APPLY_DPI(4. / zoom_scale);
// setting fill bars
- match_color_to_background(cr, exposure_out, 1.0);
+ _match_color_to_background(cr, exposure_out, 1.0);
cairo_set_line_width(cr, 2.0 * fill_width);
cairo_move_to(cr, x_pointer - setting_offset_x, y_pointer);
@@ -3379,9 +3302,9 @@ void gui_post_expose(dt_iop_module_t *self,
cairo_stroke(cr);
// draw exposure cursor
- draw_exposure_cursor(cr, x_pointer, y_pointer, outer_radius,
+ _draw_exposure_cursor(cr, x_pointer, y_pointer, outer_radius,
luminance_in, zoom_scale, 6, .9);
- draw_exposure_cursor(cr, x_pointer, y_pointer, inner_radius,
+ _draw_exposure_cursor(cr, x_pointer, y_pointer, inner_radius,
luminance_out, zoom_scale, 3, .9);
// Create Pango objects : texts
@@ -3407,7 +3330,7 @@ void gui_post_expose(dt_iop_module_t *self,
pango_layout_get_pixel_extents(layout, &ink, NULL);
// Draw the text plain blackground
- get_shade_from_luminance(cr, luminance_out, 0.75);
+ _get_shade_from_luminance(cr, luminance_out, 0.75);
cairo_rectangle(cr,
x_pointer + (outer_radius + 2. * g->inner_padding) / zoom_scale,
y_pointer - ink.y - ink.height / 2.0 - g->inner_padding / zoom_scale,
@@ -3416,7 +3339,7 @@ void gui_post_expose(dt_iop_module_t *self,
cairo_fill(cr);
// Display the EV reading
- match_color_to_background(cr, exposure_out, 1.0);
+ _match_color_to_background(cr, exposure_out, 1.0);
cairo_move_to(cr, x_pointer + (outer_radius + 4. * g->inner_padding) / zoom_scale,
y_pointer - ink.y - ink.height / 2.);
pango_cairo_show_layout(cr, layout);
@@ -3443,7 +3366,6 @@ void gui_post_expose(dt_iop_module_t *self,
}
}
-
static void _develop_distort_callback(gpointer instance,
dt_iop_module_t *self)
{
@@ -3462,7 +3384,6 @@ static void _develop_distort_callback(gpointer instance,
dt_dev_reprocess_preview(darktable.develop);
}
-
static void _set_distort_signal(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -3473,7 +3394,6 @@ static void _set_distort_signal(dt_iop_module_t *self)
}
}
-
static void _unset_distort_signal(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -3484,15 +3404,16 @@ static void _unset_distort_signal(dt_iop_module_t *self)
}
}
-
void gui_focus(dt_iop_module_t *self, gboolean in)
{
+#ifdef MF_DEBUG
printf("gui_focus %d\n", in);
+#endif
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_gui_enter_critical_section(self);
g->has_focus = in;
dt_iop_gui_leave_critical_section(self);
- switch_cursors(self);
+ _switch_cursors(self);
if(!in)
{
//lost focus - stop showing mask
@@ -3516,7 +3437,6 @@ void gui_focus(dt_iop_module_t *self, gboolean in)
}
}
-
static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
GtkWidget *widget,
dt_iop_toneequalizer_gui_data_t *const restrict g)
@@ -3527,9 +3447,9 @@ static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
if(g->cst)
cairo_surface_destroy(g->cst);
- g->allocation.height -= DT_RESIZE_HANDLE_SIZE;
+ g->graph_w_gradients_height = g->allocation.height - DT_RESIZE_HANDLE_SIZE - g->inner_padding;
g->cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
- g->allocation.width, g->allocation.height);
+ g->allocation.width, g->graph_w_gradients_height);
if(g->cr)
cairo_destroy(g->cr);
@@ -3569,7 +3489,7 @@ static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
// align the right border on sliders:
g->graph_width = g->allocation.width - g->inset - 2.0 * g->line_height;
// give room to nodes:
- g->graph_height = g->allocation.height - g->inset - 2.0 * g->line_height;
+ g->graph_height = g->graph_w_gradients_height - g->inset - 2.0 * g->line_height;
g->gradient_left_limit = 0.0;
g->gradient_right_limit = g->graph_width;
g->gradient_top_limit = g->graph_height + 2 * g->inner_padding;
@@ -3577,7 +3497,7 @@ static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
g->legend_top_limit = -0.5 * g->line_height - 2.0 * g->inner_padding;
g->x_label = g->graph_width + g->sign_width + 3.0 * g->inner_padding;
- gtk_render_background(g->context, g->cr, 0, 0, g->allocation.width, g->allocation.height);
+ gtk_render_background(g->context, g->cr, 0, 0, g->allocation.width, g->graph_w_gradients_height);
// set the graph as the origin of the coordinates
cairo_translate(g->cr, g->line_height + 2 * g->inner_padding,
@@ -3660,9 +3580,8 @@ static inline gboolean _init_drawing(dt_iop_module_t *const restrict self,
return TRUE;
}
-
// must be called while holding self->gui_lock
-static inline void init_nodes_x(dt_iop_toneequalizer_gui_data_t *g)
+static inline void _init_nodes_x(dt_iop_toneequalizer_gui_data_t *g)
{
if(g == NULL) return;
@@ -3674,9 +3593,8 @@ static inline void init_nodes_x(dt_iop_toneequalizer_gui_data_t *g)
}
}
-
// must be called while holding self->gui_lock
-static inline void init_nodes_y(dt_iop_toneequalizer_gui_data_t *g)
+static inline void _init_nodes_y(dt_iop_toneequalizer_gui_data_t *g)
{
if(g == NULL) return;
@@ -3689,8 +3607,10 @@ static inline void init_nodes_y(dt_iop_toneequalizer_gui_data_t *g)
}
}
-
-static inline void interpolate_gui_color(GdkRGBA a, GdkRGBA b, float t, GdkRGBA *out)
+static inline void _interpolate_gui_color(GdkRGBA a,
+ GdkRGBA b,
+ float t,
+ GdkRGBA *out)
{
float t_clamp = fast_clamp(t, 0.0f, 1.0f);
out->red = a.red + t_clamp * (b.red - a.red);
@@ -3699,8 +3619,7 @@ static inline void interpolate_gui_color(GdkRGBA a, GdkRGBA b, float t, GdkRGBA
out->alpha = a.alpha + t_clamp * (b.alpha - a.alpha);
}
-
-static inline void compute_gui_curve_colors(dt_iop_module_t *self)
+static inline void _compute_gui_curve_colors(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
dt_iop_toneequalizer_params_t *p = self->params;
@@ -3718,8 +3637,6 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
const int shadows_limit = (int)UI_HISTO_SAMPLES * 0.3333;
const int highlights_limit = (int)UI_HISTO_SAMPLES * 0.6666;
- // printf("ev_dx=%f filter_active=%d filter=%d\n", ev_dx, filter_active, p->filter);
-
if (!g->gui_histogram_valid || !g->gui_curve_valid) {
// the module is not completely initialized, set all colors to standard
for(int k = 0; k < UI_HISTO_SAMPLES; k++)
@@ -3740,7 +3657,7 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
// contrast that the user probably expects.
const float x_dist = ((float)(shadows_limit - k) / (float)UI_HISTO_SAMPLES) * 8.0f;
const float color_dist = fminf(x_dist, -curve[k]);
- interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ _interpolate_gui_color(standard, warning, color_dist, &temp_color);
colors[k] = temp_color;
}
else if(filter_active && k > highlights_limit && curve[k] > 0.0f)
@@ -3749,7 +3666,7 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
// contrast that the user probably expects.
const float x_dist = ((float)(k - highlights_limit) / (float)UI_HISTO_SAMPLES) * 8.0f;
const float color_dist = fminf(x_dist, curve[k]);
- interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ _interpolate_gui_color(standard, warning, color_dist, &temp_color);
colors[k] = temp_color;
}
else if(!filter_active && k < shadows_limit && curve[k] > 0.0f)
@@ -3757,7 +3674,7 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
// Raise shadows without filter, this leads to a loss of contrast.
const float x_dist = ((float)(shadows_limit - k) / (float)UI_HISTO_SAMPLES) * 8.0f;
const float color_dist = fminf(x_dist, curve[k]);
- interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ _interpolate_gui_color(standard, warning, color_dist, &temp_color);
colors[k] = temp_color;
}
else if(!filter_active && k > highlights_limit && curve[k] < 0.0f)
@@ -3765,7 +3682,7 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
// Lower highlights without filter, this leads to a loss of contrast.
const float x_dist = ((float)(k - highlights_limit) / (float)UI_HISTO_SAMPLES) * 8.0f;
const float color_dist = fminf(x_dist, -curve[k]);
- interpolate_gui_color(standard, warning, color_dist, &temp_color);
+ _interpolate_gui_color(standard, warning, color_dist, &temp_color);
colors[k] = temp_color;
}
@@ -3781,14 +3698,15 @@ static inline void compute_gui_curve_colors(dt_iop_module_t *self)
colors[k] = error;
}
+#ifdef MF_DEBUG
// printf("curve[%d]=%f ev_dx=%f ev_dy=%f steepness=%f colors[%d]=%f\n", k, curve[k], ev_dx, ev_dy, steepness, k, colors[k].red);
+#endif
}
}
-
-static gboolean area_draw(GtkWidget *widget,
- cairo_t *cr,
- dt_iop_module_t *self)
+static gboolean _area_draw(GtkWidget *widget,
+ cairo_t *cr,
+ dt_iop_module_t *self)
{
// Draw the widget equalizer view
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -3813,11 +3731,11 @@ static gboolean area_draw(GtkWidget *widget,
dt_iop_gui_leave_critical_section(self);
// Refresh cached UI elements
- update_gui_histogram(self); // TODO MF: Delay until both PREVIEW and FULL pixelpipes are ready
- curve_interpolation(self);
+ _update_gui_histogram(self); // TODO MF: Delay until both PREVIEW and FULL pixelpipes are ready
+ _curve_interpolation(self);
// The colors depend on the histogram and the curve
- compute_gui_curve_colors(self);
+ _compute_gui_curve_colors(self);
// Draw graph background
cairo_set_line_width(g->cr, DT_PIXEL_APPLY_DPI(0.5));
@@ -3839,11 +3757,7 @@ static gboolean area_draw(GtkWidget *widget,
if(g->gui_histogram_valid && self->enabled)
{
- float histo_height;
- if (g->two_histograms_display)
- histo_height = 0.5 * g->graph_height;
- else
- histo_height = g->graph_height;
+ const float histo_height = g->graph_height * (g->two_histograms_display ? 0.5 : 1.0);
// draw the mask histogram background
set_color(g->cr, darktable.bauhaus->inset_histogram);
@@ -3874,15 +3788,13 @@ static gboolean area_draw(GtkWidget *widget,
const float y_temp = fast_clamp((float)(g->image_histogram[k]) / (float)(g->image_max_histogram), -1.0f, 1.0f) * 0.96;
cairo_line_to(g->cr, (x_temp + 8.0) * g->graph_width / 8.0,
y_temp * histo_height);
- //if (k % 5 == 0)
- // printf("g->image_histogram[%d] = %d, image_max_histogram = %d\n", k, g->image_histogram[k], g->image_max_histogram);
}
cairo_line_to(g->cr, g->graph_width, 0);
cairo_close_path(g->cr);
cairo_fill(g->cr);
}
- if(post_scale_shift(g->prv_histogram_last_decile, p->post_scale, p->post_shift) > -0.1f)
+ if(_post_scale_shift(g->prv_histogram_last_decile, p->post_scale, p->post_shift) > -0.1f)
{
// histogram overflows controls in highlights : display warning
cairo_save(g->cr);
@@ -3894,7 +3806,7 @@ static gboolean area_draw(GtkWidget *widget,
cairo_restore(g->cr);
}
- if(post_scale_shift(g->prv_histogram_first_decile, p->post_scale, p->post_shift) < -7.9f)
+ if(_post_scale_shift(g->prv_histogram_first_decile, p->post_scale, p->post_shift) < -7.9f)
{
// histogram overflows controls in lowlights : display warning
cairo_save(g->cr);
@@ -3940,11 +3852,11 @@ static gboolean area_draw(GtkWidget *widget,
}
dt_iop_gui_enter_critical_section(self);
- init_nodes_x(g);
+ _init_nodes_x(g);
dt_iop_gui_leave_critical_section(self);
dt_iop_gui_enter_critical_section(self);
- init_nodes_y(g);
+ _init_nodes_y(g);
dt_iop_gui_leave_critical_section(self);
if(g->user_param_valid)
@@ -4020,82 +3932,12 @@ static gboolean area_draw(GtkWidget *widget,
cairo_set_source_surface(cr, g->cst, 0, 0);
cairo_paint(cr);
- return TRUE;
+ return FALSE; // Draw the handle next
}
-
-// static gboolean _toneequalizer_bar_draw(GtkWidget *widget,
-// cairo_t *crf,
-// dt_iop_module_t *self)
-// {
-// // Draw the widget equalizer view
-// dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
-
-// update_gui_histogram(self);
-
-// GtkAllocation allocation;
-// gtk_widget_get_allocation(widget, &allocation);
-// cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
-// allocation.width, allocation.height);
-// cairo_t *cr = cairo_create(cst);
-
-// // draw background
-// set_color(cr, darktable.bauhaus->graph_bg);
-// cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
-// cairo_fill_preserve(cr);
-// cairo_clip(cr);
-
-// dt_iop_gui_enter_critical_section(self);
-
-// if(g->gui_histogram_valid)
-// {
-// // draw histogram span
-// const float left = (g->histogram_first_decile + 8.0f) / 8.0f;
-// const float right = (g->histogram_last_decile + 8.0f) / 8.0f;
-// const float width = (right - left);
-// set_color(cr, darktable.bauhaus->inset_histogram);
-// cairo_rectangle(cr, left * allocation.width, 0,
-// width * allocation.width, allocation.height);
-// cairo_fill(cr);
-
-// // draw average bar
-// set_color(cr, darktable.bauhaus->graph_fg);
-// cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(3));
-// const float average = ((g->histogram_first_decile + g->histogram_last_decile) / 2.0f + 8.0f) / 8.0f;
-// cairo_move_to(cr, average * allocation.width, 0.0);
-// cairo_line_to(cr, average * allocation.width, allocation.height);
-// cairo_stroke(cr);
-
-// // draw clipping bars
-// cairo_set_source_rgb(cr, 0.75, 0.50, 0);
-// cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(6));
-// if(g->histogram_first_decile < -7.9f)
-// {
-// cairo_move_to(cr, DT_PIXEL_APPLY_DPI(3), 0.0);
-// cairo_line_to(cr, DT_PIXEL_APPLY_DPI(3), allocation.height);
-// cairo_stroke(cr);
-// }
-// if(g->histogram_last_decile > - 0.1f)
-// {
-// cairo_move_to(cr, allocation.width - DT_PIXEL_APPLY_DPI(3), 0.0);
-// cairo_line_to(cr, allocation.width - DT_PIXEL_APPLY_DPI(3), allocation.height);
-// cairo_stroke(cr);
-// }
-// }
-
-// dt_iop_gui_leave_critical_section(self);
-
-// cairo_set_source_surface(crf, cst, 0, 0);
-// cairo_paint(crf);
-// cairo_destroy(cr);
-// cairo_surface_destroy(cst);
-// return TRUE;
-// }
-
-
-static gboolean area_enter_leave_notify(GtkWidget *widget,
- GdkEventCrossing *event,
- dt_iop_module_t *self)
+static gboolean _area_enter_leave_notify(GtkWidget *widget,
+ GdkEventCrossing *event,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return TRUE;
if(!self->enabled) return FALSE;
@@ -4126,12 +3968,10 @@ static gboolean area_enter_leave_notify(GtkWidget *widget,
return FALSE;
}
-
-static gboolean area_button_press(GtkWidget *widget,
- GdkEventButton *event,
- dt_iop_module_t *self)
+static gboolean _area_button_press(GtkWidget *widget,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
{
-
if(darktable.gui->reset) return TRUE;
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -4173,7 +4013,7 @@ static gboolean area_button_press(GtkWidget *widget,
{
dt_dev_add_history_item(darktable.develop, self, TRUE);
}
- return TRUE;
+ return FALSE;
}
// Unlock the colour picker so we can display our own custom cursor
@@ -4182,10 +4022,9 @@ static gboolean area_button_press(GtkWidget *widget,
return FALSE;
}
-
-static gboolean area_motion_notify(GtkWidget *widget,
- GdkEventMotion *event,
- dt_iop_module_t *self)
+static gboolean _area_motion_notify(GtkWidget *widget,
+ GdkEventMotion *event,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return TRUE;
if(!self->enabled) return FALSE;
@@ -4202,7 +4041,7 @@ static gboolean area_motion_notify(GtkWidget *widget,
const float cursor_exposure = g->area_x / g->graph_width * 8.0f - 8.0f;
// Get the desired correction on exposure NUM_SLIDERS
- g->area_dragging = set_new_params_interactive(cursor_exposure, offset,
+ g->area_dragging = _set_new_params_interactive(cursor_exposure, offset,
g->sigma * g->sigma / 2.0f, g, p);
dt_iop_gui_leave_critical_section(self);
}
@@ -4233,13 +4072,12 @@ static gboolean area_motion_notify(GtkWidget *widget,
dt_iop_gui_leave_critical_section(self);
gtk_widget_queue_draw(GTK_WIDGET(g->area));
- return TRUE;
+ return FALSE;
}
-
-static gboolean area_button_release(GtkWidget *widget,
- GdkEventButton *event,
- dt_iop_module_t *self)
+static gboolean _area_button_release(GtkWidget *widget,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return TRUE;
if(!self->enabled) return FALSE;
@@ -4264,24 +4102,23 @@ static gboolean area_button_release(GtkWidget *widget,
g->area_dragging = FALSE;
dt_iop_gui_leave_critical_section(self);
- return TRUE;
+ return FALSE;
}
}
return FALSE;
}
-
-static gboolean area_scroll(GtkWidget *widget,
- GdkEventScroll *event,
- gpointer user_data)
+static gboolean _area_scroll(GtkWidget *widget,
+ GdkEventScroll *event,
+ gpointer user_data)
{
// do not propagate to tab bar unless scrolling sidebar
return !dt_gui_ignore_scroll(event);
}
-static gboolean notebook_button_press(GtkWidget *widget,
- GdkEventButton *event,
- dt_iop_module_t *self)
+static gboolean _notebook_button_press(GtkWidget *widget,
+ GdkEventButton *event,
+ dt_iop_module_t *self)
{
if(darktable.gui->reset) return TRUE;
@@ -4294,7 +4131,6 @@ static gboolean notebook_button_press(GtkWidget *widget,
return FALSE;
}
-
GSList *mouse_actions(dt_iop_module_t *self)
{
GSList *lm = NULL;
@@ -4310,17 +4146,18 @@ GSList *mouse_actions(dt_iop_module_t *self)
return lm;
}
-
/**
* Post pipe events
**/
static void _develop_ui_pipe_started_callback(gpointer instance,
dt_iop_module_t *self)
{
+#ifdef MF_DEBUG
printf("ui pipe started callback\n");
+#endif
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
- switch_cursors(self);
+ _switch_cursors(self);
if(!self->expanded || !self->enabled)
{
@@ -4338,11 +4175,12 @@ static void _develop_ui_pipe_started_callback(gpointer instance,
--darktable.gui->reset;
}
-
static void _develop_preview_pipe_finished_callback(gpointer instance,
dt_iop_module_t *self)
{
+#ifdef MF_DEBUG
printf("preview pipe finished callback\n");
+#endif
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
@@ -4352,23 +4190,23 @@ static void _develop_preview_pipe_finished_callback(gpointer instance,
// reprocess of the preview has been scheduled.
_set_distort_signal(self);
- switch_cursors(self);
+ _switch_cursors(self);
gtk_widget_queue_draw(GTK_WIDGET(g->area));
// gtk_widget_queue_draw(GTK_WIDGET(g->bar));
}
-
static void _develop_ui_pipe_finished_callback(gpointer instance,
dt_iop_module_t *self)
{
+#ifdef MF_DEBUG
printf("ui pipe finished callback\n");
+#endif
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
if(g == NULL) return;
- switch_cursors(self);
+ _switch_cursors(self);
}
-
void gui_reset(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
@@ -4382,12 +4220,11 @@ void gui_reset(dt_iop_module_t *self)
gtk_widget_queue_draw(GTK_WIDGET(g->area));
}
-
void gui_init(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = IOP_GUI_ALLOC(toneequalizer);
- gui_cache_init(self);
+ _gui_cache_init(self);
// g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
@@ -4406,19 +4243,19 @@ void gui_init(dt_iop_module_t *self)
| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
| GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
gtk_widget_set_can_focus(GTK_WIDGET(g->area), TRUE);
- g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(area_draw), self);
+ g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(_area_draw), self);
g_signal_connect(G_OBJECT(g->area), "button-press-event",
- G_CALLBACK(area_button_press), self);
+ G_CALLBACK(_area_button_press), self);
g_signal_connect(G_OBJECT(g->area), "button-release-event",
- G_CALLBACK(area_button_release), self);
+ G_CALLBACK(_area_button_release), self);
g_signal_connect(G_OBJECT(g->area), "leave-notify-event",
- G_CALLBACK(area_enter_leave_notify), self);
+ G_CALLBACK(_area_enter_leave_notify), self);
g_signal_connect(G_OBJECT(g->area), "enter-notify-event",
- G_CALLBACK(area_enter_leave_notify), self);
+ G_CALLBACK(_area_enter_leave_notify), self);
g_signal_connect(G_OBJECT(g->area), "motion-notify-event",
- G_CALLBACK(area_motion_notify), self);
+ G_CALLBACK(_area_motion_notify), self);
g_signal_connect(G_OBJECT(g->area), "scroll-event",
- G_CALLBACK(area_scroll), self);
+ G_CALLBACK(_area_scroll), self);
gtk_widget_set_tooltip_text(GTK_WIDGET(g->area), _("double-click to reset the curve"));
static dt_action_def_t notebook_def = { };
@@ -4455,7 +4292,7 @@ void gui_init(dt_iop_module_t *self)
"negative values will avoid oscillations and behave more robustly\n"
"but may produce brutal tone transitions and damage local contrast."));
gtk_box_pack_start(GTK_BOX(self->widget), g->smoothing, FALSE, FALSE, 0);
- g_signal_connect(G_OBJECT(g->smoothing), "value-changed", G_CALLBACK(smoothing_callback), self);
+ g_signal_connect(G_OBJECT(g->smoothing), "value-changed", G_CALLBACK(_smoothing_callback), self);
// sliders section (former "simple" page)
dt_gui_new_collapsible_section(&g->sliders_section, "plugins/darkroom/toneequal/expand_sliders", _("sliders"),
@@ -4511,7 +4348,6 @@ void gui_init(dt_iop_module_t *self)
dt_bauhaus_widget_set_label(g->whites, N_("simple"), N_("-1 EV"));
dt_bauhaus_widget_set_label(g->speculars, N_("simple"), N_("+0 EV"));
-
// Masking options
self->widget = dt_ui_notebook_page(g->notebook, N_("masking"), NULL);
GtkWidget *masking_page = self->widget;
@@ -4578,18 +4414,6 @@ void gui_init(dt_iop_module_t *self)
self->widget = GTK_WIDGET(g->advanced_masking_section.container);
- // g->bar = GTK_DRAWING_AREA(gtk_drawing_area_new());
- // gtk_widget_set_size_request(GTK_WIDGET(g->bar), -1, 40);
- // gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->bar), TRUE, TRUE, 0);
- // gtk_widget_set_can_focus(GTK_WIDGET(g->bar), TRUE);
- // g_signal_connect(G_OBJECT(g->bar), "draw",
- // G_CALLBACK(_toneequalizer_bar_draw), self);
- // gtk_widget_set_tooltip_text
- // (GTK_WIDGET(g->bar),
- // _("mask histogram span between the first and last deciles.\n"
- // "the central line shows the average. orange bars appear at extrema"
- // " if clipping occurs."));
-
g->quantization = dt_bauhaus_slider_from_params(self, "quantization");
dt_bauhaus_slider_set_format(g->quantization, _(" EV"));
gtk_widget_set_tooltip_text
@@ -4605,7 +4429,7 @@ void gui_init(dt_iop_module_t *self)
(g->exposure_boost,
_("use this to slide the mask average exposure along NUM_SLIDERS\n"
"for a better control of the exposure correction with the available nodes."));
- dt_bauhaus_widget_set_quad(g->exposure_boost, self, dtgtk_cairo_paint_wand, FALSE, auto_adjust_exposure_boost,
+ dt_bauhaus_widget_set_quad(g->exposure_boost, self, dtgtk_cairo_paint_wand, FALSE, _auto_adjust_exposure_boost,
_("auto-adjust the average exposure"));
g->contrast_boost = dt_bauhaus_slider_from_params(self, "contrast_boost");
@@ -4617,7 +4441,7 @@ void gui_init(dt_iop_module_t *self)
"and dilate the mask contrast around -4EV\n"
"this allows to spread the exposure histogram over more NUM_SLIDERS\n"
"for a better control of the exposure correction."));
- dt_bauhaus_widget_set_quad(g->contrast_boost, self, dtgtk_cairo_paint_wand, FALSE, auto_adjust_contrast_boost,
+ dt_bauhaus_widget_set_quad(g->contrast_boost, self, dtgtk_cairo_paint_wand, FALSE, _auto_adjust_contrast_boost,
_("auto-adjust the contrast"));
@@ -4626,7 +4450,7 @@ void gui_init(dt_iop_module_t *self)
dt_ui_label_new(_("show image histogram in graph")), TRUE, TRUE, 0);
g->show_two_histograms = dt_iop_togglebutton_new
(self, NULL,
- N_("display the image histogram together with mask histogram"), NULL, G_CALLBACK(show_two_histograms_callback),
+ N_("display the image histogram together with mask histogram"), NULL, G_CALLBACK(_show_two_histograms_callback),
FALSE, 0, 0, dtgtk_cairo_paint_showmask, histo_box);
dt_gui_add_class(g->show_two_histograms, "dt_transparent_background");
dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->show_two_histograms),
@@ -4645,7 +4469,7 @@ void gui_init(dt_iop_module_t *self)
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(wrapper), TRUE, TRUE, 0);
g_signal_connect(G_OBJECT(g->notebook), "button-press-event",
- G_CALLBACK(notebook_button_press), self);
+ G_CALLBACK(_notebook_button_press), self);
gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(g->notebook), FALSE, FALSE, 0);
GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
@@ -4653,7 +4477,7 @@ void gui_init(dt_iop_module_t *self)
dt_ui_label_new(_("display exposure mask")), TRUE, TRUE, 0);
g->show_luminance_mask = dt_iop_togglebutton_new
(self, NULL,
- N_("display exposure mask"), NULL, G_CALLBACK(show_luminance_mask_callback),
+ N_("display exposure mask"), NULL, G_CALLBACK(_show_luminance_mask_callback),
FALSE, 0, 0, dtgtk_cairo_paint_showmask, hbox);
dt_gui_add_class(g->show_luminance_mask, "dt_transparent_background");
dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->show_luminance_mask),
@@ -4667,7 +4491,6 @@ void gui_init(dt_iop_module_t *self)
DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_HISTORY_CHANGE, _develop_ui_pipe_started_callback);
}
-
void gui_cleanup(dt_iop_module_t *self)
{
dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
From 1b4b7f4dfa4bc424d27c2e0bb1bdf34db69f7bf3 Mon Sep 17 00:00:00 2001
From: Marc Fouquet
Date: Sun, 8 Jun 2025 11:45:14 +0200
Subject: [PATCH 5/6] UI rework
---
src/iop/toneequal.c | 312 +++++++++++++++++++++++++++++---------------
1 file changed, 206 insertions(+), 106 deletions(-)
diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c
index 3b9b8eea4434..d8a6e7423cb3 100644
--- a/src/iop/toneequal.c
+++ b/src/iop/toneequal.c
@@ -201,7 +201,7 @@ typedef enum dt_iop_toneequalizer_post_auto_align_t
DT_TONEEQ_ALIGN_CENTER, // $DESCRIPTION: "at mid-tones"
DT_TONEEQ_ALIGN_RIGHT, // $DESCRIPTION: "at highlights"
DT_TONEEQ_ALIGN_FIT, // $DESCRIPTION: "fully fit"
-
+ DT_TONEEQ_ALIGN_LEGACY, // $DESCRIPTION: "legacy pre-processing"
} dt_iop_toneequalizer_post_auto_align_t;
typedef struct dt_iop_toneequalizer_params_t
@@ -226,7 +226,7 @@ typedef struct dt_iop_toneequalizer_params_t
int iterations; // $MIN: 1 $MAX: 20 $DEFAULT: 1 $DESCRIPTION: "filter diffusion"
float post_scale; // $MIN: -3.0 $MAX: 3.0 $DEFAULT: 0.0 $DESCRIPTION: "mask contrast / scale histogram"
float post_shift; // $MIN: -4.0 $MAX: 4.0 $DEFAULT: 0.0 $DESCRIPTION: "mask brightness / shift histogram"
- dt_iop_toneequalizer_post_auto_align_t post_auto_align; // $DEFAULT: DT_TONEEQ_ALIGN_FIT $DESCRIPTION: "auto align mask exposure"
+ dt_iop_toneequalizer_post_auto_align_t post_auto_align; // $DEFAULT: DT_TONEEQ_ALIGN_FIT $DESCRIPTION: "align mask exposure"
} dt_iop_toneequalizer_params_t;
@@ -317,9 +317,11 @@ typedef struct dt_iop_toneequalizer_gui_data_t
GtkWidget *lum_estimator;
GtkWidget *filter, *feathering, *contrast_boost, *iterations, *exposure_boost, *post_scale, *post_shift;
GtkNotebook *notebook;
- dt_gui_collapsible_section_t sliders_section, guided_filter_section, advanced_masking_section;
+ dt_gui_collapsible_section_t sliders_section;
+ GtkWidget *pre_processing_label, *post_processing_label, *advanced_label;
+
GtkWidget *show_luminance_mask;
- GtkWidget *show_two_histograms;
+ // GtkWidget *show_two_histograms;
// Cache Pango and Cairo stuff for the equalizer drawing
float line_height;
@@ -542,7 +544,7 @@ int legacy_params(dt_iop_module_t *self,
// V3 params
n->post_scale = 0.0f;
n->post_shift = 0.0f;
- n->post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
+ n->post_auto_align = DT_TONEEQ_ALIGN_LEGACY;
*new_params = n;
*new_params_size = sizeof(dt_iop_toneequalizer_params_v3_t);
@@ -628,7 +630,7 @@ void init_presets(dt_iop_module_so_t *self)
p.quantization = 0.0f;
p.post_scale = 0.0f;
p.post_shift = 0.0f;
- p.post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
+ p.post_auto_align = DT_TONEEQ_ALIGN_LEGACY;
// Init exposure settings
p.noise = p.ultra_deep_blacks = p.deep_blacks = p.blacks = 0.0f;
@@ -741,7 +743,7 @@ void init_presets(dt_iop_module_so_t *self)
// build the 1D contrast curves that revert the local compression of
// contrast above
- p.post_auto_align = DT_TONEEQ_ALIGN_CUSTOM;
+ p.post_auto_align = DT_TONEEQ_ALIGN_LEGACY;
p.filter = DT_TONEEQ_NONE;
_dilate_shadows_highlight_preset_set_exposure_params(&p, 0.25f);
dt_gui_presets_add_generic
@@ -977,8 +979,7 @@ void compute_auto_post_scale_shift(dt_iop_toneequalizer_post_auto_align_t post_a
float histogram_first_decile,
float histogram_last_decile,
float *post_scale, float *post_shift,
- dt_dev_pixelpipe_type_t const debug_pipe
- )
+ dt_dev_pixelpipe_type_t const debug_pipe)
{
const float first_decile_target = -7.0f;
const float last_decile_target = -1.0f;
@@ -992,6 +993,7 @@ void compute_auto_post_scale_shift(dt_iop_toneequalizer_post_auto_align_t post_a
switch(post_auto_align)
{
case(DT_TONEEQ_ALIGN_CUSTOM):
+ case(DT_TONEEQ_ALIGN_LEGACY):
{
// fully user-controlled, do not modify
break;
@@ -1617,6 +1619,8 @@ void _toneeq_process(dt_iop_module_t *self,
&post_scale, &post_shift,
piece->pipe->type);
+ printf("_toneeq_process / DT_DEV_PIXELPIPE_FULL: post_scale %f, post_shift %f\n", post_scale, post_shift);
+
dt_iop_gui_enter_critical_section(self);
g->post_scale_value = post_scale;
g->post_shift_value = post_shift;
@@ -1669,7 +1673,9 @@ void _toneeq_process(dt_iop_module_t *self,
g->full_luminance_valid = TRUE;
}
- if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM) {
+ if ((d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM)
+ && (d->post_auto_align != DT_TONEEQ_ALIGN_LEGACY))
+ {
// Calculate post scale/shift using previously saved deciles of the full mask
dt_iop_gui_enter_critical_section(self);
@@ -1704,15 +1710,20 @@ void _toneeq_process(dt_iop_module_t *self,
float histogram_first_decile = 0;
float histogram_last_decile = 0;
- if (d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM) {
+ if ((d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM)
+ && (d->post_auto_align != DT_TONEEQ_ALIGN_LEGACY))
+ {
_compute_mask_stats_from_full_image(piece, in, roi_in, d,
- &histogram_first_decile, &histogram_last_decile);
+ &histogram_first_decile, &histogram_last_decile);
+ compute_auto_post_scale_shift(d->post_auto_align,
+ histogram_first_decile, histogram_last_decile,
+ &post_scale, &post_shift,
+ piece->pipe->type);
+ if (piece->pipe->type & DT_DEV_PIXELPIPE_EXPORT)
+ {
+ printf("_toneeq_process / DT_DEV_PIXELPIPE_EXPORT: post_scale %f, post_shift %f\n", post_scale, post_shift);
+ }
}
- compute_auto_post_scale_shift(d->post_auto_align,
- histogram_first_decile, histogram_last_decile,
- &post_scale, &post_shift,
- piece->pipe->type);
-
#ifdef MF_DEBUG
printf("_toneeq_process NO GUI PIXELPIPE (%d): roi with=%d roi_height=%d post_align=%d d->post_scale=%f "
"d->post_shift=%f final post_scale=%f final post_shift=%f\n",
@@ -1850,7 +1861,8 @@ void modify_roi_in(dt_iop_module_t *self,
// Auto-align is on and either has the upstream image changed or the user
// modified some option that requires re-calculation of the mask
- if(d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM
+ if((d->post_auto_align != DT_TONEEQ_ALIGN_CUSTOM)
+ && (d->post_auto_align != DT_TONEEQ_ALIGN_LEGACY)
&& (current_upstream_hash != saved_upstream_hash || !full_luminance_valid))
{
// We need the full image as our roi
@@ -1860,9 +1872,7 @@ void modify_roi_in(dt_iop_module_t *self,
roi_in->height = piece->buf_in.height;
roi_in->scale = 1.0f;
-#ifdef MF_DEBUG
- printf("modify_roi_in: full image for auto-alignment\n");
-#endif
+ printf("modify_roi_in/DT_DEV_PIXELPIPE_FULL: requesting full image for auto-alignment\n");
}
}
}
@@ -2330,11 +2340,22 @@ static inline void _update_gui_histogram(dt_iop_module_t *const self)
g->image_EV_per_UI_sample = (mask_EV_of_target * mask_to_image * target_to_full) / (float)UI_HISTO_SAMPLES;
- if (g->show_two_histograms)
- _compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->image_max_histogram);
+ // if (g->show_two_histograms)
+ // _compute_gui_histogram(g->image_hires_histogram, g->image_histogram, p->post_scale, p->post_shift, &g->image_max_histogram);
g->gui_histogram_valid = TRUE;
}
+ // else
+ // { // TODO MF: REMOVE LATER, THIS IS ONLY FOR DEBUGGING
+ // if (!g->prv_luminance_valid)
+ // {
+ // printf("_update_gui_histogram: Skipping histogram drawing, PIXELPIPE_PREVIEW is not ready yet.\n");
+ // }
+ // if (!g->full_luminance_valid)
+ // {
+ // printf("_update_gui_histogram: Skipping histogram drawing, PIXELPIPE_FULL is not ready yet.\n");
+ // }
+ // }
dt_iop_gui_leave_critical_section(self);
}
@@ -2380,24 +2401,54 @@ static void _show_guiding_controls(dt_iop_module_t *self)
switch(p->post_auto_align)
{
+ case(DT_TONEEQ_ALIGN_CUSTOM):
+ {
+ gtk_widget_set_visible(g->pre_processing_label, FALSE);
+ gtk_widget_set_visible(g->exposure_boost, FALSE);
+ gtk_widget_set_visible(g->contrast_boost, FALSE);
+
+ gtk_widget_set_visible(g->post_processing_label, TRUE);
+ gtk_widget_set_visible(g->post_scale, TRUE);
+ gtk_widget_set_visible(g->post_shift, TRUE);
+
+ break;
+ }
case(DT_TONEEQ_ALIGN_LEFT):
case(DT_TONEEQ_ALIGN_CENTER):
case(DT_TONEEQ_ALIGN_RIGHT):
{
+ gtk_widget_set_visible(g->pre_processing_label, FALSE);
+ gtk_widget_set_visible(g->exposure_boost, FALSE);
+ gtk_widget_set_visible(g->contrast_boost, FALSE);
+
+ gtk_widget_set_visible(g->post_processing_label, TRUE);
gtk_widget_set_visible(g->post_scale, TRUE);
gtk_widget_set_visible(g->post_shift, FALSE);
+
break;
}
case(DT_TONEEQ_ALIGN_FIT):
{
+ gtk_widget_set_visible(g->pre_processing_label, FALSE);
+ gtk_widget_set_visible(g->exposure_boost, FALSE);
+ gtk_widget_set_visible(g->contrast_boost, FALSE);
+
+ gtk_widget_set_visible(g->post_processing_label, FALSE);
gtk_widget_set_visible(g->post_scale, FALSE);
gtk_widget_set_visible(g->post_shift, FALSE);
+
break;
}
- case(DT_TONEEQ_ALIGN_CUSTOM):
+ case(DT_TONEEQ_ALIGN_LEGACY):
{
- gtk_widget_set_visible(g->post_scale, TRUE);
- gtk_widget_set_visible(g->post_shift, TRUE);
+ gtk_widget_set_visible(g->pre_processing_label, TRUE);
+ gtk_widget_set_visible(g->exposure_boost, TRUE);
+ gtk_widget_set_visible(g->contrast_boost, TRUE);
+
+ gtk_widget_set_visible(g->post_processing_label, FALSE);
+ gtk_widget_set_visible(g->post_scale, FALSE);
+ gtk_widget_set_visible(g->post_shift, FALSE);
+
break;
}
}
@@ -2424,7 +2475,7 @@ void gui_update(dt_iop_module_t *self)
_invalidate_luminance_cache(self);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_luminance_mask), g->mask_display);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
+ // gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
}
@@ -2464,17 +2515,52 @@ void gui_changed(dt_iop_module_t *self,
}
else if (w == g->post_auto_align)
{
- // We may have switched from a more automatic to a less automatic mode.
- // Copy the automatically determined parameters to the GUI sliders.
- p->post_scale = g->post_scale_value;
- p->post_shift = g->post_shift_value;
+ // TODO MF:
+ // For now reset everything whenever the mode is changed.
+ // It may be nice to preserve some values, i.e. when switching
+ // form fully fit to custom. But there are corner-cases,
+ // i.e, regarding presets that make this complocated.
+ p->post_scale = 0.0f;
+ p->post_shift = 0.0f;
++darktable.gui->reset;
dt_bauhaus_slider_set(g->post_scale, p->post_scale);
dt_bauhaus_slider_set(g->post_shift, p->post_shift);
--darktable.gui->reset;
+ g->post_scale_value = 0.0f;
+ g->post_shift_value = 0.0f;
+
+ p->contrast_boost = 0.0f;
+ p->exposure_boost = 0.0f;
+
+ ++darktable.gui->reset;
+ dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
+ dt_bauhaus_slider_set(g->exposure_boost, p->exposure_boost);
+ --darktable.gui->reset;
+
+ // else
+ // {
+ // p->contrast_boost = 0.0f;
+ // p->exposure_boost = 0.0f;
+
+ // ++darktable.gui->reset;
+ // dt_bauhaus_slider_set(g->contrast_boost, p->contrast_boost);
+ // dt_bauhaus_slider_set(g->exposure_boost, p->exposure_boost);
+ // --darktable.gui->reset;
+
+ // // // We may have switched from a more automatic to a less automatic mode.
+ // // // Copy the automatically determined parameters to the GUI sliders.
+ // // p->post_scale = g->post_scale_value;
+ // // p->post_shift = g->post_shift_value;
+
+ // ++darktable.gui->reset;
+ // dt_bauhaus_slider_set(g->post_scale, p->post_scale);
+ // dt_bauhaus_slider_set(g->post_shift, p->post_shift);
+ // --darktable.gui->reset;
+ // }
+
_invalidate_luminance_cache(self);
_show_guiding_controls(self);
}
@@ -2680,24 +2766,24 @@ static void _show_luminance_mask_callback(GtkWidget *togglebutton,
}
// TODO MF: Remove this again? Two histograms are only useful for debugging.
-static void _show_two_histograms_callback(GtkWidget *togglebutton,
- GdkEventButton *event,
- dt_iop_module_t *self)
-{
- if(darktable.gui->reset) return;
- dt_iop_request_focus(self);
+// static void _show_two_histograms_callback(GtkWidget *togglebutton,
+// GdkEventButton *event,
+// dt_iop_module_t *self)
+// {
+// if(darktable.gui->reset) return;
+// dt_iop_request_focus(self);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
+// gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), TRUE);
- dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
- g->two_histograms_display = !g->two_histograms_display;
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
+// dt_iop_toneequalizer_gui_data_t *g = self->gui_data;
+// g->two_histograms_display = !g->two_histograms_display;
+// gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->show_two_histograms), g->two_histograms_display);
- // dt_dev_reprocess_center(self->dev);
- dt_iop_refresh_center(self); // TODO: is this needed?
- // Unlock the colour picker so we can display our own custom cursor
- // dt_iop_color_picker_reset(self, TRUE);
-}
+// // dt_dev_reprocess_center(self->dev);
+// dt_iop_refresh_center(self); // TODO: is this needed?
+// // Unlock the colour picker so we can display our own custom cursor
+// // dt_iop_color_picker_reset(self, TRUE);
+// }
/****************************************************************************
@@ -4269,11 +4355,37 @@ void gui_init(dt_iop_module_t *self)
g->post_auto_align = dt_bauhaus_combobox_from_params(self, "post_auto_align");
gtk_widget_set_tooltip_text(g->post_auto_align, _("automatically set the mask exposure/contrast"));
- g->post_scale = dt_bauhaus_slider_from_params(self, "post_scale");
- dt_bauhaus_slider_set_soft_range(g->post_scale, -2.0, 2.0);
+ g->pre_processing_label = dt_ui_section_label_new(C_("section", "mask pre-processing"));
+ gtk_box_pack_start(GTK_BOX(self->widget),
+ g->pre_processing_label,
+ FALSE, FALSE, 0);
+
+ g->exposure_boost = dt_bauhaus_slider_from_params(self, "exposure_boost");
+ dt_bauhaus_slider_set_soft_range(g->exposure_boost, -4.0, 4.0);
+ dt_bauhaus_slider_set_format(g->exposure_boost, _(" EV"));
gtk_widget_set_tooltip_text
- (g->post_scale,
- _("set the mask contrast / scale the histogram"));
+ (g->exposure_boost,
+ _("use this to slide the mask average exposure along sliders\n"
+ "for a better control of the exposure correction with the available nodes."));
+ dt_bauhaus_widget_set_quad(g->exposure_boost, self, dtgtk_cairo_paint_wand, FALSE, _auto_adjust_exposure_boost,
+ _("auto-adjust the average exposure"));
+
+ g->contrast_boost = dt_bauhaus_slider_from_params(self, "contrast_boost");
+ dt_bauhaus_slider_set_soft_range(g->contrast_boost, -2.0, 2.0);
+ dt_bauhaus_slider_set_format(g->contrast_boost, _(" EV"));
+ gtk_widget_set_tooltip_text
+ (g->contrast_boost,
+ _("use this to counter the averaging effect of the guided filter\n"
+ "and dilate the mask contrast around -4EV\n"
+ "this allows to spread the exposure histogram over more sliders\n"
+ "for a better control of the exposure correction."));
+ dt_bauhaus_widget_set_quad(g->contrast_boost, self, dtgtk_cairo_paint_wand, FALSE, _auto_adjust_contrast_boost,
+ _("auto-adjust the contrast"));
+
+ g->post_processing_label = dt_ui_section_label_new(C_("section", "mask post-processing"));
+ gtk_box_pack_start(GTK_BOX(self->widget),
+ g->post_processing_label,
+ FALSE, FALSE, 0);
g->post_shift = dt_bauhaus_slider_from_params(self, "post_shift");
dt_bauhaus_slider_set_soft_range(g->post_shift, -4.0, 4.0);
@@ -4281,6 +4393,12 @@ void gui_init(dt_iop_module_t *self)
(g->post_shift,
_("set the mask exposure / shift the histogram"));
+ g->post_scale = dt_bauhaus_slider_from_params(self, "post_scale");
+ dt_bauhaus_slider_set_soft_range(g->post_scale, -2.0, 2.0);
+ gtk_widget_set_tooltip_text
+ (g->post_scale,
+ _("set the mask contrast / scale the histogram"));
+
self->widget = dt_ui_notebook_page(g->notebook, N_("curve"), NULL);
g->smoothing = dt_bauhaus_slider_new_with_range(self, -2.33f, +1.67f, 0, 0.0f, 2);
@@ -4350,20 +4468,20 @@ void gui_init(dt_iop_module_t *self)
// Masking options
self->widget = dt_ui_notebook_page(g->notebook, N_("masking"), NULL);
- GtkWidget *masking_page = self->widget;
+ // GtkWidget *masking_page = self->widget;
// guided filter section
- dt_gui_new_collapsible_section(&g->guided_filter_section, "plugins/darkroom/toneequal/expand_sliders",
- _("guided filter"), GTK_BOX(masking_page), DT_ACTION(self));
- gtk_widget_set_tooltip_text(g->guided_filter_section.expander, _("guided filter"));
-
+ // dt_gui_new_collapsible_section(&g->guided_filter_section, "plugins/darkroom/toneequal/expand_sliders",
+ // _("guided filter"), GTK_BOX(masking_page), DT_ACTION(self));
+ // gtk_widget_set_tooltip_text(g->guided_filter_section.expander, _("guided filter"));
+ //
// Hack to make the collapsible section align at the top
- g_object_ref(g->guided_filter_section.expander);
- gtk_container_remove(GTK_CONTAINER(masking_page), g->guided_filter_section.expander);
- gtk_box_pack_start(GTK_BOX(masking_page), g->guided_filter_section.expander, FALSE, FALSE, 0);
- g_object_unref(g->guided_filter_section.expander);
-
- self->widget = GTK_WIDGET(g->guided_filter_section.container);
+ // g_object_ref(g->guided_filter_section.expander);
+ // gtk_container_remove(GTK_CONTAINER(masking_page), g->guided_filter_section.expander);
+ // gtk_box_pack_start(GTK_BOX(masking_page), g->guided_filter_section.expander, FALSE, FALSE, 0);
+ // g_object_unref(g->guided_filter_section.expander);
+ //
+ // self->widget = GTK_WIDGET(g->guided_filter_section.container);
g->lum_estimator = dt_bauhaus_combobox_from_params(self, "lum_estimator");
gtk_widget_set_tooltip_text(g->lum_estimator, _("preview the mask and chose the estimator that gives you the\n"
@@ -4401,18 +4519,23 @@ void gui_init(dt_iop_module_t *self)
"lower values give smoother gradients and better smoothing\n"
"but may lead to inaccurate edges taping and halos"));
- // Collapsible section mask pre-processing
- dt_gui_new_collapsible_section(&g->advanced_masking_section, "plugins/darkroom/toneequal/expand_advanced_masking",
- _("mask pre-processing"), GTK_BOX(masking_page), DT_ACTION(self));
- gtk_widget_set_tooltip_text(g->advanced_masking_section.expander, _("mask pre-processing"));
+ // // Collapsible section mask pre-processing
+ // dt_gui_new_collapsible_section(&g->advanced_masking_section, "plugins/darkroom/toneequal/expand_advanced_masking",
+ // _("mask pre-processing"), GTK_BOX(masking_page), DT_ACTION(self));
+ // gtk_widget_set_tooltip_text(g->advanced_masking_section.expander, _("mask pre-processing"));
- // Hack to make the collapsible section align at the top
- g_object_ref(g->advanced_masking_section.expander);
- gtk_container_remove(GTK_CONTAINER(masking_page), g->advanced_masking_section.expander);
- gtk_box_pack_start(GTK_BOX(masking_page), g->advanced_masking_section.expander, FALSE, FALSE, 0);
- g_object_unref(g->advanced_masking_section.expander);
+ // // Hack to make the collapsible section align at the top
+ // g_object_ref(g->advanced_masking_section.expander);
+ // gtk_container_remove(GTK_CONTAINER(masking_page), g->advanced_masking_section.expander);
+ // gtk_box_pack_start(GTK_BOX(masking_page), g->advanced_masking_section.expander, FALSE, FALSE, 0);
+ // g_object_unref(g->advanced_masking_section.expander);
+
+ // self->widget = GTK_WIDGET(g->advanced_masking_section.container);
- self->widget = GTK_WIDGET(g->advanced_masking_section.container);
+ g->advanced_label = dt_ui_section_label_new(C_("section", "advanced"));
+ gtk_box_pack_start(GTK_BOX(self->widget),
+ g->advanced_label,
+ FALSE, FALSE, 0);
g->quantization = dt_bauhaus_slider_from_params(self, "quantization");
dt_bauhaus_slider_set_format(g->quantization, _(" EV"));
@@ -4422,41 +4545,18 @@ void gui_init(dt_iop_module_t *self)
"higher values posterize the luminance mask to help the guiding\n"
"produce piece-wise smooth areas when using high feathering values"));
- g->exposure_boost = dt_bauhaus_slider_from_params(self, "exposure_boost");
- dt_bauhaus_slider_set_soft_range(g->exposure_boost, -4.0, 4.0);
- dt_bauhaus_slider_set_format(g->exposure_boost, _(" EV"));
- gtk_widget_set_tooltip_text
- (g->exposure_boost,
- _("use this to slide the mask average exposure along NUM_SLIDERS\n"
- "for a better control of the exposure correction with the available nodes."));
- dt_bauhaus_widget_set_quad(g->exposure_boost, self, dtgtk_cairo_paint_wand, FALSE, _auto_adjust_exposure_boost,
- _("auto-adjust the average exposure"));
-
- g->contrast_boost = dt_bauhaus_slider_from_params(self, "contrast_boost");
- dt_bauhaus_slider_set_soft_range(g->contrast_boost, -2.0, 2.0);
- dt_bauhaus_slider_set_format(g->contrast_boost, _(" EV"));
- gtk_widget_set_tooltip_text
- (g->contrast_boost,
- _("use this to counter the averaging effect of the guided filter\n"
- "and dilate the mask contrast around -4EV\n"
- "this allows to spread the exposure histogram over more NUM_SLIDERS\n"
- "for a better control of the exposure correction."));
- dt_bauhaus_widget_set_quad(g->contrast_boost, self, dtgtk_cairo_paint_wand, FALSE, _auto_adjust_contrast_boost,
- _("auto-adjust the contrast"));
-
-
- GtkWidget *histo_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_pack_start(GTK_BOX(histo_box),
- dt_ui_label_new(_("show image histogram in graph")), TRUE, TRUE, 0);
- g->show_two_histograms = dt_iop_togglebutton_new
- (self, NULL,
- N_("display the image histogram together with mask histogram"), NULL, G_CALLBACK(_show_two_histograms_callback),
- FALSE, 0, 0, dtgtk_cairo_paint_showmask, histo_box);
- dt_gui_add_class(g->show_two_histograms, "dt_transparent_background");
- dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->show_two_histograms),
- dtgtk_cairo_paint_showmask, 0, NULL);
- dt_gui_add_class(g->show_two_histograms, "dt_bauhaus_alignment");
- gtk_box_pack_start(GTK_BOX(self->widget), histo_box, FALSE, FALSE, 0);
+ // GtkWidget *histo_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ // gtk_box_pack_start(GTK_BOX(histo_box),
+ // dt_ui_label_new(_("show image histogram in graph")), TRUE, TRUE, 0);
+ // g->show_two_histograms = dt_iop_togglebutton_new
+ // (self, NULL,
+ // N_("display the image histogram together with mask histogram"), NULL, G_CALLBACK(_show_two_histograms_callback),
+ // FALSE, 0, 0, dtgtk_cairo_paint_showmask, histo_box);
+ // dt_gui_add_class(g->show_two_histograms, "dt_transparent_background");
+ // dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(g->show_two_histograms),
+ // dtgtk_cairo_paint_showmask, 0, NULL);
+ // dt_gui_add_class(g->show_two_histograms, "dt_bauhaus_alignment");
+ // gtk_box_pack_start(GTK_BOX(self->widget), histo_box, FALSE, FALSE, 0);
// start building top level widget
From eccb2d1f30fd29c7df61b7b9282885088213e9d8 Mon Sep 17 00:00:00 2001
From: Marc Fouquet
Date: Thu, 12 Jun 2025 16:44:04 +0200
Subject: [PATCH 6/6] Fix for the shadows/highlights bug.
---
src/iop/toneequal.c | 145 +++++++++++++++++++++++++++++++++++++-------
1 file changed, 124 insertions(+), 21 deletions(-)
diff --git a/src/iop/toneequal.c b/src/iop/toneequal.c
index d8a6e7423cb3..c982cd4215ef 100644
--- a/src/iop/toneequal.c
+++ b/src/iop/toneequal.c
@@ -129,6 +129,71 @@
#include // Only needed for debug printf of hashes, TODO remove
#endif
+#include
+#include
+
+#define dbg_plot_width 49
+#define dbg_plot_height 5 // y = +2, +1, 0, -1, -2
+
+
+void plot_ascii(const float y_values[49], float mark1, float mark2) {
+ const float x_start = -16.0f;
+ const float x_step = 0.5f;
+
+ // Prepare the graph: fill with spaces
+ char graph[dbg_plot_height][dbg_plot_width + 1];
+ for (int row = 0; row < dbg_plot_height; ++row) {
+ for (int col = 0; col < dbg_plot_width; ++col) {
+ // Draw dashed lines at y = +2 (row 0), y = 0 (row 2), y = -2 (row 4)
+ if (row == 0 || row == 2 || row == 4)
+ graph[row][col] = '-';
+ else
+ graph[row][col] = ' ';
+ }
+ graph[row][dbg_plot_width] = '\0';
+ }
+
+ // Overwrite with stars for the data points
+ for (int col = 0; col < dbg_plot_width; ++col) {
+ float y = y_values[col];
+ // Map y in [-2,2] to row index: +2->0, 0->2, -2->4
+ int row = (int)round((2.0f - y) * (dbg_plot_height) / 5.0f);
+ if (row < 0) row = 0;
+ if (row >= dbg_plot_height) row = dbg_plot_height;
+ graph[row][col] = '*';
+ }
+
+ // Prepare the x-axis marker row
+ char x_axis[dbg_plot_width + 1];
+ for (int col = 0; col < dbg_plot_width; ++col) {
+ float x = x_start + col * x_step;
+ if (fabsf(x - mark1) < x_step / 2 || fabsf(x - mark2) < x_step / 2) {
+ x_axis[col] = '^';
+ } else if (fmodf(x + 16, 2.0f) < 0.01f) {
+ x_axis[col] = '|';
+ } else {
+ x_axis[col] = ' ';
+ }
+ }
+ x_axis[dbg_plot_width] = '\0';
+
+ // Print the top border and +2 label
+
+ // Print each row of the graph with labels
+ for (int row = 0; row < dbg_plot_height; ++row) {
+ printf("%s", graph[row]);
+ if (row == 0) printf(" +2\n");
+ else if (row == 2) printf(" 0\n");
+ else if (row == 4) printf(" -2\n");
+ else printf("\n");
+ }
+ // Print the x-axis markers
+ printf("%s\n", x_axis);
+ // Print the x-axis labels
+ printf("-16 -14 -12 -10 -8 -6 -4 -2 0 2 4 6 8\n");
+ printf("\n");
+}
+
DT_MODULE_INTROSPECTION(3, dt_iop_toneequalizer_params_t)
/****************************************************************************
@@ -145,8 +210,11 @@ DT_MODULE_INTROSPECTION(3, dt_iop_toneequalizer_params_t)
#define DT_TONEEQ_MIN_EV (-8.0f)
#define DT_TONEEQ_MAX_EV (0.0f)
-#define HIRES_HISTO_MIN_EV -16.0f
-#define HIRES_HISTO_MAX_EV 8.0f
+// We need more space for the histogram and also for the LUT
+// This needs to comprise the possible scaled/shifted range of
+// the original 8EV.
+#define HIRES_MIN_EV -16.0f
+#define HIRES_MAX_EV 8.0f
/**
* Build the exposures octaves :
@@ -155,6 +223,7 @@ DT_MODULE_INTROSPECTION(3, dt_iop_toneequalizer_params_t)
#define NUM_SLIDERS 9
#define NUM_OCTAVES 8
+#define LUT_OCTAVES 24
#define LUT_RESOLUTION 10000
// radial distances used for pixel ops
@@ -233,7 +302,7 @@ typedef struct dt_iop_toneequalizer_params_t
typedef struct dt_iop_toneequalizer_data_t
{
float factors[NUM_OCTAVES] DT_ALIGNED_ARRAY;
- float correction_lut[NUM_OCTAVES * LUT_RESOLUTION + 1] DT_ALIGNED_ARRAY;
+ float correction_lut[LUT_OCTAVES * LUT_RESOLUTION + 1] DT_ALIGNED_ARRAY;
float blending, feathering, contrast_boost, exposure_boost, quantization, smoothing, post_scale, post_shift;
float scale;
int radius;
@@ -804,7 +873,7 @@ static inline void _compute_hires_histogram_and_stats(const float *const restric
// 8EV after, for a total of 24.
// Also the resolution is increased to compensate for the fact that the user
// can scale the histogram.
- const float temp_ev_range = HIRES_HISTO_MAX_EV - HIRES_HISTO_MIN_EV;
+ const float temp_ev_range = HIRES_MAX_EV - HIRES_MIN_EV;
// (Re)init the histogram
memset(hires_histogram, 0, sizeof(int) * HIRES_HISTO_SAMPLES);
@@ -814,7 +883,7 @@ static inline void _compute_hires_histogram_and_stats(const float *const restric
for(size_t k = 0; k < num_elem; k++)
{
const int index =
- CLAMP((int)(((log2f(luminance[k]) - HIRES_HISTO_MIN_EV) / temp_ev_range) * (float)HIRES_HISTO_SAMPLES),
+ CLAMP((int)(((log2f(luminance[k]) - HIRES_MIN_EV) / temp_ev_range) * (float)HIRES_HISTO_SAMPLES),
0, HIRES_HISTO_SAMPLES - 1);
hires_histogram[index] += 1;
}
@@ -851,8 +920,8 @@ static inline void _compute_hires_histogram_and_stats(const float *const restric
}
// Convert decile positions to exposures
- *first_decile = (temp_ev_range * ((float)first_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
- *last_decile = (temp_ev_range * ((float)last_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_HISTO_MIN_EV;
+ *first_decile = (temp_ev_range * ((float)first_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_MIN_EV;
+ *last_decile = (temp_ev_range * ((float)last_decile_pos / (float)(HIRES_HISTO_SAMPLES - 1))) + HIRES_MIN_EV;
}
__DT_CLONE_TARGETS__
@@ -972,6 +1041,15 @@ static inline float _post_scale_shift(const float v,
return (v + 4.0f) * scale_exp - 4.0f + post_shift;
}
+static inline float _inverse_post_scale_shift(const float v,
+ const float post_scale,
+ const float post_shift)
+{
+ const float scale_exp = exp2f(post_scale);
+ // signifficant range -8..0, centering around the middle
+ return ((v - post_shift + 4.0f) / scale_exp) - 4.0f;
+}
+
// This is similar to the auto-buttons for exposure/contrast boost.
// However it runs automatically in the pipe, so it does not need to be
// triggered by the user each time the upstream exposure changes.
@@ -1133,7 +1211,9 @@ static float _gaussian_func(const float radius,
static void _compute_correction_lut(float *restrict lut, const float sigma,
const float *const restrict factors,
const float post_scale, const float post_shift,
- dt_dev_pixelpipe_type_t const debug_pipe)
+ dt_dev_pixelpipe_type_t const debug_pipe,
+ const float debug_first_decile, const float debug_last_decile
+ )
{
#ifdef MF_DEBUG
// printf("_compute_correction_lut pipe=%d, post_scale=%f, post_shift=%f\n", debug_pipe, post_scale, post_shift);
@@ -1144,11 +1224,14 @@ static void _compute_correction_lut(float *restrict lut, const float sigma,
// TODO MF: Does the openmp still work here?
DT_OMP_FOR(shared(centers_ops))
- for(int j = 0; j <= LUT_RESOLUTION * NUM_OCTAVES; j++)
+ for(int j = 0; j <= LUT_RESOLUTION * LUT_OCTAVES; j++)
{
// build the correction for each pixel
// as the sum of the contribution of each luminance channelcorrection
- const float exposure_uncorrected = (float)j / (float)LUT_RESOLUTION + DT_TONEEQ_MIN_EV; // [-8...0] EV
+ const float exposure_uncorrected = (float)j / (float)LUT_RESOLUTION + HIRES_MIN_EV; // [-16...8] EV
+
+ // Transform to account for post/scale shift and clamp to the curve range of
+ // -8 to 0 afterwards
const float exposure = fast_clamp(_post_scale_shift(exposure_uncorrected, post_scale, post_shift),
DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
float result = 0.0f;
@@ -1159,6 +1242,18 @@ static void _compute_correction_lut(float *restrict lut, const float sigma,
// the user-set correction is expected in [-2;+2] EV, so is the interpolated one
lut[j] = fast_clamp(result, 0.25f, 4.0f);
}
+
+#ifdef MF_DEBUG
+ if (!isnan(debug_first_decile))
+ {
+ float values[49];
+ for(int j = 0; j <= 2 * LUT_OCTAVES; j++)
+ {
+ values[j] = log2f(lut[j * (int)(LUT_RESOLUTION/2)]);
+ }
+ plot_ascii(values, debug_first_decile, debug_last_decile);
+ }
+#endif
}
// this is the version currently used, as using a lut gives a
@@ -1189,8 +1284,8 @@ static inline void _apply_toneequalizer(const float *const restrict in,
// The radial-basis interpolation is valid in [-8; 0] EV and can quickly diverge outside.
// Note: not doing an explicit lut[index] check is safe as long we take care of proper
// DT_TONEEQ_MIN_EV and DT_TONEEQ_MAX_EV and allocated lut size LUT_RESOLUTION+1
- const float exposure = fast_clamp(log2f(luminance[k]), DT_TONEEQ_MIN_EV, DT_TONEEQ_MAX_EV);
- const float correction = lut[(unsigned)roundf((exposure - DT_TONEEQ_MIN_EV) * lutres)];
+ const float exposure = fast_clamp(log2f(luminance[k]), HIRES_MIN_EV, HIRES_MAX_EV);
+ const float correction = lut[(unsigned)roundf((exposure - HIRES_MIN_EV) * lutres)];
// apply correction
for_each_channel(c)
out[4 * k + c] = correction * in[4 * k + c];
@@ -1619,8 +1714,6 @@ void _toneeq_process(dt_iop_module_t *self,
&post_scale, &post_shift,
piece->pipe->type);
- printf("_toneeq_process / DT_DEV_PIXELPIPE_FULL: post_scale %f, post_shift %f\n", post_scale, post_shift);
-
dt_iop_gui_enter_critical_section(self);
g->post_scale_value = post_scale;
g->post_shift_value = post_shift;
@@ -1759,7 +1852,7 @@ void _toneeq_process(dt_iop_module_t *self,
#endif
_compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
post_scale, post_shift,
- piece->pipe->type);
+ piece->pipe->type, NAN, NAN);
_apply_toneequalizer(in_cropped, luminance, out,
roi_out, roi_out,
d, piece->pipe->type);
@@ -1770,9 +1863,21 @@ void _toneeq_process(dt_iop_module_t *self,
printf("_toneeq_process: in roi %d %d %d %d\n",
roi_in->x, roi_in->y, roi_in->width, roi_in->height);
#endif
- _compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+ if(self->dev->gui_attached && (piece->pipe->type & DT_DEV_PIXELPIPE_PREVIEW)) {
+ _compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
+ post_scale, post_shift,
+ piece->pipe->type,
+ g->prv_histogram_first_decile, g->prv_histogram_last_decile
+ );
+ }
+ else
+ {
+ _compute_correction_lut(d->correction_lut, d->smoothing, d->factors,
post_scale, post_shift,
- piece->pipe->type);
+ piece->pipe->type, NAN, NAN);
+ }
+
+
_apply_toneequalizer(in, luminance, out,
roi_in, roi_out,
d, piece->pipe->type);
@@ -1871,8 +1976,6 @@ void modify_roi_in(dt_iop_module_t *self,
roi_in->width = piece->buf_in.width;
roi_in->height = piece->buf_in.height;
roi_in->scale = 1.0f;
-
- printf("modify_roi_in/DT_DEV_PIXELPIPE_FULL: requesting full image for auto-alignment\n");
}
}
}
@@ -2276,14 +2379,14 @@ static inline void _compute_gui_histogram(int hires_histogram[HIRES_HISTO_SAMPLE
// (Re)init the histogram
memset(histogram, 0, sizeof(int) * UI_HISTO_SAMPLES);
- const float temp_ev_range = HIRES_HISTO_MAX_EV - HIRES_HISTO_MIN_EV;
+ const float temp_ev_range = HIRES_MAX_EV - HIRES_MIN_EV;
// remap the extended histogram into the gui histogram
// bins between [-8; 0] EV remapped between [0 ; UI_HISTO_SAMPLES]
for(size_t k = 0; k < HIRES_HISTO_SAMPLES; ++k)
{
// from [0...HIRES_HISTO_SAMPLES] to [-16...8EV]
- const float EV = temp_ev_range * (float)k / (float)(HIRES_HISTO_SAMPLES - 1) + HIRES_HISTO_MIN_EV;
+ const float EV = temp_ev_range * (float)k / (float)(HIRES_HISTO_SAMPLES - 1) + HIRES_MIN_EV;
// apply shift & scale to the EV value
const float shift_scaled_EV = _post_scale_shift(EV, histogram_scale, histogram_shift);