/* This file is part of darktable, Copyright (C) 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 "common.h" // the per-pixel steps of src/iop/cacorrectrgb.c, one kernel per loop there. // The guide channel is a runtime index, so pixels go through float arrays. #define CACORRECT_MAX_EV_DIFF 2.0f static inline void _load4(read_only image2d_t img, const int x, const int y, float v[4]) { const float4 p = readpixel(img, x, y); v[0] = p.x; v[1] = p.y; v[2] = p.z; v[3] = p.w; } static inline void _store4(write_only image2d_t img, const int x, const int y, const float v[4]) { write_imagef(img, (int2)(x, y), (float4)(v[0], v[1], v[2], v[3])); } // the first loop of get_manifolds() kernel void cacorrectrgb_manifolds(read_only image2d_t in, read_only image2d_t blurred_in, write_only image2d_t manifold_higher, write_only image2d_t manifold_lower, const int width, const int height, const int guide) { const int x = get_global_id(0); const int y = get_global_id(1); if(x >= width || y >= height) return; float pin[4], avg[4]; _load4(in, x, y, pin); _load4(blurred_in, x, y, avg); const float pixelg = fmax(pin[guide], 1E-6f); float weighth = pixelg >= avg[guide] ? 1.0f : 0.0f; float weightl = pixelg <= avg[guide] ? 1.0f : 0.0f; float logdiffs[2]; for(int kc = 0; kc <= 1; kc++) { const int c = (kc + guide + 1) % 3; logdiffs[kc] = dtcl_log2(fmax(pin[c], 1E-6f) / pixelg); } const float maxlogdiff = fmax(fabs(logdiffs[0]), fabs(logdiffs[1])); if(maxlogdiff > CACORRECT_MAX_EV_DIFF) { const float correction_weight = CACORRECT_MAX_EV_DIFF / maxlogdiff; weightl *= correction_weight; weighth *= correction_weight; } float high[4], low[4]; for(int kc = 0; kc <= 1; kc++) { const int c = (kc + guide + 1) % 3; high[c] = logdiffs[kc] * weighth; low[c] = logdiffs[kc] * weightl; } high[guide] = pixelg * weighth; low[guide] = pixelg * weightl; high[3] = weighth; low[3] = weightl; _store4(manifold_higher, x, y, high); _store4(manifold_lower, x, y, low); } static inline void _normalize(const float blurred[4], float m[4], const int guide) { const float weight = fmax(m[3], 1E-2f); const float mg = m[guide] / weight; m[guide] = mg; for(int kc = 0; kc <= 1; kc++) { const int c = (kc + guide + 1) % 3; m[c] = dtcl_exp2(m[c] / weight) * mg; } if(weight < 0.05f) { const float w = (weight - 0.01f) / (0.05f - 0.01f); for(int c = 0; c < 4; c++) m[c] = w * m[c] + (1.0f - w) * blurred[c]; } } // normalize_manifolds() kernel void cacorrectrgb_normalize(read_only image2d_t blurred_in, read_only image2d_t blurred_higher, read_only image2d_t blurred_lower, write_only image2d_t manifold_higher, write_only image2d_t manifold_lower, const int width, const int height, const int guide) { const int x = get_global_id(0); const int y = get_global_id(1); if(x >= width || y >= height) return; float avg[4], high[4], low[4]; _load4(blurred_in, x, y, avg); _load4(blurred_higher, x, y, high); _load4(blurred_lower, x, y, low); _normalize(avg, high, guide); _normalize(avg, low, guide); _store4(manifold_higher, x, y, high); _store4(manifold_lower, x, y, low); } // the refining loop of get_manifolds() kernel void cacorrectrgb_refine(read_only image2d_t in, read_only image2d_t higher, read_only image2d_t lower, read_only image2d_t blurred_in, write_only image2d_t manifold_higher, write_only image2d_t manifold_lower, const int width, const int height, const int guide) { const int x = get_global_id(0); const int y = get_global_id(1); if(x >= width || y >= height) return; float pin[4], high[4], low[4], avg[4]; _load4(in, x, y, pin); _load4(higher, x, y, high); _load4(lower, x, y, low); _load4(blurred_in, x, y, avg); const float pixelg = dtcl_log2(fmax(pin[guide], 1E-6f)); const float highg = dtcl_log2(fmax(high[guide], 1E-6f)); const float lowg = dtcl_log2(fmax(low[guide], 1E-6f)); const float avgg = dtcl_log2(fmax(avg[guide], 1E-6f)); float w = 1.0f; for(int kc = 0; kc <= 1; kc++) { const int c = (guide + kc + 1) % 3; const float pixel = dtcl_log2(fmax(pin[c], 1E-6f)); const float highc = dtcl_log2(fmax(high[c], 1E-6f)); const float lowc = dtcl_log2(fmax(low[c], 1E-6f)); const float dist_to_ll = fabs(pixelg - lowg - pixel + lowc); const float dist_to_hh = fabs(pixelg - highg - pixel + highc); const float dist_to_lh = fabs((pixelg - pixel) - (highg - lowc)); const float dist_to_hl = fabs((pixelg - pixel) - (lowg - highc)); const int near_low = fabs(pixelg - lowg) < fabs(pixelg - highg); const float dist_to_good = near_low ? dist_to_ll : dist_to_hh; const float dist_to_bad = near_low ? dist_to_hl : dist_to_lh; w *= 1.0f * (0.2f + 1.0f / fmax(dist_to_good, 0.1f)) / (0.2f + 1.0f / fmax(dist_to_bad, 0.1f)); } float logdiffs[2]; for(int kc = 0; kc <= 1; kc++) { const int c = (guide + kc + 1) % 3; logdiffs[kc] = dtcl_log2(fmax(pin[c], 1E-6f)) - pixelg; } const float maxlogdiff = fmax(fabs(logdiffs[0]), fabs(logdiffs[1])); if(maxlogdiff > CACORRECT_MAX_EV_DIFF) w *= CACORRECT_MAX_EV_DIFF / maxlogdiff; // the pixel goes to one manifold, the other gets zero float m[4]; for(int kc = 0; kc <= 1; kc++) { const int c = (kc + guide + 1) % 3; m[c] = logdiffs[kc] * w; } m[guide] = fmax(pin[guide], 0.0f) * w; m[3] = w; const float zero[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; if(pixelg > avgg) { _store4(manifold_higher, x, y, m); _store4(manifold_lower, x, y, zero); } else { _store4(manifold_higher, x, y, zero); _store4(manifold_lower, x, y, m); } } // apply_correction(), with the manifolds upscaled to the full size kernel void cacorrectrgb_apply(read_only image2d_t in, read_only image2d_t higher, read_only image2d_t lower, write_only image2d_t out, const int width, const int height, const int guide, const int mode) { const int x = get_global_id(0); const int y = get_global_id(1); if(x >= width || y >= height) return; float pin[4], high[4], low[4]; _load4(in, x, y, pin); _load4(higher, x, y, high); _load4(lower, x, y, low); const float high_guide = fmax(high[guide], 1E-6f); const float low_guide = fmax(low[guide], 1E-6f); const float log_high = dtcl_log2(high_guide); const float log_low = dtcl_log2(low_guide); const float dist_low_high = log_high - log_low; const float pixelg = fmax(pin[guide], 0.0f); const float log_pixg = dtcl_log2(fmin(fmax(pixelg, low_guide), high_guide)); float weight_low = fabs(log_high - log_pixg) / fmax(dist_low_high, 1E-6f); const float threshold_dist_low_high = 0.25f; if(dist_low_high < threshold_dist_low_high) { const float weight = dist_low_high / threshold_dist_low_high; weight_low = weight_low * weight + 0.5f * (1.0f - weight); } const float weight_high = fmax(1.0f - weight_low, 0.0f); float o[4]; for(int kc = 0; kc <= 1; kc++) { const int c = (guide + kc + 1) % 3; const float pixelc = fmax(pin[c], 0.0f); const float ratio_high_manifolds = high[c] / high_guide; const float ratio_low_manifolds = low[c] / low_guide; const float ratio = dtcl_pow(ratio_low_manifolds, weight_low) * dtcl_pow(ratio_high_manifolds, weight_high); const float outp = pixelg * ratio; o[c] = mode == 1 ? fmin(outp, pixelc) : mode == 2 ? fmax(outp, pixelc) : outp; } o[guide] = pixelg; o[3] = pin[3]; _store4(out, x, y, o); } // the first loop of reduce_artifacts(): the two guided channels of in and out kernel void cacorrectrgb_artifacts_prepare(read_only image2d_t in, read_only image2d_t corrected, write_only image2d_t in_out, const int width, const int height, const int guide) { const int x = get_global_id(0); const int y = get_global_id(1); if(x >= width || y >= height) return; float pin[4], pout[4], o[4]; _load4(in, x, y, pin); _load4(corrected, x, y, pout); for(int kc = 0; kc <= 1; kc++) { const int c = (guide + kc + 1) % 3; o[kc * 2 + 0] = pin[c]; o[kc * 2 + 1] = pout[c]; } _store4(in_out, x, y, o); } // the second loop of reduce_artifacts() kernel void cacorrectrgb_artifacts(read_only image2d_t in, read_only image2d_t corrected, read_only image2d_t blurred_in_out, write_only image2d_t out, const int width, const int height, const int guide, const float safety) { const int x = get_global_id(0); const int y = get_global_id(1); if(x >= width || y >= height) return; float pin[4], o[4], avg[4]; _load4(in, x, y, pin); _load4(corrected, x, y, o); _load4(blurred_in_out, x, y, avg); float w = 1.0f; for(int kc = 0; kc <= 1; kc++) { const float avg_in = dtcl_log2(fmax(avg[kc * 2 + 0], 1E-6f)); const float avg_out = dtcl_log2(fmax(avg[kc * 2 + 1], 1E-6f)); w *= dtcl_exp(-fmax(fabs(avg_out - avg_in), 0.01f) * safety); } for(int kc = 0; kc <= 1; kc++) { const int c = (guide + kc + 1) % 3; o[c] = fmax(1.0f - w, 0.0f) * fmax(pin[c], 0.0f) + w * fmax(o[c], 0.0f); } _store4(out, x, y, o); }