/* This file is part of darktable, Copyright (C) 2009-2026 darktable developers. darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. darktable is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with darktable. If not, see . */ #include #include #include #include "common/colorspaces_inline_conversions.h" #include "common/imagebuf.h" #include "control/control.h" #include "develop/develop.h" #include "gui/accelerators.h" #include "gui/gtk.h" #include "iop/iop_api.h" DT_MODULE_INTROSPECTION(1, dt_iop_gamma_params_t) typedef struct dt_iop_gamma_params_t { float gamma, linear; } dt_iop_gamma_params_t; const char *name() { return C_("modulename", "display encoding"); } int default_group() { return IOP_GROUP_COLOR | IOP_GROUP_TECHNICAL; } int flags() { return IOP_FLAGS_HIDDEN | IOP_FLAGS_ONE_INSTANCE | IOP_FLAGS_FENCE | IOP_FLAGS_UNSAFE_COPY | IOP_FLAGS_WRITE_PIPECACHE; } dt_iop_colorspace_type_t default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece) { return IOP_CS_RGB; } DT_OMP_DECLARE_SIMD(aligned(in, out, mask_color: 16) uniform(mask_color, alpha)) static inline void _write_pixel(const float *const restrict in, uint8_t *const restrict out, const float *const restrict mask_color, const float alpha) { // takes a linear RGB pixel as input dt_aligned_pixel_t pixel; // linear sRGB (REC 709) -> gamma corrected sRGB for(size_t c = 0; c < 3; c++) pixel[c] = in[c] <= 0.0031308f ? 12.92f * in[c] : (1.0f + 0.055f) * powf(in[c], 1.0f / 2.4f) - 0.055f; // the output of this module is BGR(A) instead of RGBA; can't use // for_each_channel here due to the index swap for(size_t c = 0; c < 3; c++) { const float value = roundf(255.0f * (pixel[c] * (1.0f - alpha) + mask_color[c] * alpha)); out[2 - c] = (uint8_t)(fminf(fmaxf(value, 0.0f), 255.0f)); } } DT_OMP_DECLARE_SIMD(aligned(pixel: 16) uniform(norm)) static void _normalize_color(float *const restrict pixel, const float norm) { // color may not be black! const float factor = norm / max3f(pixel); for_each_channel(x) pixel[x] *= factor; } DT_OMP_DECLARE_SIMD(aligned(XYZ, sRGB: 16) uniform(norm)) static inline void _XYZ_to_REC_709_normalized(const float *const restrict XYZ, float *const restrict sRGB, const float norm) { dt_XYZ_to_Rec709_D50(XYZ, sRGB); _normalize_color(sRGB, norm); } DT_OMP_DECLARE_SIMD(aligned(in, out: 64) uniform(buffsize, alpha)) static void _channel_display_monochrome(const float *const restrict in, uint8_t *const restrict out, const size_t buffsize, const float alpha) { // yellow; "unused" element enables vectorization const dt_aligned_pixel_t mask_color = { 1.0f, 1.0f, 0.0f }; DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { dt_aligned_pixel_t pixel = { in[j + 1], in[j + 1], in[j + 1], in[j + 1] }; _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } } DT_OMP_DECLARE_SIMD(aligned(in, out: 64) uniform(buffsize, alpha, channel)) static void _channel_display_false_color(const float *const restrict in, uint8_t *const restrict out, const size_t buffsize, const float alpha, dt_dev_pixelpipe_display_mask_t channel) { // yellow, "unused" element aids vectorization const dt_aligned_pixel_t mask_color = { 1.0f, 1.0f, 0.0f }; switch(channel & DT_DEV_PIXELPIPE_DISPLAY_ANY & ~DT_DEV_PIXELPIPE_DISPLAY_OUTPUT) { case DT_DEV_PIXELPIPE_DISPLAY_a: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { dt_aligned_pixel_t xyz; dt_aligned_pixel_t pixel; // colors with "a" exceeding the range [-56,56] range will // yield colors not representable in sRGB const float value = fminf(fmaxf(in[j + 1] * 256.0f - 128.0f, -56.0f), 56.0f); const dt_aligned_pixel_t lab = { 79.0f - value * (11.0f / 56.0f), value, 0.0f, 0.0f }; dt_Lab_to_XYZ(lab, xyz); _XYZ_to_REC_709_normalized(xyz, pixel, 0.75f); _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_b: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { dt_aligned_pixel_t xyz, pixel; // colors with "b" exceeding the range [-65,65] range will // yield colors not representable in sRGB const float value = fminf(fmaxf(in[j + 1] * 256.0f - 128.0f, -65.0f), 65.0f); const dt_aligned_pixel_t lab = { 60.0f + value * (2.0f / 65.0f), 0.0f, value, 0.0f }; dt_Lab_to_XYZ(lab, xyz); _XYZ_to_REC_709_normalized(xyz, pixel, 0.75f); _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_R: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { const dt_aligned_pixel_t pixel = { in[j + 1], 0.0f, 0.0f, 0.0f }; _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_G: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { const dt_aligned_pixel_t pixel = { 0.0f, in[j + 1], 0.0f, 0.0f }; _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_B: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { const dt_aligned_pixel_t pixel = { 0.0f, 0.0f, in[j + 1], 0.0f }; _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_LCH_C: case DT_DEV_PIXELPIPE_DISPLAY_HSL_S: case DT_DEV_PIXELPIPE_DISPLAY_JzCzhz_Cz: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { const dt_aligned_pixel_t pixel = { 0.5f, 0.5f * (1.0f - in[j + 1]), 0.5f, 0.0f }; _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_LCH_h: DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j += 4) { dt_aligned_pixel_t lch = { 65.0f, 37.0f, in[j + 1], 0.0f }; dt_aligned_pixel_t lab, xyz, pixel; dt_LCH_2_Lab(lch, lab); lab[3] = 0.0f; dt_Lab_to_XYZ(lab, xyz); _XYZ_to_REC_709_normalized(xyz, pixel, 0.75f); _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_HSL_H: DT_OMP_FOR() for(size_t j = 0; j < buffsize; j += 4) { dt_aligned_pixel_t hsl = { in[j + 1], 0.5f, 0.5f, 0.0f }; dt_aligned_pixel_t pixel; dt_HSL_2_RGB(hsl, pixel); _normalize_color(pixel, 0.75f); _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_JzCzhz_hz: DT_OMP_FOR() for(size_t j = 0; j < buffsize; j += 4) { const dt_aligned_pixel_t JzCzhz = { 0.011f, 0.01f, in[j + 1] }; dt_aligned_pixel_t JzAzBz; dt_aligned_pixel_t XYZ_D65; dt_aligned_pixel_t pixel; dt_JzCzhz_2_JzAzBz(JzCzhz, JzAzBz); dt_JzAzBz_2_XYZ(JzAzBz, XYZ_D65); dt_XYZ_to_Rec709_D65(XYZ_D65, pixel); _normalize_color(pixel, 0.75f); _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } break; case DT_DEV_PIXELPIPE_DISPLAY_L: case DT_DEV_PIXELPIPE_DISPLAY_GRAY: case DT_DEV_PIXELPIPE_DISPLAY_HSL_l: case DT_DEV_PIXELPIPE_DISPLAY_JzCzhz_Jz: default: _channel_display_monochrome(in, out, buffsize, alpha); break; } } DT_OMP_DECLARE_SIMD(aligned(in, out: 64) uniform(buffsize, alpha)) static void _mask_display(const float *const restrict in, uint8_t *const restrict out, const size_t buffsize, const float alpha) { // yellow, "unused" element aids vectorization const dt_aligned_pixel_t mask_color = { 1.0f, 1.0f, 0.0f }; const float mix = CLIP(dt_conf_get_float("darkroom/ui/develop_mask_mix")); DT_OMP_FOR_SIMD(aligned(in, out: 64) aligned(mask_color: 16)) for(size_t j = 0; j < buffsize; j+= 4) { const float gray = mixf(0.3f * in[j + 0] + 0.59f * in[j + 1] + 0.11f * in[j + 2], in[j + 3], mix); const dt_aligned_pixel_t pixel = { gray, gray, gray, gray }; _write_pixel(pixel, out + j, mask_color, in[j + 3] * alpha); } } DT_OMP_DECLARE_SIMD(aligned(in, out: 64) uniform(buffsize)) static void _copy_output(const float *const restrict in, uint8_t *const restrict out, const size_t buffsize) { DT_OMP_FOR_SIMD(aligned(in, out: 64)) for(size_t j = 0; j < buffsize; j += 4) { // the output of this module is BGR(A) instead of RGBA, so we can't use for_each_channel for(size_t c = 0; c < 3; c++) { out[j + 2 - c] = (uint8_t)(fminf(roundf(255.0f * fmaxf(in[j + c], 0.0f)), 255.0f)); } } } void process(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const i, void *const o, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out) { if(!dt_iop_have_required_input_format(4 /*we need full-color pixels*/, self, piece->colors, i, o, roi_in, roi_out)) return; // image has been copied through to output and module's trouble flag has been updated // this module also expects the same size of input image as the output image if(roi_in->width != roi_out->width || roi_in->height != roi_out->height) return; // dt_dev_image(want_float): the caller wants the scene-referred linear float // working RGB, not display-encoded 8-bit ARGB. Pass the 4-channel float buffer // straight through (the output buffer is already allocated 16 B/px). gamma // stays the terminal module so backbuf dimensions remain consistent. if(piece->pipe->type & DT_DEV_PIXELPIPE_IMAGE_FLOAT) { dt_iop_image_copy_by_size( (float *const restrict)o, (const float *const restrict)i, roi_out->width, roi_out->height, 4); return; } const dt_dev_pixelpipe_display_mask_t mask_display = piece->pipe->mask_display; const gboolean fcolor = dt_conf_is_equal("channel_display", "false color"); const size_t buffsize = (size_t)roi_out->width * roi_out->height * 4; const float alpha = (mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) ? 1.0f : 0.0f; if((mask_display & DT_DEV_PIXELPIPE_DISPLAY_CHANNEL) && (mask_display & DT_DEV_PIXELPIPE_DISPLAY_ANY)) { if(fcolor) { _channel_display_false_color((const float *const restrict)i, (uint8_t *const restrict)o, buffsize, alpha, mask_display); } else { _channel_display_monochrome((const float *const restrict)i, (uint8_t *const restrict)o, buffsize, alpha); } } else if(mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) { _mask_display((const float *const restrict)i, (uint8_t *const restrict)o, buffsize, 1.0f); } else { _copy_output((const float *const restrict)i, (uint8_t *const restrict)o, buffsize); } if(mask_display) dt_dev_pixelpipe_invalidate_cacheline(piece->pipe, i, "gamma mask display"); } void init(dt_iop_module_t *self) { // self->data = malloc(sizeof(dt_iop_gamma_data_t)); self->params = calloc(1, sizeof(dt_iop_gamma_params_t)); self->default_params = calloc(1, sizeof(dt_iop_gamma_params_t)); self->params_size = sizeof(dt_iop_gamma_params_t); self->gui_data = NULL; self->hide_enable_button = TRUE; self->default_enabled = TRUE; } // clang-format off // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py // vim: shiftwidth=2 expandtab tabstop=2 cindent // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified; // clang-format on