diff --git a/src/common/illuminants.h b/src/common/illuminants.h index 5f9530677c86..99a89343ae4f 100644 --- a/src/common/illuminants.h +++ b/src/common/illuminants.h @@ -34,6 +34,7 @@ typedef enum dt_illuminant_t DT_ILLUMINANT_BB = 6, // $DESCRIPTION: "Planckian (black body)" general black body radiator - not CIE standard DT_ILLUMINANT_CUSTOM = 7, // $DESCRIPTION: "custom" input x and y directly - bypass search DT_ILLUMINANT_CAMERA = 10,// $DESCRIPTION: "as shot in camera" read RAW EXIF for WB + DT_ILLUMINANT_FROM_WB = 11,// $DESCRIPTION: "as set in white balance module" read coefficients from the white balance module DT_ILLUMINANT_LAST, DT_ILLUMINANT_DETECT_SURFACES = 8, DT_ILLUMINANT_DETECT_EDGES = 9, @@ -215,19 +216,22 @@ static inline void illuminant_CCT_to_RGB(const float t, dt_aligned_pixel_t RGB) illuminant_xy_to_RGB(x, y, RGB); } +static inline gboolean find_temperature_from_wb_coeffs(const dt_image_t *img, const dt_aligned_pixel_t wb_coeffs, + float *chroma_x, float *chroma_y); // Fetch image from pipeline and read EXIF for camera RAW WB coeffs -static inline gboolean find_temperature_from_raw_coeffs(const dt_image_t *img, const dt_aligned_pixel_t custom_wb, +static inline gboolean find_temperature_from_as_shot_coeffs(const dt_image_t *img, const dt_aligned_pixel_t correction_ratios, float *chroma_x, float *chroma_y); -static inline int illuminant_to_xy(const dt_illuminant_t illuminant, // primary type of illuminant - const dt_image_t *img, // image container - const dt_aligned_pixel_t custom_wb, // optional user-set WB coeffs - float *x_out, float *y_out, // chromaticity output - const float t, // temperature in K, if needed - const dt_illuminant_fluo_t fluo, // sub-type of fluorescent illuminant, if needed - const dt_illuminant_led_t iled) // sub-type of led illuminant, if needed +static inline int illuminant_to_xy(const dt_illuminant_t illuminant, // primary type of illuminant + const dt_image_t *img, // image container + const dt_aligned_pixel_t correction_ratios, // optional D65 correction ratios derived from user-set coefficients + const dt_aligned_pixel_t wb_coeffs, // optional user-set WB coeffs (absolute) + float *x_out, float *y_out, // chromaticity output + const float t, // temperature in K, if needed + const dt_illuminant_fluo_t fluo, // sub-type of fluorescent illuminant, if needed + const dt_illuminant_led_t iled) // sub-type of led illuminant, if needed { /** * Compute the x and y chromaticity coordinates in Yxy spaces for standard illuminants @@ -294,13 +298,26 @@ static inline int illuminant_to_xy(const dt_illuminant_t illuminant, // primary // Model valid for T in [1667 ; 25000] K CCT_to_xy_blackbody(t, &x, &y); if(y != 0.f && x != 0.f) break; - // else t is out of bounds -> use custom/original values (next case) + // else t is out of bounds -> use custom/original values + return FALSE; } case DT_ILLUMINANT_CAMERA: { - // Detect WB from RAW EXIF - if(img) - if(find_temperature_from_raw_coeffs(img, custom_wb, &x, &y)) break; + // Detect WB from RAW EXIF, correcting with D65/wb_coeff ratios + if(img && find_temperature_from_as_shot_coeffs(img, correction_ratios, &x, &y)) + break; + + // xy calculation failed + return FALSE; + } + case DT_ILLUMINANT_FROM_WB: + { + // Detect WB from user-provided coefficients + if(img && find_temperature_from_wb_coeffs(img, wb_coeffs, &x, &y)) + break; + + // xy calculation failed + return FALSE; } case DT_ILLUMINANT_CUSTOM: // leave x and y as-is case DT_ILLUMINANT_DETECT_EDGES: @@ -385,31 +402,11 @@ static inline void matrice_pseudoinverse(float (*in)[3], float (*out)[3], int si } } - -static gboolean find_temperature_from_raw_coeffs(const dt_image_t *img, const dt_aligned_pixel_t custom_wb, - float *chroma_x, float *chroma_y) +// returns TRUE if OK, FALSE if failed +static gboolean get_CAM_to_XYZ(const dt_image_t * img, float(* CAM_to_XYZ)[3]) { - if(img == NULL) return FALSE; - if(!dt_image_is_matrix_correction_supported(img)) return FALSE; - - gboolean has_valid_coeffs = TRUE; - const int num_coeffs = (img->flags & DT_IMAGE_4BAYER) ? 4 : 3; - - // Check coeffs - for(int k = 0; has_valid_coeffs && k < num_coeffs; k++) - if(!dt_isnormal(img->wb_coeffs[k]) || img->wb_coeffs[k] == 0.0f) has_valid_coeffs = FALSE; - - if(!has_valid_coeffs) return FALSE; - - // Get white balance camera factors - dt_aligned_pixel_t WB = { img->wb_coeffs[0], img->wb_coeffs[1], img->wb_coeffs[2], img->wb_coeffs[3] }; - - // Adapt the camera coeffs with custom white balance if provided - // this can deal with WB coeffs that don't use the input matrix reference - if(custom_wb) - for(size_t k = 0; k < 4; k++) WB[k] *= custom_wb[k]; - - // Get the camera input profile (matrice of primaries) + if(img == NULL || CAM_to_XYZ == NULL) return FALSE; + // Get the camera input profile (matrice of primaries) - embedded or raw library DB float XYZ_to_CAM[4][3]; dt_mark_colormatrix_invalid(&XYZ_to_CAM[0][0]); @@ -438,21 +435,65 @@ static gboolean find_temperature_from_raw_coeffs(const dt_image_t *img, const dt if(!dt_is_valid_colormatrix(XYZ_to_CAM[0][0])) return FALSE; - // Bloody input matrices define XYZ -> CAM transform, as if we often needed camera profiles to output - // So we need to invert them. Here go your CPU cycles again. - float CAM_to_XYZ[4][3]; dt_mark_colormatrix_invalid(&CAM_to_XYZ[0][0]); matrice_pseudoinverse(XYZ_to_CAM, CAM_to_XYZ, 3); - if(!dt_is_valid_colormatrix(CAM_to_XYZ[0][0])) return FALSE; + return dt_is_valid_colormatrix(CAM_to_XYZ[0][0]); +} + +static inline gboolean _wb_coeffs_invalid(const dt_aligned_pixel_t wb_coeffs, const int num_coeffs) +{ + for(int k = 0; k < num_coeffs; k++) + if(!dt_isnormal(wb_coeffs[k]) || wb_coeffs[k] == 0.0f) return TRUE; + + return FALSE; +} + +// returns FALSE if failed; TRUE if successful +static gboolean find_temperature_from_wb_coeffs(const dt_image_t *img, const dt_aligned_pixel_t wb_coeffs, + float *chroma_x, float *chroma_y) +{ + if(img == NULL || wb_coeffs == NULL) return FALSE; + if(!dt_image_is_matrix_correction_supported(img)) return FALSE; + // it's enough to check the first 3 here, as WB_coeffs_to_illuminant_xy only uses indices 0 to 2 + if(_wb_coeffs_invalid(wb_coeffs, 3)) return FALSE; + + float CAM_to_XYZ[4][3]; + if(!get_CAM_to_XYZ(img, CAM_to_XYZ)) { + return FALSE; + } float x, y; - WB_coeffs_to_illuminant_xy(CAM_to_XYZ, WB, &x, &y); + WB_coeffs_to_illuminant_xy(CAM_to_XYZ, wb_coeffs, &x, &y); *chroma_x = x; *chroma_y = y; return TRUE; } +// returns FALSE if failed; TRUE if successful +static gboolean find_temperature_from_as_shot_coeffs(const dt_image_t *img, const dt_aligned_pixel_t correction_ratios, + float *chroma_x, float *chroma_y) +{ + if(img == NULL) return FALSE; + const int num_coeffs = (img->flags & DT_IMAGE_4BAYER) ? 4 : 3; + + if(_wb_coeffs_invalid(img->wb_coeffs, num_coeffs)) return FALSE; + + // Get as-shot white balance camera factors (from raw) + // component wise raw-RGB * wb_coeffs should provide R=G=B for a neutral patch under + // the scene illuminant + dt_aligned_pixel_t WB = { img->wb_coeffs[0], img->wb_coeffs[1], img->wb_coeffs[2], img->wb_coeffs[3] }; + + // Adapt the camera coeffs with custom D65 coefficients if provided ('caveats' workaround) + // this can deal with WB coeffs that don't use the input matrix reference + // correction_ratios[k] = chr->D65coeffs[k] / chr->wb_coeffs[k] + if(correction_ratios) + for(size_t k = 0; k < 4; k++) WB[k] *= correction_ratios[k]; + // for a neutral surface, raw RGB * img->wb_coeffs would produce neutral R=G=B + + return find_temperature_from_wb_coeffs(img, WB, chroma_x, chroma_y); +} + DT_OMP_DECLARE_SIMD() static inline float planckian_normal(const float x, const float t) diff --git a/src/develop/develop.h b/src/develop/develop.h index bd46d03a4ac8..b85f1ff94a35 100644 --- a/src/develop/develop.h +++ b/src/develop/develop.h @@ -147,9 +147,9 @@ typedef struct dt_dev_viewport_t - the currently used wb_coeffs in temperature module - D65coeffs and as_shot are read from exif data f) - late_correction set by temperature if we want to process data as following - If we use the new DT_IOP_TEMP_D65_LATE mode in temperature.c and don#t have - any temp parameters changes later we can calc correction coeffs to modify - as_shot rgb data to D65 + If we use the new DT_IOP_TEMP_D65_LATE mode or enable this in user modes of temperature.c + and don't have any temp parameters changes later we can calc correction ratios + to take white-balanced (neutralized) data to D65 */ typedef struct dt_dev_chroma_t { diff --git a/src/iop/channelmixerrgb.c b/src/iop/channelmixerrgb.c index aec66d386ac2..71477ba74426 100644 --- a/src/iop/channelmixerrgb.c +++ b/src/iop/channelmixerrgb.c @@ -422,7 +422,7 @@ void init_presets(dt_iop_module_so_t *self) p.illum_fluo = DT_ILLUMINANT_FLUO_F3; p.illum_led = DT_ILLUMINANT_LED_B5; p.temperature = 5003.f; - illuminant_to_xy(DT_ILLUMINANT_PIPE, NULL, NULL, &p.x, &p.y, p.temperature, + illuminant_to_xy(DT_ILLUMINANT_PIPE, NULL, NULL, NULL, &p.x, &p.y, p.temperature, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST); p.red[0] = 1.f; @@ -593,9 +593,7 @@ void init_presets(dt_iop_module_so_t *self) static gboolean _dev_is_D65_chroma(const dt_develop_t *dev) { const dt_dev_chroma_t *chr = &dev->chroma; - return chr->late_correction - ? dt_dev_equal_chroma(chr->wb_coeffs, chr->as_shot) - : dt_dev_equal_chroma(chr->wb_coeffs, chr->D65coeffs); + return chr->late_correction || dt_dev_equal_chroma(chr->wb_coeffs, chr->D65coeffs); } static gboolean _area_mapping_active(const dt_iop_channelmixer_rgb_gui_data_t *g) @@ -611,14 +609,14 @@ static const char *_area_mapping_section_text(const dt_iop_channelmixer_rgb_gui_ return _area_mapping_active(g) ? _("area color mapping (active)") : _("area color mapping"); } -static gboolean _get_white_balance_coeff(const dt_iop_module_t *self, - dt_aligned_pixel_t custom_wb) +static gboolean _get_d65_correction_ratios(const dt_iop_module_t *self, + dt_aligned_pixel_t out_correction_ratios) { const dt_dev_chroma_t *chr = &self->dev->chroma; // Init output with a no-op for_four_channels(k) - custom_wb[k] = 1.0f; + out_correction_ratios[k] = 1.0f; if(!dt_image_is_matrix_correction_supported(&self->dev->image_storage)) return TRUE; @@ -638,11 +636,33 @@ static gboolean _get_white_balance_coeff(const dt_iop_module_t *self, if(valid_chroma && changed_chroma) { for_four_channels(k) - custom_wb[k] = (float)chr->D65coeffs[k] / chr->wb_coeffs[k]; + { + if(chr->wb_coeffs[k] > 1e-6f) + out_correction_ratios[k] = chr->D65coeffs[k] / chr->wb_coeffs[k]; + else + out_correction_ratios[k] = 1.0f; + } } return FALSE; } +static void _get_corrected_illuminant_xy(const dt_iop_module_t *self, + const dt_iop_channelmixer_rgb_params_t *p, + float *x, float *y) +{ + dt_aligned_pixel_t correction_ratios; + _get_d65_correction_ratios(self, correction_ratios); + dt_aligned_pixel_t wb_coeffs = { 0.f }; + if(p->illuminant == DT_ILLUMINANT_FROM_WB) + { + for(int k = 0; k < 4; k++) + wb_coeffs[k] = self->dev->chroma.wb_coeffs[k]; + } + illuminant_to_xy(p->illuminant, &(self->dev->image_storage), correction_ratios, + wb_coeffs, x, y, p->temperature, p->illum_fluo, + p->illum_led); +} + DT_OMP_DECLARE_SIMD(aligned(input, output:16) uniform(compression, clip)) static inline void _gamut_mapping(const dt_aligned_pixel_t input, @@ -1246,7 +1266,7 @@ static void _check_if_close_to_daylight(const float x, float uv_test[2]; // Compute the test chromaticity from the daylight model - illuminant_to_xy(DT_ILLUMINANT_D, NULL, NULL, &xy_test[0], &xy_test[1], t, + illuminant_to_xy(DT_ILLUMINANT_D, NULL, NULL, NULL, &xy_test[0], &xy_test[1], t, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST); xy_to_uv(xy_test, uv_test); @@ -1255,7 +1275,7 @@ static void _check_if_close_to_daylight(const float x, const float delta_daylight = dt_fast_hypotf(uv_test[0] - uv_ref[0], uv_test[1] - uv_ref[1]); // Compute the test chromaticity from the blackbody model - illuminant_to_xy(DT_ILLUMINANT_BB, NULL, NULL, &xy_test[0], &xy_test[1], t, + illuminant_to_xy(DT_ILLUMINANT_BB, NULL, NULL, NULL, &xy_test[0], &xy_test[1], t, DT_ILLUMINANT_FLUO_LAST, DT_ILLUMINANT_LED_LAST); xy_to_uv(xy_test, uv_test); @@ -2208,10 +2228,25 @@ void process(dt_iop_module_t *self, // change here, so we can't update the defaults. So we need to // re-run the detection at runtime… float x, y; - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); + dt_aligned_pixel_t correction_ratios; + _get_d65_correction_ratios(self, correction_ratios); - if(find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(x), &(y))) + if(find_temperature_from_as_shot_coeffs(&(self->dev->image_storage), correction_ratios, &(x), &(y))) + { + // Convert illuminant from xyY to XYZ + dt_aligned_pixel_t XYZ; + illuminant_xy_to_XYZ(x, y, XYZ); + + // Convert illuminant from XYZ to Bradford modified LMS + convert_any_XYZ_to_LMS(XYZ, data->illuminant, data->adaptation); + data->illuminant[3] = 0.f; + } + } + else if(data->illuminant_type == DT_ILLUMINANT_FROM_WB) + { + float x, y; + + if(find_temperature_from_wb_coeffs(&(self->dev->image_storage), self->dev->chroma.wb_coeffs, &(x), &(y))) { // Convert illuminant from xyY to XYZ dt_aligned_pixel_t XYZ; @@ -2319,10 +2354,33 @@ int process_cl(dt_iop_module_t *self, // change here, so we can't update the defaults. So we need to // re-run the detection at runtime… float x, y; - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); + dt_aligned_pixel_t correction_ratios; + _get_d65_correction_ratios(self, correction_ratios); + + if(find_temperature_from_as_shot_coeffs(&(self->dev->image_storage), correction_ratios, &(x), &(y))) + { + // Convert illuminant from xyY to XYZ + dt_aligned_pixel_t XYZ; + illuminant_xy_to_XYZ(x, y, XYZ); + + // Convert illuminant from XYZ to Bradford modified LMS + convert_any_XYZ_to_LMS(XYZ, d->illuminant, d->adaptation); + d->illuminant[3] = 0.f; + } + } + else if(d->illuminant_type == DT_ILLUMINANT_FROM_WB) + { + // The camera illuminant is a behaviour rather than a preset of + // values: it uses whatever is in the RAW EXIF. But it depends on + // what temperature.c is doing and needs to be updated + // accordingly, to give a consistent result. We initialise the + // CAT defaults using the temperature coeffs at startup, but if + // temperature is changed later, we get no notification of the + // change here, so we can't update the defaults. So we need to + // re-run the detection at runtime… + float x, y; - if(find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, &(x), &(y))) + if(find_temperature_from_wb_coeffs(&(self->dev->image_storage), self->dev->chroma.wb_coeffs, &(x), &(y))) { // Convert illuminant from xyY to XYZ dt_aligned_pixel_t XYZ; @@ -3040,6 +3098,38 @@ static void _preview_pipe_finished_callback(gpointer instance, dt_iop_module_t * dt_iop_gui_enter_critical_section(self); gtk_label_set_markup(GTK_LABEL(g->label_delta_E), g->delta_E_label_text); + + dt_iop_channelmixer_rgb_params_t *p = self->params; + if(p->illuminant == DT_ILLUMINANT_FROM_WB) + { + // The WB coefficients might have changed in the temperature module. + // We need to re-calculate the chromaticity (x, y) to update the GUI feedback. + float x = p->x; + float y = p->y; + + if(find_temperature_from_wb_coeffs(&(self->dev->image_storage), self->dev->chroma.wb_coeffs, &x, &y)) + { + p->x = x; + p->y = y; + + darktable.gui->reset++; + // Update derived GUI widgets (CCT label, color patch) + _update_approx_cct(self); + _update_illuminant_color(self); + + // Update Hue/Chroma sliders to match the new coordinates + dt_aligned_pixel_t xyY = { p->x, p->y, 1.f }; + dt_aligned_pixel_t Lch; + dt_xyY_to_Lch(xyY, Lch); + + if(Lch[1] > 0) + dt_bauhaus_slider_set(g->illum_x, rad2degf(Lch[2])); + dt_bauhaus_slider_set(g->illum_y, Lch[1]); + + darktable.gui->reset--; + } + } + dt_iop_gui_leave_critical_section(self); _set_trouble_messages(self); } @@ -3110,16 +3200,12 @@ void commit_params(dt_iop_module_t *self, // find x y coordinates of illuminant for CIE 1931 2° observer float x = p->x; float y = p->y; - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); - illuminant_to_xy(p->illuminant, &(self->dev->image_storage), - custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led); + _get_corrected_illuminant_xy(self, p, &x, &y); - // if illuminant is set as camera, x and y are set on-the-fly at + // if illuminant is from camera or user WB coefficients, x and y are set on-the-fly at // commit time, so we need to set adaptation too - if(p->illuminant == DT_ILLUMINANT_CAMERA) - _check_if_close_to_daylight(x, y, NULL, NULL, &(d->adaptation)); - + if(p->illuminant == DT_ILLUMINANT_CAMERA || p->illuminant == DT_ILLUMINANT_FROM_WB) + _check_if_close_to_daylight(x, y, NULL, NULL, &(d->adaptation)); d->illuminant_type = p->illuminant; // Convert illuminant from xyY to XYZ @@ -3262,6 +3348,7 @@ static void _update_illuminants(const dt_iop_module_t *self) break; } case DT_ILLUMINANT_CAMERA: + case DT_ILLUMINANT_FROM_WB: { gtk_widget_set_visible(g->adaptation, TRUE); gtk_widget_set_visible(g->temperature, FALSE); @@ -3554,11 +3641,8 @@ static gboolean _illuminant_color_draw(GtkWidget *widget, // camera RAW is chosen float x = p->x; float y = p->y; - dt_aligned_pixel_t RGB = { 0 }; - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); - illuminant_to_xy(p->illuminant, &(self->dev->image_storage), custom_wb, - &x, &y, p->temperature, p->illum_fluo, p->illum_led); + dt_aligned_pixel_t RGB = { 0.f }; + _get_corrected_illuminant_xy(self, p, &x, &y); illuminant_xy_to_RGB(x, y, RGB); cairo_set_source_rgb(cr, RGB[0], RGB[1], RGB[2]); cairo_rectangle(cr, INNER_PADDING, margin, width, height); @@ -3658,10 +3742,7 @@ static void _update_approx_cct(const dt_iop_module_t *self) float x = p->x; float y = p->y; - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); - illuminant_to_xy(p->illuminant, &(self->dev->image_storage), - custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led); + _get_corrected_illuminant_xy(self, p, &x, &y); dt_illuminant_t test_illuminant; float t = 5000.f; @@ -3859,6 +3940,24 @@ void init(dt_iop_module_t *self) d->red[0] = d->green[1] = d->blue[2] = 1.0; } +static void _add_or_remove_illuminant(const dt_iop_module_t *self, + const int illuminant, + const gboolean use_illuminant) +{ + // we only call this if g is not NULL + dt_iop_channelmixer_rgb_gui_data_t *g = self->gui_data; + const int pos = dt_bauhaus_combobox_get_from_value(g->illuminant, illuminant); + if(use_illuminant) + { + if(pos == -1) + dt_bauhaus_combobox_add_introspection(g->illuminant, NULL, + self->so->get_f("illuminant")->Enum.values, + illuminant, illuminant); + } + else if (pos != -1) + dt_bauhaus_combobox_remove_at(g->illuminant, pos); +} + void reload_defaults(dt_iop_module_t *self) { dt_iop_channelmixer_rgb_params_t *d = self->default_params; @@ -3895,10 +3994,10 @@ void reload_defaults(dt_iop_module_t *self) { d->adaptation = DT_ADAPTATION_CAT16; - dt_aligned_pixel_t custom_wb; - if(!_get_white_balance_coeff(self, custom_wb)) + dt_aligned_pixel_t correction_ratios; + if(!_get_d65_correction_ratios(self, correction_ratios)) { - if(find_temperature_from_raw_coeffs(img, custom_wb, &(d->x), &(d->y))) + if(find_temperature_from_as_shot_coeffs(img, correction_ratios, &(d->x), &(d->y))) d->illuminant = DT_ILLUMINANT_CAMERA; _check_if_close_to_daylight(d->x, d->y, &(d->temperature), &(d->illuminant), &(d->adaptation)); @@ -3926,16 +4025,9 @@ void reload_defaults(dt_iop_module_t *self) g->delta_E_label_text = NULL; } - const int pos = dt_bauhaus_combobox_get_from_value(g->illuminant, DT_ILLUMINANT_CAMERA); - if(dt_image_is_matrix_correction_supported(img) && !dt_image_is_monochrome(img)) - { - if(pos == -1) - dt_bauhaus_combobox_add_introspection(g->illuminant, NULL, - self->so->get_f("illuminant")->Enum.values, - DT_ILLUMINANT_CAMERA, DT_ILLUMINANT_CAMERA); - } - else - dt_bauhaus_combobox_remove_at(g->illuminant, pos); + const gboolean has_color_matrix = dt_image_is_matrix_correction_supported(img) && !dt_image_is_monochrome(img); + _add_or_remove_illuminant(self, DT_ILLUMINANT_CAMERA, has_color_matrix); + _add_or_remove_illuminant(self, DT_ILLUMINANT_FROM_WB, has_color_matrix); gui_changed(self, NULL, NULL); } @@ -3999,9 +4091,19 @@ void gui_changed(dt_iop_module_t *self, // illuminant = "as set in camera", temperature and // chromaticity are inited with the preset content when // illuminant is changed. - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); - find_temperature_from_raw_coeffs(&(self->dev->image_storage), custom_wb, + dt_aligned_pixel_t correction_ratios; + _get_d65_correction_ratios(self, correction_ratios); + find_temperature_from_as_shot_coeffs(&(self->dev->image_storage), correction_ratios, + &(p->x), &(p->y)); + _check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation)); + } + if(*prev_illuminant == DT_ILLUMINANT_FROM_WB) + { + // If illuminant was previously "as set in white balance module", + // (x, y) were computed at runtime from the WB module's coefficients + // and p->x, p->y may hold stale values. Recompute them now so + // the new illuminant mode starts from the correct chromaticity. + find_temperature_from_wb_coeffs(&(self->dev->image_storage), self->dev->chroma.wb_coeffs, &(p->x), &(p->y)); _check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation)); } @@ -4016,15 +4118,24 @@ void gui_changed(dt_iop_module_t *self, if(p->illuminant == DT_ILLUMINANT_CAMERA) { // Get camera WB and update illuminant - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); - const gboolean found = find_temperature_from_raw_coeffs(&(self->dev->image_storage), - custom_wb, &(p->x), &(p->y)); + dt_aligned_pixel_t correction_ratios; + _get_d65_correction_ratios(self, correction_ratios); + const gboolean found = find_temperature_from_as_shot_coeffs(&(self->dev->image_storage), + correction_ratios, &(p->x), &(p->y)); _check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation)); if(found) dt_control_log(_("white balance successfully extracted from raw image")); } + if(p->illuminant == DT_ILLUMINANT_FROM_WB) + { + const gboolean found = find_temperature_from_wb_coeffs(&(self->dev->image_storage), + self->dev->chroma.wb_coeffs, &(p->x), &(p->y)); + _check_if_close_to_daylight(p->x, p->y, &(p->temperature), NULL, &(p->adaptation)); + + if(found) + dt_control_log(_("white balance successfully extracted from white balance module")); + } #ifdef AI_ACTIVATED else if(p->illuminant == DT_ILLUMINANT_DETECT_EDGES || p->illuminant == DT_ILLUMINANT_DETECT_SURFACES) @@ -4052,17 +4163,19 @@ void gui_changed(dt_iop_module_t *self, // illuminant to allow swapping modes if(p->illuminant != DT_ILLUMINANT_CUSTOM - && p->illuminant != DT_ILLUMINANT_CAMERA) + && p->illuminant != DT_ILLUMINANT_CAMERA + && p->illuminant != DT_ILLUMINANT_FROM_WB) { // We are in any mode defining (x, y) indirectly from an // interface, so commit (x, y) explicitly - illuminant_to_xy(p->illuminant, NULL, NULL, &(p->x), &(p->y), + illuminant_to_xy(p->illuminant, NULL, NULL, NULL, &(p->x), &(p->y), p->temperature, p->illum_fluo, p->illum_led); } if(p->illuminant != DT_ILLUMINANT_D && p->illuminant != DT_ILLUMINANT_BB - && p->illuminant != DT_ILLUMINANT_CAMERA) + && p->illuminant != DT_ILLUMINANT_CAMERA + && p->illuminant != DT_ILLUMINANT_FROM_WB) { // We are in any mode not defining explicitly a temperature, so // find the the closest CCT and commit it @@ -4146,7 +4259,7 @@ void gui_changed(dt_iop_module_t *self, // If "as shot in camera" illuminant is used, CAT space is forced automatically // therefore, make the control insensitive - gtk_widget_set_sensitive(g->adaptation, p->illuminant != DT_ILLUMINANT_CAMERA); + gtk_widget_set_sensitive(g->adaptation, p->illuminant != DT_ILLUMINANT_CAMERA && p->illuminant != DT_ILLUMINANT_FROM_WB); _declare_cat_on_pipe(self, FALSE); @@ -4223,15 +4336,13 @@ static void _auto_set_illuminant(dt_iop_module_t *self, // find x y coordinates of illuminant for CIE 1931 2° observer float x = p->x; float y = p->y; + _get_corrected_illuminant_xy(self, p, &x, &y); + dt_adaptation_t adaptation = p->adaptation; - dt_aligned_pixel_t custom_wb; - _get_white_balance_coeff(self, custom_wb); - illuminant_to_xy(p->illuminant, &(self->dev->image_storage), - custom_wb, &x, &y, p->temperature, p->illum_fluo, p->illum_led); // if illuminant is set as camera, x and y are set on-the-fly at // commit time, so we need to set adaptation too - if(p->illuminant == DT_ILLUMINANT_CAMERA) + if(p->illuminant == DT_ILLUMINANT_CAMERA || p->illuminant == DT_ILLUMINANT_FROM_WB) _check_if_close_to_daylight(x, y, NULL, NULL, &adaptation); // Convert illuminant from xyY to XYZ diff --git a/src/iop/colorin.c b/src/iop/colorin.c index e2438f33fe8d..188410039560 100644 --- a/src/iop/colorin.c +++ b/src/iop/colorin.c @@ -653,10 +653,15 @@ int process_cl(dt_iop_module_t *self, const dt_dev_chroma_t *chr = &self->dev->chroma; const gboolean corrected = chr->late_correction; - dt_aligned_pixel_t coeffs = { corrected ? chr->D65coeffs[0] / chr->as_shot[0] : 1.0f, - corrected ? chr->D65coeffs[1] / chr->as_shot[1] : 1.0f, - corrected ? chr->D65coeffs[2] / chr->as_shot[2] : 1.0f, - corrected ? chr->D65coeffs[3] / chr->as_shot[3] : 1.0f }; + dt_aligned_pixel_t coeffs; + for_four_channels(k) + { + if(corrected && chr->wb_coeffs[k] > 1e-6f) + coeffs[k] = chr->D65coeffs[k] / chr->wb_coeffs[k]; + else + coeffs[k] = 1.0f; + } + if(corrected) { for_four_channels(k) @@ -1199,10 +1204,15 @@ void process(dt_iop_module_t *self, const dt_dev_chroma_t *chr = &self->dev->chroma; const dt_iop_colorin_data_t *const d = piece->data; const gboolean corrected = chr->late_correction && d->type != DT_COLORSPACE_LAB; - const dt_aligned_pixel_t coeffs = { corrected ? chr->D65coeffs[0] / chr->as_shot[0] : 1.0f, - corrected ? chr->D65coeffs[1] / chr->as_shot[1] : 1.0f, - corrected ? chr->D65coeffs[2] / chr->as_shot[2] : 1.0f, - corrected ? chr->D65coeffs[3] / chr->as_shot[3] : 1.0f }; + dt_aligned_pixel_t coeffs; + for_four_channels(k) + { + if(corrected && chr->wb_coeffs[k] > 1e-6f) + coeffs[k] = chr->D65coeffs[k] / chr->wb_coeffs[k]; + else + coeffs[k] = 1.0f; + } + dt_dev_pixelpipe_t *pipe = piece->pipe; if(corrected) { diff --git a/src/iop/highlights.c b/src/iop/highlights.c index 7738935f340e..d0734bc519a8 100644 --- a/src/iop/highlights.c +++ b/src/iop/highlights.c @@ -659,7 +659,7 @@ int process_cl(dt_iop_module_t *self, dt_aligned_pixel_t clips = { clip, clip, clip, clip}; if(chr->late_correction) for_each_channel(c) - clips[c] *= chr->as_shot[c] / chr->D65coeffs[c]; + clips[c] *= chr->wb_coeffs[c] / chr->D65coeffs[c]; dev_clips = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), clips); dev_xtrans = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->xtrans), piece->xtrans); if(!dev_clips || !dev_xtrans) goto finish; @@ -736,7 +736,7 @@ static void process_clip(dt_iop_module_t *self, dt_aligned_pixel_t clips = { clip, clip, clip, clip}; if(chr->late_correction) { - for_each_channel(c) clips[c] *= chr->as_shot[c] / chr->D65coeffs[c]; + for_each_channel(c) clips[c] *= chr->wb_coeffs[c] / chr->D65coeffs[c]; } for(int row = 0; row < roi_out->height; row++) { diff --git a/src/iop/hlreconstruct/opposed.c b/src/iop/hlreconstruct/opposed.c index 0875e270c58e..bb8828b5c9db 100644 --- a/src/iop/hlreconstruct/opposed.c +++ b/src/iop/hlreconstruct/opposed.c @@ -239,10 +239,14 @@ static float *_process_opposed(dt_iop_module_t *self, const dt_dev_chroma_t *chr = &self->dev->chroma; const gboolean late = chr->late_correction; - const dt_aligned_pixel_t correction = { late ? (float)(chr->D65coeffs[0] / chr->as_shot[0]) : 1.0f, - late ? (float)(chr->D65coeffs[1] / chr->as_shot[1]) : 1.0f, - late ? (float)(chr->D65coeffs[2] / chr->as_shot[2]) : 1.0f, - 1.0f }; + dt_aligned_pixel_t correction; + for_four_channels(k) + { + if(late && chr->wb_coeffs[k] > 1e-6f) + correction[k] = chr->D65coeffs[k] / chr->wb_coeffs[k]; + else + correction[k] = 1.0f; + } const size_t mwidth = roi_in->width / 3; const size_t mheight = roi_in->height / 3; @@ -434,10 +438,14 @@ static cl_int process_opposed_cl(dt_iop_module_t *self, const dt_dev_chroma_t *chr = &self->dev->chroma; const gboolean late = chr->late_correction; - dt_aligned_pixel_t correction = { late ? (float)(chr->D65coeffs[0] / chr->as_shot[0]) : 1.0f, - late ? (float)(chr->D65coeffs[1] / chr->as_shot[1]) : 1.0f, - late ? (float)(chr->D65coeffs[2] / chr->as_shot[2]) : 1.0f, - 1.0f }; + dt_aligned_pixel_t correction; + for_four_channels(k) + { + if(late && chr->wb_coeffs[k] > 1e-6f) + correction[k] = chr->D65coeffs[k] / chr->wb_coeffs[k]; + else + correction[k] = 1.0f; + } cl_int err = CL_MEM_OBJECT_ALLOCATION_FAILURE; cl_mem dev_chrominance = NULL; diff --git a/src/iop/hlreconstruct/segbased.c b/src/iop/hlreconstruct/segbased.c index 1875207789b0..148873cec4f6 100644 --- a/src/iop/hlreconstruct/segbased.c +++ b/src/iop/hlreconstruct/segbased.c @@ -468,10 +468,14 @@ static void _process_segmentation(dt_dev_pixelpipe_iop_t *piece, const dt_dev_chroma_t *chr = &piece->module->dev->chroma; const gboolean late = chr->late_correction; - const dt_aligned_pixel_t correction = { late ? (float)(chr->D65coeffs[0] / chr->as_shot[0]) : 1.0f, - late ? (float)(chr->D65coeffs[1] / chr->as_shot[1]) : 1.0f, - late ? (float)(chr->D65coeffs[2] / chr->as_shot[2]) : 1.0f, - 1.0f }; + dt_aligned_pixel_t correction; + for_four_channels(k) + { + if(late && chr->wb_coeffs[k] > 1e-6f) + correction[k] = chr->D65coeffs[k] / chr->wb_coeffs[k]; + else + correction[k] = 1.0f; + } const int recovery_mode = d->recovery; const float strength = d->strength; diff --git a/src/iop/temperature.c b/src/iop/temperature.c index 1fb24c349c38..8417e6920bb2 100644 --- a/src/iop/temperature.c +++ b/src/iop/temperature.c @@ -43,7 +43,7 @@ #include "common/colorspaces.h" #include "external/cie_colorimetric_tables.c" -DT_MODULE_INTROSPECTION(4, dt_iop_temperature_params_t) +DT_MODULE_INTROSPECTION(5, dt_iop_temperature_params_t) #define INITIALBLACKBODYTEMPERATURE 4000 @@ -72,6 +72,7 @@ typedef struct dt_iop_temperature_params_t float blue; // $MIN: 0.0 $MAX: 8.0 float various; // $MIN: 0.0 $MAX: 8.0 int preset; + gboolean late_correction; // $DEFAULT: FALSE $DESCRIPTION: "prepare data for color calibration" } dt_iop_temperature_params_t; typedef struct dt_iop_temperature_gui_data_t @@ -87,6 +88,7 @@ typedef struct dt_iop_temperature_gui_data_t GtkWidget *btn_d65_late; GtkWidget *temp_label; GtkWidget *balance_label; + GtkWidget *check_late_correction; int preset_cnt; int preset_num[54]; double mod_coeff[4]; @@ -102,6 +104,7 @@ typedef struct dt_iop_temperature_data_t { float coeffs[4]; int preset; + gboolean late_correction; } dt_iop_temperature_data_t; typedef struct dt_iop_temperature_global_data_t @@ -142,6 +145,16 @@ int legacy_params(dt_iop_module_t *self, int preset; } dt_iop_temperature_params_v4_t; + typedef struct dt_iop_temperature_params_v5_t + { + float red; + float green; + float blue; + float various; + int preset; + gboolean late_correction; + } dt_iop_temperature_params_v5_t; + if(old_version == 2) { typedef struct dt_iop_temperature_params_v2_t @@ -179,6 +192,18 @@ int legacy_params(dt_iop_module_t *self, *new_version = 4; return 0; } + if(old_version == 4) + { + const dt_iop_temperature_params_v4_t *o = (dt_iop_temperature_params_v4_t *)old_params; + dt_iop_temperature_params_v5_t *n = malloc(sizeof(dt_iop_temperature_params_v5_t)); + + memcpy(n, o, sizeof(dt_iop_temperature_params_v4_t)); + n->late_correction = FALSE; + *new_params = n; + *new_params_size = sizeof(dt_iop_temperature_params_v5_t); + *new_version = 5; + return 0; + } return 1; } @@ -526,7 +551,7 @@ static inline void _publish_chroma(dt_dev_pixelpipe_iop_t *piece) d->coeffs[k] * piece->pipe->dsc.processed_maximum[k]; chr->wb_coeffs[k] = d->coeffs[k]; } - chr->late_correction = (d->preset == DT_IOP_TEMP_D65_LATE); + chr->late_correction = d->late_correction; } void process(dt_iop_module_t *self, @@ -717,12 +742,23 @@ void commit_params(dt_iop_module_t *self, d->preset = p->preset; + gboolean effective_late_correction = FALSE; + + if(p->preset == DT_IOP_TEMP_D65_LATE) + effective_late_correction = TRUE; + else if(p->preset == DT_IOP_TEMP_D65) + effective_late_correction = FALSE; + else + effective_late_correction = p->late_correction; + + d->late_correction = effective_late_correction; + chr->late_correction = effective_late_correction; + + chr->temperature = piece->enabled ? self : NULL; /* Make sure the chroma information stuff is valid If piece is disabled we always clear the trouble message and make sure chroma does know there is no temperature module. */ - chr->late_correction = p->preset == DT_IOP_TEMP_D65_LATE; - chr->temperature = piece->enabled ? self : NULL; if(pipe->type & DT_DEV_PIXELPIPE_PREVIEW && !piece->enabled) dt_iop_set_module_trouble_message(self, NULL, NULL, NULL); } @@ -1163,9 +1199,43 @@ static void _update_preset(dt_iop_module_t *self, int mode) { dt_iop_temperature_params_t *p = self->params; dt_dev_chroma_t *chr = &self->dev->chroma; + dt_iop_temperature_gui_data_t *g = self->gui_data; + + const gboolean is_current_reference = (p->preset == DT_IOP_TEMP_D65_LATE) || + (p->preset == DT_IOP_TEMP_D65); + const gboolean is_new_mode_manual = (mode != DT_IOP_TEMP_D65_LATE) && + (mode != DT_IOP_TEMP_D65); + + if(is_current_reference && is_new_mode_manual) + { + // set iff color calibration active in adaptation mode + p->late_correction = (chr->adaptation != NULL); + } p->preset = mode; - chr->late_correction = mode == DT_IOP_TEMP_D65_LATE; + + if(mode == DT_IOP_TEMP_D65_LATE) + { + chr->late_correction = TRUE; + } + else if(mode == DT_IOP_TEMP_D65) + { + chr->late_correction = FALSE; + } + else + { + // For manual modes, use the parameter + chr->late_correction = p->late_correction; + } + + if(g && g->check_late_correction) + { + // show the checkbox only in modes where user can adjust multipliers + gtk_widget_set_visible(g->check_late_correction, is_new_mode_manual); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->check_late_correction), + p->late_correction); + } } void gui_update(dt_iop_module_t *self) @@ -1516,6 +1586,7 @@ void reload_defaults(dt_iop_module_t *self) dt_iop_temperature_params_t *p = self->params; d->preset = dt_is_scene_referred() ? DT_IOP_TEMP_D65_LATE : DT_IOP_TEMP_AS_SHOT; + d->late_correction = dt_is_scene_referred(); float *dcoeffs = (float *)d; for_four_channels(k) @@ -1741,8 +1812,10 @@ void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous) _mul2temp(self, p, &g->mod_temp, &g->mod_tint); - dt_bauhaus_combobox_set(g->presets, DT_IOP_TEMP_USER); - _update_preset(self, DT_IOP_TEMP_USER); + if(w != g->check_late_correction) { + dt_bauhaus_combobox_set(g->presets, DT_IOP_TEMP_USER); + _update_preset(self, DT_IOP_TEMP_USER); + } } static gboolean _btn_toggled(GtkWidget *togglebutton, @@ -2164,12 +2237,26 @@ void gui_init(dt_iop_module_t *self) (g->scale_tint, _("color tint of the image, from magenta (value < 1) to green (value > 1)")); + g->check_late_correction = dt_bauhaus_toggle_from_params(self, "late_correction"); + g_object_ref(g->check_late_correction); // prevent destruction + GtkWidget *temp_parent = gtk_widget_get_parent(g->check_late_correction); + if(temp_parent) + gtk_container_remove(GTK_CONTAINER(temp_parent), g->check_late_correction); + + gtk_widget_set_tooltip_text(g->check_late_correction, + _("ensures the white balance coefficients are treated as a reference for the color calibration module.\n" + "keep this checked if you use the modern scene-referred workflow but want to manually adjust white balance.")); + GtkWidget *box_enabled = dt_gui_vbox(g->buttonbar, + g->check_late_correction, g->presets, g->finetune, temp_label_box, g->scale_k, g->scale_tint); + + g_object_unref(g->check_late_correction); + dt_gui_new_collapsible_section (&g->cs, "plugins/darkroom/temperature/expand_coefficients",