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 "common/darktable.h"
58#include "iop/highlights/lch.h"
59#include <math.h>
60#include <string.h>
61
62/* interpolate value for a pixel, ideal via ratio to nearby pixel */
63static inline float interp_pix_xtrans(const int ratio_next, const ssize_t offset_next, const float clip0,
64 const float clip_next, const float *const in, const float *const ratios)
65{
66 assert(ratio_next != 0);
67 // it's OK to exceed clipping of current pixel's color based on a
68 // neighbor -- that is the purpose of interpolating highlight
69 // colors
70 const float clip_val = fmaxf(clip0, clip_next);
71 if(in[offset_next] >= clip_next - 1e-5f)
72 {
73 // next pixel is also clipped
74 return clip_val;
75 }
76 else
77 {
78 // set this pixel in ratio to the next
79 assert(ratio_next != 0);
80 if(ratio_next > 0)
81 return fminf(in[offset_next] / ratios[ratio_next], clip_val);
82 else
83 return fminf(in[offset_next] * ratios[-ratio_next], clip_val);
84 }
85}
86
87void interpolate_color_xtrans(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
88 const dt_iop_roi_t *const roi_out, int dim, int dir, int other,
89 const float *const clip, const uint8_t (*const xtrans)[6], const int pass)
90{
91 // In Bayer each row/col has only green/red or green/blue
92 // transitions, hence can reconstruct color by single ratio per
93 // row. In x-trans there can be transitions between arbitrary colors
94 // in a row/col (and 2x2 green blocks which provide no color
95 // transition information). Hence calculate multiple color ratios
96 // for each row/col.
97
98 // Lookup for color ratios, e.g. red -> blue is roff[0][2] and blue
99 // -> red is roff[2][0]. Returned value is an index into ratios. If
100 // negative, then need to invert the ratio. Identity color
101 // transitions aren't used.
102 const int roff[3][3] = { { 0, -1, -2 }, { 1, 0, -3 }, { 2, 3, 0 } };
103 // record ratios of color transitions 0:unused, 1:RG, 2:RB, and 3:GB
104 dt_aligned_pixel_t ratios = { 1.0f, 1.0f, 1.0f, 1.0f };
105
106 // passes are 0:+x, 1:-x, 2:+y, 3:-y
107 // dims are 0:traverse a row, 1:traverse a column
108 // dir is 1:left to right, -1: right to left
109 int i = (dim == 0) ? 0 : other;
110 int j = (dim == 0) ? other : 0;
111 const ssize_t offs = (ssize_t)(dim ? roi_out->width : 1) * ((dir < 0) ? -1 : 1);
112 const ssize_t offl = offs - (dim ? 1 : roi_out->width);
113 const ssize_t offr = offs + (dim ? 1 : roi_out->width);
114 int beg, end;
115 if(dir == 1)
116 {
117 beg = 0;
118 end = (dim == 0) ? roi_out->width : roi_out->height;
119 }
120 else
121 {
122 beg = ((dim == 0) ? roi_out->width : roi_out->height) - 1;
123 end = -1;
124 }
125
126 float *in, *out;
127 if(dim == 1)
128 {
129 out = (float *)ovoid + (size_t)i + (size_t)beg * roi_out->width;
130 in = (float *)ivoid + (size_t)i + (size_t)beg * roi_in->width;
131 }
132 else
133 {
134 out = (float *)ovoid + (size_t)beg + (size_t)j * roi_out->width;
135 in = (float *)ivoid + (size_t)beg + (size_t)j * roi_in->width;
136 }
137
138 for(int k = beg; k != end; k += dir)
139 {
140 if(dim == 1)
141 j = k;
142 else
143 i = k;
144
145 const uint8_t f0 = FCxtrans(j, i, roi_in, xtrans);
146 const uint8_t f1 = FCxtrans(dim ? (j + dir) : j, dim ? i : (i + dir), roi_in, xtrans);
147 const uint8_t fl = FCxtrans(dim ? (j + dir) : (j - 1), dim ? (i - 1) : (i + dir), roi_in, xtrans);
148 const uint8_t fr = FCxtrans(dim ? (j + dir) : (j + 1), dim ? (i + 1) : (i + dir), roi_in, xtrans);
149 const float clip0 = clip[f0];
150 const float clip1 = clip[f1];
151 const float clipl = clip[fl];
152 const float clipr = clip[fr];
153 const float clip_max = fmaxf(fmaxf(clip[0], clip[1]), clip[2]);
154
155 if(i == 0 || i == roi_out->width - 1 || j == 0 || j == roi_out->height - 1)
156 {
157 if(pass == 3) out[0] = fminf(clip_max, in[0]);
158 }
159 else
160 {
161 // ratio to next pixel if this & next are unclamped and not in
162 // 2x2 green block
163 if((f0 != f1) && (in[0] < clip0 && in[0] > 1e-5f) && (in[offs] < clip1 && in[offs] > 1e-5f))
164 {
165 const int r = roff[f0][f1];
166 assert(r != 0);
167 if(r > 0)
168 ratios[r] = (3.f * ratios[r] + (in[offs] / in[0])) / 4.f;
169 else
170 ratios[-r] = (3.f * ratios[-r] + (in[0] / in[offs])) / 4.f;
171 }
172
173 if(in[0] >= clip0 - 1e-5f)
174 {
175 // interplate color for clipped pixel
176 float add;
177 if(f0 != f1)
178 // next pixel is different color
179 add = interp_pix_xtrans(roff[f0][f1], offs, clip0, clip1, in, ratios);
180 else
181 // at start of 2x2 green block, look diagonally
182 add = (fl != f0) ? interp_pix_xtrans(roff[f0][fl], offl, clip0, clipl, in, ratios)
183 : interp_pix_xtrans(roff[f0][fr], offr, clip0, clipr, in, ratios);
184
185 if(pass == 0)
186 out[0] = add;
187 else if(pass == 3)
188 out[0] = fminf(clip_max, (out[0] + add) / 4.0f);
189 else
190 out[0] += add;
191 }
192 else
193 {
194 // pixel is not clipped
195 if(pass == 3) out[0] = in[0];
196 }
197 }
198 out += offs;
199 in += offs;
200 }
201}
202
203void interpolate_color(const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_out, int dim,
204 int dir, int other, const float *clip, const uint32_t filters, const int pass)
205{
206 float ratio = 1.0f;
207 float *in, *out;
208
209 int i = 0, j = 0;
210 if(dim == 0)
211 j = other;
212 else
213 i = other;
214 ssize_t offs = dim ? roi_out->width : 1;
215 if(dir < 0) offs = -offs;
216 int beg, end;
217 if(dim == 0 && dir == 1)
218 {
219 beg = 0;
220 end = roi_out->width;
221 }
222 else if(dim == 0 && dir == -1)
223 {
224 beg = roi_out->width - 1;
225 end = -1;
226 }
227 else if(dim == 1 && dir == 1)
228 {
229 beg = 0;
230 end = roi_out->height;
231 }
232 else if(dim == 1 && dir == -1)
233 {
234 beg = roi_out->height - 1;
235 end = -1;
236 }
237 else
238 return;
239
240 if(dim == 1)
241 {
242 out = (float *)ovoid + i + (size_t)beg * roi_out->width;
243 in = (float *)ivoid + i + (size_t)beg * roi_out->width;
244 }
245 else
246 {
247 out = (float *)ovoid + beg + (size_t)j * roi_out->width;
248 in = (float *)ivoid + beg + (size_t)j * roi_out->width;
249 }
250 for(int k = beg; k != end; k += dir)
251 {
252 if(dim == 1)
253 j = k;
254 else
255 i = k;
256 const float clip0 = clip[FC(j, i, filters)];
257 const float clip1 = clip[FC(dim ? (j + 1) : j, dim ? i : (i + 1), filters)];
258 if(i == 0 || i == roi_out->width - 1 || j == 0 || j == roi_out->height - 1)
259 {
260 if(pass == 3) out[0] = in[0];
261 }
262 else
263 {
264 if(in[0] < clip0 && in[0] > 1e-5f)
265 { // both are not clipped
266 if(in[offs] < clip1 && in[offs] > 1e-5f)
267 { // update ratio, exponential decay. ratio = in[odd]/in[even]
268 if(k & 1)
269 ratio = (3.0f * ratio + in[0] / in[offs]) / 4.0f;
270 else
271 ratio = (3.0f * ratio + in[offs] / in[0]) / 4.0f;
272 }
273 }
274
275 if(in[0] >= clip0 - 1e-5f)
276 { // in[0] is clipped, restore it as in[1] adjusted according to ratio
277 float add = 0.0f;
278 if(in[offs] >= clip1 - 1e-5f)
279 add = fmaxf(clip0, clip1);
280 else if(k & 1)
281 add = in[offs] * ratio;
282 else
283 add = in[offs] / ratio;
284
285 if(pass == 0)
286 out[0] = add;
287 else if(pass == 3)
288 out[0] = (out[0] + add) / 4.0f;
289 else
290 out[0] += add;
291 }
292 else
293 {
294 if(pass == 3) out[0] = in[0];
295 }
296 }
297 out += offs;
298 in += offs;
299 }
300}
301
302/*
303 * these 2 constants were computed using following Sage code:
304 *
305 * sqrt3 = sqrt(3)
306 * sqrt12 = sqrt(12) # 2*sqrt(3)
307 *
308 * print 'sqrt3 = ', sqrt3, ' ~= ', RealField(128)(sqrt3)
309 * print 'sqrt12 = ', sqrt12, ' ~= ', RealField(128)(sqrt12)
310 */
311
313void process_lch_bayer(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
314 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
315 const float clip)
316{
317 const uint32_t filters = piece->dsc_in.filters;
318 __OMP_PARALLEL_FOR__(collapse(2))
319 for(int j = 0; j < roi_out->height; j++)
320 {
321 for(int i = 0; i < roi_out->width; i++)
322 {
323 float *const out = (float *)ovoid + (size_t)roi_out->width * j + i;
324 const float *const in = (float *)ivoid + (size_t)roi_out->width * j + i;
325
326 if(i == roi_out->width - 1 || j == roi_out->height - 1)
327 {
328 // fast path for border
329 out[0] = MIN(clip, in[0]);
330 }
331 else
332 {
333 int clipped = 0;
334
335 // sample 1 bayer block. thus we will have 2 green values.
336 float R = 0.0f, Gmin = FLT_MAX, Gmax = -FLT_MAX, B = 0.0f;
337 for(int jj = 0; jj <= 1; jj++)
338 {
339 for(int ii = 0; ii <= 1; ii++)
340 {
341 const float val = in[(size_t)jj * roi_out->width + ii];
342
343 clipped = (clipped || (val > clip));
344
345 const int c = FC(j + jj + roi_out->y, i + ii + roi_out->x, filters);
346 switch(c)
347 {
348 case 0:
349 R = val;
350 break;
351 case 1:
352 Gmin = MIN(Gmin, val);
353 Gmax = MAX(Gmax, val);
354 break;
355 case 2:
356 B = val;
357 break;
358 }
359 }
360 }
361
362 if(clipped)
363 {
364 const float Ro = MIN(R, clip);
365 const float Go = MIN(Gmin, clip);
366 const float Bo = MIN(B, clip);
367
368 const float L = (R + Gmax + B) / 3.0f;
369
370 float C = SQRT3 * (R - Gmax);
371 float H = 2.0f * B - Gmax - R;
372
373 const float Co = SQRT3 * (Ro - Go);
374 const float Ho = 2.0f * Bo - Go - Ro;
375
376 if(R != Gmax && Gmax != B)
377 {
378 const float ratio = sqrtf((Co * Co + Ho * Ho) / (C * C + H * H));
379 C *= ratio;
380 H *= ratio;
381 }
382
383 dt_aligned_pixel_t RGB = { 0.0f, 0.0f, 0.0f };
384
385 /*
386 * backtransform proof, sage:
387 *
388 * R,G,B,L,C,H = var('R,G,B,L,C,H')
389 * solve([L==(R+G+B)/3, C==sqrt(3)*(R-G), H==2*B-G-R], R, G, B)
390 *
391 * result:
392 * [[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]]
393 */
394 RGB[0] = L - H / 6.0f + C / SQRT12;
395 RGB[1] = L - H / 6.0f - C / SQRT12;
396 RGB[2] = L + H / 3.0f;
397
398 out[0] = RGB[FC(j + roi_out->y, i + roi_out->x, filters)];
399 }
400 else
401 {
402 out[0] = in[0];
403 }
404 }
405 }
406 }
407}
408
410void process_lch_xtrans(dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
411 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
412 const float clip)
413{
414 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
416 for(int j = 0; j < roi_out->height; j++)
417 {
418 float *out = (float *)ovoid + (size_t)roi_out->width * j;
419 float *in = (float *)ivoid + (size_t)roi_in->width * j;
420
421 // bit vector used as ring buffer to remember clipping of current
422 // and last two columns, checking current pixel and its vertical
423 // neighbors
424 int cl = 0;
425
426 for(int i = 0; i < roi_out->width; i++)
427 {
428 // update clipping ring buffer
429 cl = (cl << 1) & 6;
430 if(j >= 2 && j <= roi_out->height - 3)
431 {
432 cl |= (in[-roi_in->width] > clip) | (in[0] > clip) | (in[roi_in->width] > clip);
433 }
434
435 if(i < 2 || i > roi_out->width - 3 || j < 2 || j > roi_out->height - 3)
436 {
437 // fast path for border
438 out[0] = MIN(clip, in[0]);
439 }
440 else
441 {
442 // if current pixel is clipped, always reconstruct
443 int clipped = (in[0] > clip);
444 if(!clipped)
445 {
446 clipped = cl;
447 if(clipped)
448 {
449 // If the ring buffer can't show we are in an obviously
450 // unclipped region, this is the slow case: check if there
451 // is any 3x3 block touching the current pixel which has
452 // no clipping, as then don't need to reconstruct the
453 // current pixel. This avoids zippering in edge
454 // transitions from clipped to unclipped areas. The
455 // X-Trans sensor seems prone to this, unlike Bayer, due
456 // to its irregular pattern.
457 for(int offset_j = -2; offset_j <= 0; offset_j++)
458 {
459 for(int offset_i = -2; offset_i <= 0; offset_i++)
460 {
461 if(clipped)
462 {
463 clipped = 0;
464 for(int jj = offset_j; jj <= offset_j + 2; jj++)
465 {
466 for(int ii = offset_i; ii <= offset_i + 2; ii++)
467 {
468 const float val = in[(ssize_t)jj * roi_in->width + ii];
469 clipped = (clipped || (val > clip));
470 }
471 }
472 }
473 }
474 }
475 }
476 }
477
478 if(clipped)
479 {
480 dt_aligned_pixel_t mean = { 0.0f, 0.0f, 0.0f };
481 dt_aligned_pixel_t RGBmax = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
482 int cnt[3] = { 0, 0, 0 };
483
484 for(int jj = -1; jj <= 1; jj++)
485 {
486 for(int ii = -1; ii <= 1; ii++)
487 {
488 const float val = in[(ssize_t)jj * roi_in->width + ii];
489 const int c = FCxtrans(j + jj, i + ii, roi_in, xtrans);
490 mean[c] += val;
491 cnt[c]++;
492 RGBmax[c] = MAX(RGBmax[c], val);
493 }
494 }
495
496 const float Ro = MIN(mean[0] / cnt[0], clip);
497 const float Go = MIN(mean[1] / cnt[1], clip);
498 const float Bo = MIN(mean[2] / cnt[2], clip);
499
500 const float R = RGBmax[0];
501 const float G = RGBmax[1];
502 const float B = RGBmax[2];
503
504 const float L = (R + G + B) / 3.0f;
505
506 float C = SQRT3 * (R - G);
507 float H = 2.0f * B - G - R;
508
509 const float Co = SQRT3 * (Ro - Go);
510 const float Ho = 2.0f * Bo - Go - Ro;
511
512 if(R != G && G != B)
513 {
514 const float ratio = sqrtf((Co * Co + Ho * Ho) / (C * C + H * H));
515 C *= ratio;
516 H *= ratio;
517 }
518
519 dt_aligned_pixel_t RGB = { 0.0f, 0.0f, 0.0f };
520
521 RGB[0] = L - H / 6.0f + C / SQRT12;
522 RGB[1] = L - H / 6.0f - C / SQRT12;
523 RGB[2] = L + H / 3.0f;
524
525 out[0] = RGB[FCxtrans(j, i, roi_out, xtrans)];
526 }
527 else
528 out[0] = in[0];
529 }
530 out++;
531 in++;
532 }
533 }
534}
int height
Definition bilateral.h:1
#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
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
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:613
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:313
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:203
__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:410
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:63
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:87
float *const restrict const size_t k
#define R
float dt_aligned_pixel_t[4]
const float r
#define SQRT12
#define SQRT3
dt_iop_buffer_dsc_t dsc_in
uint32_t filters
Definition format.h:60
uint8_t xtrans[6][6]
Definition format.h:70
Region of interest passed through the pixelpipe.
Definition imageop.h:72
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29