Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
lch.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Bruce Guenter.
4 Copyright (C) 2010-2011 Henrik Andersson.
5 Copyright (C) 2010-2014, 2016 johannes hanika.
6 Copyright (C) 2010 Stuart Henderson.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Robert Bieber.
9 Copyright (C) 2011-2014, 2016, 2019 Tobias Ellinghaus.
10 Copyright (C) 2011-2012, 2014, 2016-2017 Ulrich Pegelow.
11 Copyright (C) 2012, 2015 Edouard Gomez.
12 Copyright (C) 2012 Jérémy Rosen.
13 Copyright (C) 2012 Richard Wonka.
14 Copyright (C) 2013, 2020 Aldric Renaudin.
15 Copyright (C) 2014, 2016 Dan Torop.
16 Copyright (C) 2014-2016 Roman Lebedev.
17 Copyright (C) 2015-2016 Pedro Côrte-Real.
18 Copyright (C) 2017 Heiko Bauke.
19 Copyright (C) 2017 luzpaz.
20 Copyright (C) 2018, 2020-2026 Aurélien PIERRE.
21 Copyright (C) 2018 Edgardo Hoszowski.
22 Copyright (C) 2018 Maurizio Paglia.
23 Copyright (C) 2018-2020, 2022 Pascal Obry.
24 Copyright (C) 2018 rawfiner.
25 Copyright (C) 2019 Andreas Schneider.
26 Copyright (C) 2019 Diederik ter Rahe.
27 Copyright (C) 2019-2020, 2022 Hanno Schwalm.
28 Copyright (C) 2020 Chris Elston.
29 Copyright (C) 2020, 2022 Diederik Ter Rahe.
30 Copyright (C) 2020-2021 Ralf Brown.
31 Copyright (C) 2021 Hubert Kowalski.
32 Copyright (C) 2022 Martin Bařinka.
33 Copyright (C) 2022 Philipp Lutz.
34 Copyright (C) 2022 Victor Forsiuk.
35 Copyright (C) 2023 Alynx Zhou.
36 Copyright (C) 2023 Guillaume Stutin.
37 Copyright (C) 2023 Luca Zulberti.
38
39 darktable is free software: you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation, either version 3 of the License, or
42 (at your option) any later version.
43
44 darktable is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
48
49 You should have received a copy of the GNU General Public License
50 along with darktable. If not, see <http://www.gnu.org/licenses/>.
51 */
52
53// LCh highlight reconstruction (Bayer + X-Trans), with its colour interpolation helpers. (implementation; see
54// lch.h for the public API.)
55
56#include "system/openmp.h"
58#include "system/simd.h"
61#include "iop/highlights/lch.h"
62#include <math.h>
63#include <string.h>
64
65/* interpolate value for a pixel, ideal via ratio to nearby pixel */
66static inline float interp_pix_xtrans(const int ratio_next, const ssize_t offset_next, const float clip0,
67 const float clip_next, const float *const in, const float *const ratios)
68{
69 assert(ratio_next != 0);
70 // it's OK to exceed clipping of current pixel's color based on a
71 // neighbor -- that is the purpose of interpolating highlight
72 // colors
73 const float clip_val = fmaxf(clip0, clip_next);
74 if(in[offset_next] >= clip_next - 1e-5f)
75 {
76 // next pixel is also clipped
77 return clip_val;
78 }
79 else
80 {
81 // set this pixel in ratio to the next
82 assert(ratio_next != 0);
83 if(ratio_next > 0)
84 return fminf(in[offset_next] / ratios[ratio_next], clip_val);
85 else
86 return fminf(in[offset_next] * ratios[-ratio_next], clip_val);
87 }
88}
89
90void interpolate_color_xtrans(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
91 const dt_iop_roi_t *const roi_out, int dim, int dir, int other,
92 const float *const clip, const uint8_t (*const xtrans)[6], const int pass)
93{
94 // In Bayer each row/col has only green/red or green/blue
95 // transitions, hence can reconstruct color by single ratio per
96 // row. In x-trans there can be transitions between arbitrary colors
97 // in a row/col (and 2x2 green blocks which provide no color
98 // transition information). Hence calculate multiple color ratios
99 // for each row/col.
100
101 // Lookup for color ratios, e.g. red -> blue is roff[0][2] and blue
102 // -> red is roff[2][0]. Returned value is an index into ratios. If
103 // negative, then need to invert the ratio. Identity color
104 // transitions aren't used.
105 const int roff[3][3] = { { 0, -1, -2 }, { 1, 0, -3 }, { 2, 3, 0 } };
106 // record ratios of color transitions 0:unused, 1:RG, 2:RB, and 3:GB
107 dt_aligned_pixel_t ratios = { 1.0f, 1.0f, 1.0f, 1.0f };
108
109 // passes are 0:+x, 1:-x, 2:+y, 3:-y
110 // dims are 0:traverse a row, 1:traverse a column
111 // dir is 1:left to right, -1: right to left
112 int i = (dim == 0) ? 0 : other;
113 int j = (dim == 0) ? other : 0;
114 const ssize_t offs = (ssize_t)(dim ? roi_out->width : 1) * ((dir < 0) ? -1 : 1);
115 const ssize_t offl = offs - (dim ? 1 : roi_out->width);
116 const ssize_t offr = offs + (dim ? 1 : roi_out->width);
117 int beg, end;
118 if(dir == 1)
119 {
120 beg = 0;
121 end = (dim == 0) ? roi_out->width : roi_out->height;
122 }
123 else
124 {
125 beg = ((dim == 0) ? roi_out->width : roi_out->height) - 1;
126 end = -1;
127 }
128
129 float *in, *out;
130 if(dim == 1)
131 {
132 out = (float *)ovoid + (size_t)i + (size_t)beg * roi_out->width;
133 in = (float *)ivoid + (size_t)i + (size_t)beg * roi_in->width;
134 }
135 else
136 {
137 out = (float *)ovoid + (size_t)beg + (size_t)j * roi_out->width;
138 in = (float *)ivoid + (size_t)beg + (size_t)j * roi_in->width;
139 }
140
141 for(int k = beg; k != end; k += dir)
142 {
143 if(dim == 1)
144 j = k;
145 else
146 i = k;
147
148 const uint8_t f0 = FCxtrans(j, i, roi_in, xtrans);
149 const uint8_t f1 = FCxtrans(dim ? (j + dir) : j, dim ? i : (i + dir), roi_in, xtrans);
150 const uint8_t fl = FCxtrans(dim ? (j + dir) : (j - 1), dim ? (i - 1) : (i + dir), roi_in, xtrans);
151 const uint8_t fr = FCxtrans(dim ? (j + dir) : (j + 1), dim ? (i + 1) : (i + dir), roi_in, xtrans);
152 const float clip0 = clip[f0];
153 const float clip1 = clip[f1];
154 const float clipl = clip[fl];
155 const float clipr = clip[fr];
156 const float clip_max = fmaxf(fmaxf(clip[0], clip[1]), clip[2]);
157
158 if(i == 0 || i == roi_out->width - 1 || j == 0 || j == roi_out->height - 1)
159 {
160 if(pass == 3) out[0] = fminf(clip_max, in[0]);
161 }
162 else
163 {
164 // ratio to next pixel if this & next are unclamped and not in
165 // 2x2 green block
166 if((f0 != f1) && (in[0] < clip0 && in[0] > 1e-5f) && (in[offs] < clip1 && in[offs] > 1e-5f))
167 {
168 const int r = roff[f0][f1];
169 assert(r != 0);
170 if(r > 0)
171 ratios[r] = (3.f * ratios[r] + (in[offs] / in[0])) / 4.f;
172 else
173 ratios[-r] = (3.f * ratios[-r] + (in[0] / in[offs])) / 4.f;
174 }
175
176 if(in[0] >= clip0 - 1e-5f)
177 {
178 // interplate color for clipped pixel
179 float add;
180 if(f0 != f1)
181 // next pixel is different color
182 add = interp_pix_xtrans(roff[f0][f1], offs, clip0, clip1, in, ratios);
183 else
184 // at start of 2x2 green block, look diagonally
185 add = (fl != f0) ? interp_pix_xtrans(roff[f0][fl], offl, clip0, clipl, in, ratios)
186 : interp_pix_xtrans(roff[f0][fr], offr, clip0, clipr, in, ratios);
187
188 if(pass == 0)
189 out[0] = add;
190 else if(pass == 3)
191 out[0] = fminf(clip_max, (out[0] + add) / 4.0f);
192 else
193 out[0] += add;
194 }
195 else
196 {
197 // pixel is not clipped
198 if(pass == 3) out[0] = in[0];
199 }
200 }
201 out += offs;
202 in += offs;
203 }
204}
205
206void interpolate_color(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_out, int dim,
207 int dir, int other, const float *clip, const uint32_t filters, const int pass)
208{
209 float ratio = 1.0f;
210 float *in, *out;
211
212 int i = 0, j = 0;
213 if(dim == 0)
214 j = other;
215 else
216 i = other;
217 ssize_t offs = dim ? roi_out->width : 1;
218 if(dir < 0) offs = -offs;
219 int beg, end;
220 if(dim == 0 && dir == 1)
221 {
222 beg = 0;
223 end = roi_out->width;
224 }
225 else if(dim == 0 && dir == -1)
226 {
227 beg = roi_out->width - 1;
228 end = -1;
229 }
230 else if(dim == 1 && dir == 1)
231 {
232 beg = 0;
233 end = roi_out->height;
234 }
235 else if(dim == 1 && dir == -1)
236 {
237 beg = roi_out->height - 1;
238 end = -1;
239 }
240 else
241 return;
242
243 if(dim == 1)
244 {
245 out = (float *)ovoid + i + (size_t)beg * roi_out->width;
246 in = (float *)ivoid + i + (size_t)beg * roi_out->width;
247 }
248 else
249 {
250 out = (float *)ovoid + beg + (size_t)j * roi_out->width;
251 in = (float *)ivoid + beg + (size_t)j * roi_out->width;
252 }
253 for(int k = beg; k != end; k += dir)
254 {
255 if(dim == 1)
256 j = k;
257 else
258 i = k;
259 const float clip0 = clip[FC(j, i, filters)];
260 const float clip1 = clip[FC(dim ? (j + 1) : j, dim ? i : (i + 1), filters)];
261 if(i == 0 || i == roi_out->width - 1 || j == 0 || j == roi_out->height - 1)
262 {
263 if(pass == 3) out[0] = in[0];
264 }
265 else
266 {
267 if(in[0] < clip0 && in[0] > 1e-5f)
268 { // both are not clipped
269 if(in[offs] < clip1 && in[offs] > 1e-5f)
270 { // update ratio, exponential decay. ratio = in[odd]/in[even]
271 if(k & 1)
272 ratio = (3.0f * ratio + in[0] / in[offs]) / 4.0f;
273 else
274 ratio = (3.0f * ratio + in[offs] / in[0]) / 4.0f;
275 }
276 }
277
278 if(in[0] >= clip0 - 1e-5f)
279 { // in[0] is clipped, restore it as in[1] adjusted according to ratio
280 float add = 0.0f;
281 if(in[offs] >= clip1 - 1e-5f)
282 add = fmaxf(clip0, clip1);
283 else if(k & 1)
284 add = in[offs] * ratio;
285 else
286 add = in[offs] / ratio;
287
288 if(pass == 0)
289 out[0] = add;
290 else if(pass == 3)
291 out[0] = (out[0] + add) / 4.0f;
292 else
293 out[0] += add;
294 }
295 else
296 {
297 if(pass == 3) out[0] = in[0];
298 }
299 }
300 out += offs;
301 in += offs;
302 }
303}
304
305/*
306 * these 2 constants were computed using following Sage code:
307 *
308 * sqrt3 = sqrt(3)
309 * sqrt12 = sqrt(12) # 2*sqrt(3)
310 *
311 * print 'sqrt3 = ', sqrt3, ' ~= ', RealField(128)(sqrt3)
312 * print 'sqrt12 = ', sqrt12, ' ~= ', RealField(128)(sqrt12)
313 */
314
316void process_lch_bayer(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
317 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
318 const float clip)
319{
320 const uint32_t filters = piece->dsc_in.filters;
321 __OMP_PARALLEL_FOR__(collapse(2))
322 for(int j = 0; j < roi_out->height; j++)
323 {
324 for(int i = 0; i < roi_out->width; i++)
325 {
326 float *const out = (float *)ovoid + (size_t)roi_out->width * j + i;
327 const float *const in = (float *)ivoid + (size_t)roi_out->width * j + i;
328
329 if(i == roi_out->width - 1 || j == roi_out->height - 1)
330 {
331 // fast path for border
332 out[0] = MIN(clip, in[0]);
333 }
334 else
335 {
336 int clipped = 0;
337
338 // sample 1 bayer block. thus we will have 2 green values.
339 float R = 0.0f, Gmin = FLT_MAX, Gmax = -FLT_MAX, B = 0.0f;
340 for(int jj = 0; jj <= 1; jj++)
341 {
342 for(int ii = 0; ii <= 1; ii++)
343 {
344 const float val = in[(size_t)jj * roi_out->width + ii];
345
346 clipped = (clipped || (val > clip));
347
348 const int c = FC(j + jj + roi_out->y, i + ii + roi_out->x, filters);
349 switch(c)
350 {
351 case 0:
352 R = val;
353 break;
354 case 1:
355 Gmin = MIN(Gmin, val);
356 Gmax = MAX(Gmax, val);
357 break;
358 case 2:
359 B = val;
360 break;
361 }
362 }
363 }
364
365 if(clipped)
366 {
367 const float Ro = MIN(R, clip);
368 const float Go = MIN(Gmin, clip);
369 const float Bo = MIN(B, clip);
370
371 const float L = (R + Gmax + B) / 3.0f;
372
373 float C = SQRT3 * (R - Gmax);
374 float H = 2.0f * B - Gmax - R;
375
376 const float Co = SQRT3 * (Ro - Go);
377 const float Ho = 2.0f * Bo - Go - Ro;
378
379 if(R != Gmax && Gmax != B)
380 {
381 const float ratio = sqrtf((Co * Co + Ho * Ho) / (C * C + H * H));
382 C *= ratio;
383 H *= ratio;
384 }
385
386 dt_aligned_pixel_t RGB = { 0.0f, 0.0f, 0.0f };
387
388 /*
389 * backtransform proof, sage:
390 *
391 * R,G,B,L,C,H = var('R,G,B,L,C,H')
392 * solve([L==(R+G+B)/3, C==sqrt(3)*(R-G), H==2*B-G-R], R, G, B)
393 *
394 * result:
395 * [[R == 1/6*sqrt(3)*C - 1/6*H + L, G == -1/6*sqrt(3)*C - 1/6*H + L, B == 1/3*H + L]]
396 */
397 RGB[0] = L - H / 6.0f + C / SQRT12;
398 RGB[1] = L - H / 6.0f - C / SQRT12;
399 RGB[2] = L + H / 3.0f;
400
401 out[0] = RGB[FC(j + roi_out->y, i + roi_out->x, filters)];
402 }
403 else
404 {
405 out[0] = in[0];
406 }
407 }
408 }
409 }
410}
411
413void process_lch_xtrans(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
414 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
415 const float clip)
416{
417 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
419 for(int j = 0; j < roi_out->height; j++)
420 {
421 float *out = (float *)ovoid + (size_t)roi_out->width * j;
422 float *in = (float *)ivoid + (size_t)roi_in->width * j;
423
424 // bit vector used as ring buffer to remember clipping of current
425 // and last two columns, checking current pixel and its vertical
426 // neighbors
427 int cl = 0;
428
429 for(int i = 0; i < roi_out->width; i++)
430 {
431 // update clipping ring buffer
432 cl = (cl << 1) & 6;
433 if(j >= 2 && j <= roi_out->height - 3)
434 {
435 cl |= (in[-roi_in->width] > clip) | (in[0] > clip) | (in[roi_in->width] > clip);
436 }
437
438 if(i < 2 || i > roi_out->width - 3 || j < 2 || j > roi_out->height - 3)
439 {
440 // fast path for border
441 out[0] = MIN(clip, in[0]);
442 }
443 else
444 {
445 // if current pixel is clipped, always reconstruct
446 int clipped = (in[0] > clip);
447 if(!clipped)
448 {
449 clipped = cl;
450 if(clipped)
451 {
452 // If the ring buffer can't show we are in an obviously
453 // unclipped region, this is the slow case: check if there
454 // is any 3x3 block touching the current pixel which has
455 // no clipping, as then don't need to reconstruct the
456 // current pixel. This avoids zippering in edge
457 // transitions from clipped to unclipped areas. The
458 // X-Trans sensor seems prone to this, unlike Bayer, due
459 // to its irregular pattern.
460 for(int offset_j = -2; offset_j <= 0; offset_j++)
461 {
462 for(int offset_i = -2; offset_i <= 0; offset_i++)
463 {
464 if(clipped)
465 {
466 clipped = 0;
467 for(int jj = offset_j; jj <= offset_j + 2; jj++)
468 {
469 for(int ii = offset_i; ii <= offset_i + 2; ii++)
470 {
471 const float val = in[(ssize_t)jj * roi_in->width + ii];
472 clipped = (clipped || (val > clip));
473 }
474 }
475 }
476 }
477 }
478 }
479 }
480
481 if(clipped)
482 {
483 dt_aligned_pixel_t mean = { 0.0f, 0.0f, 0.0f };
484 dt_aligned_pixel_t RGBmax = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
485 int cnt[3] = { 0, 0, 0 };
486
487 for(int jj = -1; jj <= 1; jj++)
488 {
489 for(int ii = -1; ii <= 1; ii++)
490 {
491 const float val = in[(ssize_t)jj * roi_in->width + ii];
492 const int c = FCxtrans(j + jj, i + ii, roi_in, xtrans);
493 mean[c] += val;
494 cnt[c]++;
495 RGBmax[c] = MAX(RGBmax[c], val);
496 }
497 }
498
499 const float Ro = MIN(mean[0] / cnt[0], clip);
500 const float Go = MIN(mean[1] / cnt[1], clip);
501 const float Bo = MIN(mean[2] / cnt[2], clip);
502
503 const float R = RGBmax[0];
504 const float G = RGBmax[1];
505 const float B = RGBmax[2];
506
507 const float L = (R + G + B) / 3.0f;
508
509 float C = SQRT3 * (R - G);
510 float H = 2.0f * B - G - R;
511
512 const float Co = SQRT3 * (Ro - Go);
513 const float Ho = 2.0f * Bo - Go - Ro;
514
515 if(R != G && G != B)
516 {
517 const float ratio = sqrtf((Co * Co + Ho * Ho) / (C * C + H * H));
518 C *= ratio;
519 H *= ratio;
520 }
521
522 dt_aligned_pixel_t RGB = { 0.0f, 0.0f, 0.0f };
523
524 RGB[0] = L - H / 6.0f + C / SQRT12;
525 RGB[1] = L - H / 6.0f - C / SQRT12;
526 RGB[2] = L + H / 3.0f;
527
528 out[0] = RGB[FCxtrans(j, i, roi_out, xtrans)];
529 }
530 else
531 out[0] = in[0];
532 }
533 out++;
534 in++;
535 }
536 }
537}
#define B(y, x)
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
static dt_aligned_pixel_t RGB
static int FCxtrans(const int row, const int col, global const unsigned char(*const xtrans)[6])
static int FC(const int row, const int col, const unsigned int filters)
#define H
Definition diffuse.c:614
void *const ovoid
__DT_CLONE_TARGETS__ void process_lch_bayer(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const float clip)
Definition lch.c:316
void interpolate_color(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_out, int dim, int dir, int other, const float *clip, const uint32_t filters, const int pass)
Definition lch.c:206
__DT_CLONE_TARGETS__ void process_lch_xtrans(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const float clip)
Definition lch.c:413
static float interp_pix_xtrans(const int ratio_next, const ssize_t offset_next, const float clip0, const float clip_next, const float *const in, const float *const ratios)
Definition lch.c:66
void interpolate_color_xtrans(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, int dim, int dir, int other, const float *const clip, const uint8_t(*const xtrans)[6], const int pass)
Definition lch.c:90
float *const restrict const size_t k
#define R
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define SQRT12
#define SQRT3
const float r
dt_iop_buffer_dsc_t dsc_in
uint32_t filters
Definition format.h:89
uint8_t xtrans[6][6]
Definition format.h:99
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29