Skip to content

Commit 086ec9c

Browse files
authored
Automatic highlight preservation mode compensation (#19347)
* Automatic highlight preservation mode compensation Add support for compensating Nikon Z (HLG tone mode) and Fujifilm (DR200/DR400) under-exposure in HDR/highlight preservation modes.
1 parent 3a51e4f commit 086ec9c

File tree

3 files changed

+109
-4
lines changed

3 files changed

+109
-4
lines changed

src/common/exif.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,22 @@ static bool _exif_decode_exif_data(dt_image_t *img, Exiv2::ExifData &exifData)
13241324
}
13251325
}
13261326

1327+
// Compute exposure bias applied by HDR/highlight-preservation/HLG-tone modes
1328+
img->exif_highlight_preservation = 0.0f;
1329+
if(FIND_EXIF_TAG("Exif.Nikon3.ColorSpace"))
1330+
{
1331+
if(pos->toLong() == 4) // HLG tone mode
1332+
img->exif_highlight_preservation = 2.0f;
1333+
}
1334+
else if(FIND_EXIF_TAG("Exif.Fujifilm.DevelopmentDynamicRange"))
1335+
{
1336+
int dr = pos->toLong();
1337+
if(dr == 200)
1338+
img->exif_highlight_preservation = 1.0f;
1339+
else if(dr == 400)
1340+
img->exif_highlight_preservation = 2.0f;
1341+
}
1342+
13271343
// Read focal length
13281344
if((pos = Exiv2::focalLength(exifData)) != exifData.end() && pos->size())
13291345
{

src/common/image.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ typedef struct dt_image_t
262262
float exif_focal_length;
263263
float exif_focus_distance;
264264
float exif_crop;
265+
float exif_highlight_preservation;
265266
int32_t exif_flash_tagvalue;
266267
char exif_maker[64];
267268
char exif_model[64];

src/iop/exposure.c

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
#define exposure2white(x) exp2f(-(x))
4545
#define white2exposure(x) -dt_log2f(fmaxf(1e-20f, x))
4646

47-
DT_MODULE_INTROSPECTION(6, dt_iop_exposure_params_t)
47+
DT_MODULE_INTROSPECTION(7, dt_iop_exposure_params_t)
4848

4949
typedef enum dt_iop_exposure_mode_t
5050
{
@@ -71,6 +71,7 @@ typedef struct dt_iop_exposure_params_t
7171
float deflicker_percentile; // $MIN: 0.0 $MAX: 100.0 $DEFAULT: 50.0 $DESCRIPTION: "percentile"
7272
float deflicker_target_level; // $MIN: -18.0 $MAX: 18.0 $DEFAULT: -4.0 $DESCRIPTION: "target level"
7373
gboolean compensate_exposure_bias;// $DEFAULT: FALSE $DESCRIPTION: "compensate exposure bias"
74+
gboolean compensate_hilite_pres; // $DEFAULT: TRUE $DESCRIPTION: "compensate highlight preservation"
7475
} dt_iop_exposure_params_t;
7576

7677
typedef struct dt_iop_exposure_gui_data_t
@@ -85,6 +86,7 @@ typedef struct dt_iop_exposure_gui_data_t
8586
dt_dev_histogram_stats_t deflicker_histogram_stats;
8687
GtkLabel *deflicker_used_EC;
8788
GtkWidget *compensate_exposure_bias;
89+
GtkWidget *compensate_hilite_preserv;
8890
float deflicker_computed_exposure;
8991

9092
GtkWidget *spot_mode;
@@ -168,6 +170,17 @@ int legacy_params(dt_iop_module_t *self,
168170
gboolean compensate_exposure_bias;
169171
} dt_iop_exposure_params_v6_t;
170172

173+
typedef struct dt_iop_exposure_params_v7_t
174+
{
175+
dt_iop_exposure_mode_t mode;
176+
float black;
177+
float exposure;
178+
float deflicker_percentile;
179+
float deflicker_target_level;
180+
gboolean compensate_exposure_bias;
181+
gboolean compensate_hilite_pres;
182+
} dt_iop_exposure_params_v7_t;
183+
171184
if(old_version == 2)
172185
{
173186
typedef struct dt_iop_exposure_params_v2_t
@@ -272,6 +285,24 @@ int legacy_params(dt_iop_module_t *self,
272285
*new_version = 6;
273286
return 0;
274287
}
288+
if(old_version == 6)
289+
{
290+
const dt_iop_exposure_params_v6_t *o = (dt_iop_exposure_params_v6_t *)old_params;
291+
dt_iop_exposure_params_v7_t *n = malloc(sizeof(dt_iop_exposure_params_v7_t));
292+
293+
n->mode = o->mode;
294+
n->black = o->black;
295+
n->exposure = o->exposure;
296+
n->deflicker_percentile = o->deflicker_percentile;
297+
n->deflicker_target_level = o->deflicker_target_level;
298+
n->compensate_exposure_bias = o->compensate_exposure_bias;
299+
n->compensate_hilite_pres = FALSE; // module did not compensate h.p. before version 7
300+
301+
*new_params = n;
302+
*new_params_size = sizeof(dt_iop_exposure_params_v7_t);
303+
*new_version = 7;
304+
return 0;
305+
}
275306
return 1;
276307
}
277308

@@ -287,7 +318,8 @@ void init_presets(dt_iop_module_so_t *self)
287318
.exposure = 0.0f,
288319
.deflicker_percentile = 50.0f,
289320
.deflicker_target_level = -4.0f,
290-
.compensate_exposure_bias = FALSE},
321+
.compensate_exposure_bias = FALSE,
322+
.compensate_hilite_pres = FALSE },
291323
sizeof(dt_iop_exposure_params_t), TRUE, DEVELOP_BLEND_CS_RGB_DISPLAY);
292324

293325
const gboolean is_scene_referred = dt_is_scene_referred();
@@ -333,6 +365,7 @@ void reload_defaults(dt_iop_module_t *self)
333365
d->black = 0.0f;
334366
d->compensate_exposure_bias = FALSE;
335367
}
368+
d->compensate_hilite_pres = TRUE;
336369
}
337370

338371
static void _deflicker_prepare_histogram(dt_iop_module_t *self,
@@ -545,6 +578,26 @@ static float _get_exposure_bias(const dt_iop_module_t *self)
545578
return 0.0f;
546579
}
547580

581+
static float _get_highlight_bias(const dt_iop_module_t *self)
582+
{
583+
float bias = 0.0f;
584+
585+
// Nikon: Exif.Nikon3.Colorspace==4 --> +2 EV
586+
// Fuji: Exif.Fujifilm.DevelopmentDynamicRange
587+
// 100 --> no comp
588+
// 200 --> +1 EV
589+
// 400 --> +2 EV
590+
591+
if(self->dev && self->dev->image_storage.exif_highlight_preservation > 0.0f)
592+
bias = self->dev->image_storage.exif_highlight_preservation;
593+
594+
// sanity checks, don't trust exif tags too much
595+
if(bias != DT_EXIF_TAG_UNINITIALIZED)
596+
return CLAMP(bias, -1.0f, 4.0f);
597+
else
598+
return 0.0f;
599+
}
600+
548601

549602
void commit_params(dt_iop_module_t *self,
550603
dt_iop_params_t *p1,
@@ -564,6 +617,12 @@ void commit_params(dt_iop_module_t *self,
564617
if(p->compensate_exposure_bias)
565618
d->params.exposure -= _get_exposure_bias(self);
566619

620+
// If highlight preservation compensation has been required, add it on top of
621+
// the previous compensation values
622+
// d->params.compensate_hilite_pres = p->compensate_hilite_pres;
623+
if(p->compensate_hilite_pres)
624+
d->params.exposure += _get_highlight_bias(self);
625+
567626
d->deflicker = 0;
568627

569628
if(p->mode == EXPOSURE_MODE_DEFLICKER
@@ -579,7 +638,7 @@ void init_pipe(dt_iop_module_t *self,
579638
dt_dev_pixelpipe_t *pipe,
580639
dt_dev_pixelpipe_iop_t *piece)
581640
{
582-
piece->data = malloc(sizeof(dt_iop_exposure_data_t));
641+
piece->data = calloc(1,sizeof(dt_iop_exposure_data_t));
583642
}
584643

585644
void cleanup_pipe(dt_iop_module_t *self,
@@ -626,6 +685,18 @@ void gui_update(dt_iop_module_t *self)
626685
PANGO_ELLIPSIZE_MIDDLE);
627686
g_free(label);
628687

688+
const float hlbias = _get_highlight_bias(self);
689+
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->compensate_hilite_preserv),
690+
p->compensate_hilite_pres);
691+
/* xgettext:no-c-format */
692+
label = g_strdup_printf(_("higlight preservation mode (%.1f EV)"), hlbias);
693+
gtk_button_set_label(GTK_BUTTON(g->compensate_hilite_preserv), label);
694+
gtk_label_set_ellipsize
695+
(GTK_LABEL(gtk_bin_get_child(GTK_BIN(g->compensate_hilite_preserv))),
696+
PANGO_ELLIPSIZE_MIDDLE);
697+
g_free(label);
698+
gtk_widget_set_sensitive(GTK_WIDGET(g->compensate_hilite_preserv), hlbias > 0.0f);
699+
629700
g->spot_RGB[0] = 0.f;
630701
g->spot_RGB[1] = 0.f;
631702
g->spot_RGB[2] = 0.f;
@@ -669,7 +740,7 @@ void gui_update(dt_iop_module_t *self)
669740
void init_global(dt_iop_module_so_t *self)
670741
{
671742
const int program = 2; // from programs.conf: basic.cl
672-
dt_iop_exposure_global_data_t *gd = malloc(sizeof(dt_iop_exposure_global_data_t));
743+
dt_iop_exposure_global_data_t *gd = calloc(1,sizeof(dt_iop_exposure_global_data_t));
673744
self->data = gd;
674745
gd->kernel_exposure = dt_opencl_create_kernel(program, "exposure");
675746
}
@@ -813,6 +884,10 @@ static void _auto_set_exposure(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe)
813884
if(p->compensate_exposure_bias)
814885
expo -= _get_exposure_bias(self);
815886

887+
// If the highlight preservation mode is on, we need to add it to the user param
888+
if(p->compensate_hilite_pres)
889+
expo += _get_highlight_bias(self);
890+
816891
const float white = exposure2white(-expo);
817892

818893
// apply the exposure compensation
@@ -855,6 +930,10 @@ static void _auto_set_exposure(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe)
855930
if(p->compensate_exposure_bias)
856931
expo -= _get_exposure_bias(self);
857932

933+
// If the highlight preservation mode is on, we need to add it to the user param
934+
if(p->compensate_hilite_pres)
935+
expo += _get_highlight_bias(self);
936+
858937
white = exposure2white(-expo);
859938
_exposure_set_white(self, white);
860939
}
@@ -1097,6 +1176,15 @@ void gui_init(dt_iop_module_t *self)
10971176
_("automatically remove the camera exposure bias\n"
10981177
"this is useful if you exposed the image to the right."));
10991178

1179+
g->compensate_hilite_preserv = dt_bauhaus_toggle_from_params
1180+
(self, "compensate_hilite_pres");
1181+
gtk_widget_set_tooltip_text(g->compensate_hilite_preserv,
1182+
_("remove the camera's hidden exposure bias in\n"
1183+
"HDR / highlight preservation / dynamic range / HLG tone mode.\n"
1184+
"\n"
1185+
"when enabled, tone mapping (e.g. sigmoid) is required to\n"
1186+
"avoid blown-out highlights."));
1187+
11001188
g->exposure = dt_color_picker_new(self, DT_COLOR_PICKER_AREA,
11011189
dt_bauhaus_slider_from_params(self, N_("exposure")));
11021190
gtk_widget_set_tooltip_text(g->exposure, _("adjust the exposure correction"));

0 commit comments

Comments
 (0)