Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
gather.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// Shared CFA gather/remosaic helpers for the highlights module: bilinear interpolation of the
54// raw mosaic into [R,G,B,norm] planes + clip masks, the guided-laplacian normalization channel,
55// and the remosaic back to the CFA. Used by both the guided-laplacian mode (highlights.c) and the
56// harmonic-transposition driver (process.c). (implementation; see gather.h for the public API.)
57
58#include "system/openmp.h"
59#include "system/simd.h"
63#include <math.h>
64#include <string.h>
65
67void _interpolate_and_mask(const float *const restrict input, float *const restrict interpolated,
68 float *const restrict clipping_mask, const dt_aligned_pixel_t clips_in,
69 const dt_aligned_pixel_t det_scale, const dt_aligned_pixel_t white_balance,
70 const uint32_t filters, const size_t width, const size_t height)
71{
72 // Per-channel effective detection thresholds. det_scale = 1 is the plain clip detection;
73 // below 1 it extends the reconstructable set down into the sensor's rolloff band
74 // (the BAND OVERRIDE: the knee can restore the band's level but not the slope the sensor
75 // never recorded, while the colour-line model, anchored on truly linear data below the
76 // band, can -- the measured band value then acts as the per-pixel floor).
78 for_four_channels(c) clips[c] = clips_in[c] * det_scale[c];
79
80 // Step 1 (article "The algorithm"): bilinear demosaic of the raw CFA to a throwaway [R,G,B,norm]
81 // buffer, plus a binary per-channel validity mask keyed on the clip flag v > 0.995*c (here
82 // clips[] already folds in the 0.995 detection factor and the det_scale band override above).
83 // Every channel gets a value at every pixel so the downstream guided fit is a regression, not
84 // an inpainting, problem. Refinements: masks stay binary (0/1), borders mirror (see below).
85 // Bilinear interpolation
86 __OMP_PARALLEL_FOR__(collapse(2))
87 for(size_t i = 0; i < height; i++)
88 for(size_t j = 0; j < width; j++)
89 {
90 const size_t c = FC(i, j, filters);
91 const size_t i_center = i * width;
92 const float center = input[i_center + j];
93
94 float R = 0.f;
95 float G = 0.f;
96 float B = 0.f;
97
98 int R_clipped = 0;
99 int G_clipped = 0;
100 int B_clipped = 0;
101
102 {
103 // Mirrored neighbour indexing on the image border ring: reflection preserves each
104 // neighbour's CFA colour (the Bayer pattern is 2-periodic), so the per-channel
105 // interpolation and clip flags below stay valid on the borders. The previous shortcut
106 // (R = G = B = center, all three clip flags keyed on the centre's own channel)
107 // corrupted the guide planes and produced dashed per-channel masks along the border
108 // ring : fits anchored on them dragged the border-row reconstruction down to the clip
109 // level (a one-pixel V-dip through the raw value at every contour on the border rows).
110 const size_t i_prev = ((i == 0) ? 1 : i - 1) * width;
111 const size_t i_next = ((i == height - 1) ? height - 2 : i + 1) * width;
112 const size_t j_prev = (j == 0) ? 1 : j - 1;
113 const size_t j_next = (j == width - 1) ? width - 2 : j + 1;
114
115 const float north = input[i_prev + j];
116 const float south = input[i_next + j];
117 const float west = input[i_center + j_prev];
118 const float east = input[i_center + j_next];
119
120 const float north_east = input[i_prev + j_next];
121 const float north_west = input[i_prev + j_prev];
122 const float south_east = input[i_next + j_next];
123 const float south_west = input[i_next + j_prev];
124
125 if(c == GREEN) // green pixel
126 {
127 G = center; // channel measured here: pass the raw value through
128 G_clipped = (center > clips[GREEN]); // clip flag: raw > 0.995*c
129 }
130 else // non-green pixel
131 {
132 // interpolate inside an X/Y cross: green sits on the 4 orthogonal neighbours (Bayer),
133 // so G = mean of {N,S,E,W} = equal 1/4 bilinear weights over the valid green support.
134 G = (north + south + east + west) / 4.f;
135 // validity is the OR of the neighbours' clip flags (a channel counts as clipped if ANY
136 // photosite that fed its interpolation was itself clipped).
137 G_clipped = (north > clips[GREEN] || south > clips[GREEN] || east > clips[GREEN] || west > clips[GREEN]);
138 }
139
140 if(c == RED) // red pixel
141 {
142 R = center;
143 R_clipped = (center > clips[RED]);
144 }
145 else // non-red pixel
146 {
147 if(FC(i + 1, j, filters) == RED)
148 {
149 // red neighbours are directly above/below: R = mean of {N,S}, equal 1/2 weights
150 // we are on a red column (FC(i-1) == FC(i+1) on Bayer), interpolate column-wise
151 R = (north + south) / 2.f;
152 R_clipped = (north > clips[RED] || south > clips[RED]); // OR of the 2 contributors
153 }
154 else if(FC(i, j + 1, filters) == RED)
155 {
156 // red neighbours are left/right: R = mean of {W,E}, equal 1/2 weights
157 // we are on a red row, interpolate row-wise
158 R = (west + east) / 2.f;
159 R_clipped = (west > clips[RED] || east > clips[RED]); // OR of the 2 contributors
160 }
161 else
162 {
163 // red neighbours are the 4 diagonal corners: R = mean of the square, equal 1/4 weights
164 // we are on a blue row, so interpolate inside a square
165 R = (north_west + north_east + south_east + south_west) / 4.f;
166 R_clipped = (north_west > clips[RED] || north_east > clips[RED] || south_west > clips[RED]
167 || south_east > clips[RED]); // OR of the 4 contributors
168 }
169 }
170
171 if(c == BLUE) // blue pixel
172 {
173 B = center;
174 B_clipped = (center > clips[BLUE]);
175 }
176 else // non-blue pixel
177 {
178 if(FC(i + 1, j, filters) == BLUE)
179 {
180 // blue neighbours are directly above/below: B = mean of {N,S}, equal 1/2 weights
181 // we are on a blue column (FC(i-1) == FC(i+1) on Bayer), interpolate column-wise
182 B = (north + south) / 2.f;
183 B_clipped = (north > clips[BLUE] || south > clips[BLUE]); // OR of the 2 contributors
184 }
185 else if(FC(i, j + 1, filters) == BLUE)
186 {
187 // blue neighbours are left/right: B = mean of {W,E}, equal 1/2 weights
188 // we are on a blue row, interpolate row-wise
189 B = (west + east) / 2.f;
190 B_clipped = (west > clips[BLUE] || east > clips[BLUE]); // OR of the 2 contributors
191 }
192 else
193 {
194 // blue neighbours are the 4 diagonal corners: B = mean of the square, equal 1/4 weights
195 // we are on a red row, so interpolate inside a square
196 B = (north_west + north_east + south_east + south_west) / 4.f;
197
198 B_clipped = (north_west > clips[BLUE] || north_east > clips[BLUE] || south_west > clips[BLUE]
199 || south_east > clips[BLUE]); // OR of the 4 contributors
200 }
201 }
202 }
203
204 // ALPHA slot carries the magnitude norm = sqrt(R^2 + G^2 + B^2) (Euclidean, as coded);
205 // the any-clip opacity is the OR of the three per-channel validity flags.
206 dt_aligned_pixel_t RGB = { R, G, B, sqrtf(sqf(R) + sqf(G) + sqf(B)) };
207 dt_aligned_pixel_t clipped = { R_clipped, G_clipped, B_clipped, (R_clipped || G_clipped || B_clipped) };
208
209 for_each_channel(k, aligned(RGB, interpolated, clipping_mask, clipped, white_balance))
210 {
211 const size_t idx = (i * width + j) * 4 + k;
212 // Local channel normalization (article "Local channel normalization"): divide each channel
213 // by white_balance[k] = the tile-average of that CFA colour (from _compute_laplacian_
214 // normalization), a crude local white balance so the guide-selection variance is not biased
215 // toward whichever channel carries the largest raw numbers. Clamp >= 0 (raw can dip negative).
216 interpolated[idx] = fmaxf(RGB[k] / white_balance[k], 0.f);
217 clipping_mask[idx] = clipped[k]; // store the binary flag; no feathering here (masks stay hard)
218 }
219 }
220}
221
223void _compute_laplacian_normalization(const float *const restrict input, const dt_iop_roi_t *const roi_in,
224 const uint32_t filters, const uint8_t (*const xtrans)[6],
225 dt_aligned_pixel_t normalization)
226{
227 // Local channel normalization (article "Local channel normalization"): for each CFA colour,
228 // compute its plain average over the whole ROI, sum_c = (1/N) * sum over photosites of colour c.
229 // Note the division by n_pixels here uses the FULL pixel count N (not the per-colour count), so
230 // these factors also carry the CFA fill fraction of each colour -- they are the exact divisors
231 // that _interpolate_and_mask/_remosaic later divide by / multiply back.
232 float sum_R = 0.f;
233 float sum_G = 0.f;
234 float sum_B = 0.f;
235 const float n_pixels = roi_in->height * roi_in->width;
236 if(filters == 0u)
237 {
238 // Non-raw / sRAW: the input is already 4-channel RGB, every pixel carries all three colours
239 // (CFA fill fraction 1), so each channel's normalization is its plain ROI average. The gather
240 // divides by these and the remosaic multiplies back, so the round-trip cancels exactly.
241 __OMP_PARALLEL_FOR__(collapse(2) reduction(+ : sum_R, sum_G, sum_B))
242 for(size_t i = 0; i < roi_in->height; i++)
243 for(size_t j = 0; j < roi_in->width; j++)
244 {
245 const size_t idx = (i * roi_in->width + j) * 4;
246 sum_R += input[idx + RED] / n_pixels;
247 sum_G += input[idx + GREEN] / n_pixels;
248 sum_B += input[idx + BLUE] / n_pixels;
249 }
250 }
251 else
252 {
253 __OMP_PARALLEL_FOR__(collapse(2) reduction(+ : sum_R, sum_G, sum_B))
254 for(size_t i = 0; i < roi_in->height; i++)
255 for(size_t j = 0; j < roi_in->width; j++)
256 {
257 const int c = (filters == 9u) ? FCxtrans((int)i, (int)j, roi_in, xtrans) : FC(i, j, filters);
258 if(c < 0 || c > 2) continue;
259
260 const float value = input[i * roi_in->width + j] / n_pixels; // accumulate value/N into its colour
261 if(c == RED)
262 sum_R += value;
263 else if(c == GREEN)
264 sum_G += value;
265 else
266 sum_B += value;
267 }
268 }
269
270 normalization[RED] = sum_R;
271 normalization[GREEN] = sum_G;
272 normalization[BLUE] = sum_B;
273 normalization[ALPHA] = 1.f; // norm/opacity slot is untouched by the local white balance
274}
275
277void _build_xtrans_bilinear_lookup(int32_t lookup[6][6][32], const dt_iop_roi_t *const roi_in,
278 const uint8_t (*const xtrans)[6])
279{
280 __OMP_PARALLEL_FOR__(collapse(2))
281 for(int row = 0; row < 6; row++)
282 for(int col = 0; col < 6; col++)
283 {
284 int32_t *ip = &(lookup[row][col][1]);
285 int sum[3] = { 0 };
286 const int f = FCxtrans(row, col, roi_in, xtrans);
287
288 // Loop over the local 3x3 support and keep every weighted contributor of
289 // the missing colors visible in the lookup table.
290 for(int y = -1; y <= 1; y++)
291 for(int x = -1; x <= 1; x++)
292 {
293 // Separable bilinear tent weight: 1<<((y==0)+(x==0)) gives 4 on-axis-both (the centre,
294 // excluded below), 2 for an edge neighbour (one axis aligned), 1 for a diagonal corner --
295 // i.e. the {1,2,1}x{1,2,1} kernel of the same bilinear demosaic used on Bayer above.
296 const int weight = 1 << ((y == 0) + (x == 0));
297 const int color = FCxtrans(row + y, col + x, roi_in, xtrans);
298 if(color == f) continue; // skip the centre's own colour: it is passed through as measured
299 *ip++ = (y << 16) | (x & 0xffffu);
300 *ip++ = weight;
301 *ip++ = color;
302 sum[color] += weight;
303 }
304
305 lookup[row][col][0] = (ip - &(lookup[row][col][0])) / 3;
306 for(int c = 0; c < 3; c++)
307 if(c != f)
308 {
309 *ip++ = c;
310 *ip++ = sum[c];
311 }
312 *ip = f;
313 }
314}
315
317void _interpolate_and_mask_xtrans(const float *const restrict input, float *const restrict interpolated,
318 float *const restrict clipping_mask, const dt_aligned_pixel_t clips,
319 const dt_aligned_pixel_t white_balance, const dt_iop_roi_t *const roi_in,
320 const int32_t lookup[6][6][32], const uint8_t (*const xtrans)[6],
321 const size_t width, const size_t height)
322{
323 // Step 1 (article "The algorithm"), X-Trans twin of _interpolate_and_mask: bilinear demosaic to
324 // [R,G,B,norm] + a binary per-channel validity mask keyed on v > 0.995*c. The 6x6 X-Trans phase
325 // is 3x3-periodic in support geometry, resolved via the precomputed lookup for interior pixels.
326 __OMP_PARALLEL_FOR__(collapse(2))
327 for(size_t i = 0; i < height; i++)
328 for(size_t j = 0; j < width; j++)
329 {
330 const size_t idx = i * width + j;
331 const float center = input[idx];
332
333 dt_aligned_pixel_t RGB = { 0.f };
334 dt_aligned_pixel_t clipped = { 0.f };
335
336 if(i == 0 || j == 0 || i == height - 1 || j == width - 1)
337 {
338 dt_aligned_pixel_t sum = { 0.f };
339 int count[3] = { 0 };
340 int used_clipped[3] = { 0 };
341 const int f = FCxtrans((int)i, (int)j, roi_in, xtrans);
342
343 // Along tile borders we average only the available neighbours because
344 // the full 3x3 support would otherwise leave the current ROI.
345 for(int y = MAX((int)i - 1, 0); y <= MIN((int)i + 1, (int)height - 1); y++)
346 for(int x = MAX((int)j - 1, 0); x <= MIN((int)j + 1, (int)width - 1); x++)
347 {
348 const int color = FCxtrans(y, x, roi_in, xtrans);
349 const float value = input[(size_t)y * width + x];
350 sum[color] += value;
351 count[color]++;
352 used_clipped[color] |= (value > clips[color]);
353 }
354
355 for(int c = 0; c < 3; c++)
356 {
357 const int has_samples = (count[c] > 0);
358 // c==f: the measured centre colour passes through. Otherwise plain average over the
359 // available same-colour neighbours (equal weights on the shrunken border support), with
360 // the clip flag = OR of those neighbours' flags (or the centre's own for c==f).
361 RGB[c] = (c == f || !has_samples) ? center : sum[c] / count[c];
362 clipped[c] = (c == f || !has_samples) ? (center > clips[c]) : used_clipped[c];
363 }
364 }
365 else
366 {
367 const int32_t *ip = &(lookup[i % 6][j % 6][0]);
368 dt_aligned_pixel_t sum = { 0.f };
369 int used_clipped[3] = { 0 };
370 const int neighbours = *ip++;
371
372 // We are looping on every neighbour that contributes to a missing color
373 // so the interpolation follows the X-Trans CFA geometry exactly.
374 for(int k = 0; k < neighbours; k++, ip += 3)
375 {
376 const int32_t offset = ip[0];
377 const int x = (int16_t)(offset & 0xffffu);
378 const int y = (int16_t)(offset >> 16);
379 const size_t neighbour = ((size_t)((int)i + y) * width + (size_t)((int)j + x));
380 const int color = ip[2];
381 const float value = input[neighbour];
382 sum[color] += value * ip[1];
383 used_clipped[color] |= (value > clips[color]);
384 }
385
386 // Normalize the two missing colors from the accumulated weights, then
387 // restore the measured center color unchanged.
388 // RGB[color] = (sum of weight*value) / (sum of weights) = weighted bilinear mean.
389 for(int k = 0; k < 2; k++, ip += 2)
390 {
391 const int color = ip[0];
392 const int total = ip[1];
393 RGB[color] = (total > 0) ? sum[color] / total : center; // weighted mean, else fall back
394 clipped[color] = used_clipped[color]; // OR of the contributors' flags
395 }
396
397 const int f = *ip;
398 RGB[f] = center; // centre colour: measured raw value passes through
399 clipped[f] = (center > clips[f]); // clip flag: raw > 0.995*c
400 }
401
402 // ALPHA slot = Euclidean magnitude norm sqrt(R^2+G^2+B^2); opacity = OR of the per-channel flags.
403 // NOTE (article cross-reference): this is the interpolated buffer's shared "norm" channel, and it
404 // is NOT the harmonic method's magnitude L_sum. The 2021 guided-laplacian mode consumes it (its
405 // a-trous ratio/norm split, see wavelets_process LAST_SCALE); the harmonic path only carries it and
406 // never reads it as a magnitude -- forcing it to R+G+B leaves all six ground-truth scenes
407 // bit-identical (verified). The article's L_sum = R+G+B is computed separately in the all-clip core
408 // (lum_accum in _region_guided_filter / the hl_lsb_hole kernel), which already matches the article.
409 RGB[ALPHA] = sqrtf(sqf(RGB[RED]) + sqf(RGB[GREEN]) + sqf(RGB[BLUE]));
410 clipped[ALPHA] = (clipped[RED] || clipped[GREEN] || clipped[BLUE]);
411
412 for_each_channel(k, aligned(RGB, interpolated, clipping_mask, clipped, white_balance))
413 {
414 const size_t index = idx * 4 + k;
415 // Local channel normalization: divide by white_balance[k] = tile-average of that colour;
416 // clamp >= 0. Same crude local white balance as the Bayer path above.
417 interpolated[index] = fmaxf(RGB[k] / white_balance[k], 0.f);
418 clipping_mask[index] = clipped[k]; // binary flag, no feathering (masks stay hard)
419 }
420 }
421}
422
424void _interpolate_and_mask_passthrough(const float *const restrict input, float *const restrict interpolated,
425 float *const restrict clipping_mask, const dt_aligned_pixel_t clips,
426 const dt_aligned_pixel_t white_balance, const size_t width,
427 const size_t height)
428{
429 // Non-raw / sRAW twin of _interpolate_and_mask: the input is already demosaiced 4-channel RGB, so
430 // there is nothing to interpolate. Each channel is passed through (normalized by white_balance, the
431 // tile-average, clamped >= 0) and flagged clipped against clips[]. The ALPHA slot carries the
432 // Euclidean magnitude norm; the opacity is the OR of the three per-channel flags. Masks stay binary.
433 __OMP_PARALLEL_FOR__(collapse(2))
434 for(size_t i = 0; i < height; i++)
435 for(size_t j = 0; j < width; j++)
436 {
437 const size_t idx = (i * width + j) * 4;
438 const float R = input[idx + RED];
439 const float G = input[idx + GREEN];
440 const float B = input[idx + BLUE];
441 const int R_clipped = (R > clips[RED]);
442 const int G_clipped = (G > clips[GREEN]);
443 const int B_clipped = (B > clips[BLUE]);
444
445 const dt_aligned_pixel_t RGB = { R, G, B, sqrtf(sqf(R) + sqf(G) + sqf(B)) };
446 const dt_aligned_pixel_t clipped = { R_clipped, G_clipped, B_clipped, (R_clipped || G_clipped || B_clipped) };
447
448 for_each_channel(k, aligned(RGB, interpolated, clipping_mask, clipped, white_balance))
449 {
450 interpolated[idx + k] = fmaxf(RGB[k] / white_balance[k], 0.f);
451 clipping_mask[idx + k] = clipped[k]; // binary flag; per-channel for R/G/B, any-clip in ALPHA
452 }
453 }
454}
455
457void _remosaic_and_replace(const float *const restrict input, const float *const restrict input_raw,
458 const float *const restrict interpolated, const float *const restrict clipping_mask,
459 float *const restrict output, const dt_aligned_pixel_t white_balance,
460 const dt_aligned_pixel_t clips, const int clip_is_floor, const uint32_t filters,
461 const size_t width, const size_t height)
462{
463 // Remosaic + composite (article "The algorithm", step "remosaic + composite").
464 // Compositing rule: out = opacity*rec + (1 - opacity)*base.
465 // Refinement 2 (clipped raw is a FLOOR): with clip_is_floor set, for a clipped photosite
466 // base = max(raw, rec) instead of raw -- under sensor rolloff the raw reading of a just-detected
467 // photosite sits at the detection threshold, below the true signal, so it is a lower bound, not a
468 // measurement. Feathering the reconstruction toward it printed a V-shaped dip at every contour
469 // down to the biased reading. The 2021 mode keeps the historical blend (flag 0).
470 __OMP_PARALLEL_FOR__(collapse(2))
471 for(size_t i = 0; i < height; i++)
472 for(size_t j = 0; j < width; j++)
473 {
474 const size_t c = FC(i, j, filters);
475 const size_t idx = i * width + j;
476 const size_t index = idx * 4;
477 const float opacity = clipping_mask[index + ALPHA]; // any-clip mask -> blend weight (0 or 1)
478 // Undo the local channel normalization: multiply the reconstructed channel back by its
479 // tile-average white_balance[c] to return to raw scale, clamp >= 0.
480 const float reconstructed = fmaxf(interpolated[index + c] * white_balance[c], 0.f);
481 float base = input[idx];
482 if(clip_is_floor && input_raw[idx] >= clips[c]) base = fmaxf(base, reconstructed); // floor
483 output[idx] = opacity * reconstructed + (1.f - opacity) * base; // out = a*rec + (1-a)*base
484 }
485}
486
488void _remosaic_and_replace_xtrans(const float *const restrict input, const float *const restrict input_raw,
489 const float *const restrict interpolated,
490 const float *const restrict clipping_mask, float *const restrict output,
491 const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips,
492 const int clip_is_floor, const dt_iop_roi_t *const roi_in,
493 const uint8_t (*const xtrans)[6], const size_t width, const size_t height)
494{
495 // see _remosaic_and_replace for the clip_is_floor semantics and the compositing rule
496 // out = opacity*rec + (1 - opacity)*base, base = max(raw, rec) on a clipped floor.
497 __OMP_PARALLEL_FOR__(collapse(2))
498 for(size_t i = 0; i < height; i++)
499 for(size_t j = 0; j < width; j++)
500 {
501 const size_t idx = i * width + j;
502 const size_t index = idx * 4;
503 const int c = FCxtrans((int)i, (int)j, roi_in, xtrans);
504 const float opacity = clipping_mask[index + ALPHA]; // any-clip mask -> blend weight (0 or 1)
505 // undo local channel normalization (x tile-average white_balance[c]), clamp >= 0
506 const float reconstructed = fmaxf(interpolated[index + c] * white_balance[c], 0.f);
507 float base = input[idx];
508 if(clip_is_floor && input_raw[idx] >= clips[c]) base = fmaxf(base, reconstructed); // floor
509 output[idx] = opacity * reconstructed + (1.f - opacity) * base; // out = a*rec + (1-a)*base
510 }
511}
512
514void _remosaic_and_replace_passthrough(const float *const restrict input, const float *const restrict input_raw,
515 const float *const restrict interpolated,
516 const float *const restrict clipping_mask, float *const restrict output,
517 const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips,
518 const int clip_is_floor, const size_t width, const size_t height)
519{
520 // Non-raw / sRAW twin of _remosaic_and_replace: there is no CFA to scatter onto, so each channel is
521 // composited straight back. Unlike the CFA paths (one measured colour per photosite -> a single
522 // any-clip opacity), a non-raw pixel carries all three colours, so channel c blends with its OWN clip
523 // mask clipping_mask[.. + c]: an unclipped channel keeps its measured base, only clipped channels take
524 // the reconstruction. Same clip_is_floor / compositing rule otherwise. The ALPHA slot is preserved.
525 __OMP_PARALLEL_FOR__(collapse(2))
526 for(size_t i = 0; i < height; i++)
527 for(size_t j = 0; j < width; j++)
528 {
529 const size_t idx = (i * width + j) * 4;
530 for_each_channel(c, aligned(input, input_raw, interpolated, clipping_mask, output, white_balance))
531 {
532 const float opacity = clipping_mask[idx + c]; // per-channel clip flag (0 or 1)
533 // undo local channel normalization (x tile-average white_balance[c]), clamp >= 0
534 const float reconstructed = fmaxf(interpolated[idx + c] * white_balance[c], 0.f);
535 float base = input[idx + c];
536 if(clip_is_floor && input_raw[idx + c] >= clips[c]) base = fmaxf(base, reconstructed); // floor
537 output[idx + c] = opacity * reconstructed + (1.f - opacity) * base; // out = a*rec + (1-a)*base
538 }
539 output[idx + ALPHA] = input[idx + ALPHA]; // pass the 4th channel through unchanged
540 }
541}
static float lookup(read_only image2d_t lut, const float x)
static const float x
const float f
#define B(y, x)
static const int row
static dt_aligned_pixel_t RGB
#define BLUE
#define RED
#define GREEN
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 ALPHA
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
__DT_CLONE_TARGETS__ void _interpolate_and_mask_passthrough(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t white_balance, const size_t width, const size_t height)
Definition gather.c:424
__DT_CLONE_TARGETS__ void _compute_laplacian_normalization(const float *const restrict input, const dt_iop_roi_t *const roi_in, const uint32_t filters, const uint8_t(*const xtrans)[6], dt_aligned_pixel_t normalization)
Definition gather.c:223
__DT_CLONE_TARGETS__ void _remosaic_and_replace_xtrans(const float *const restrict input, const float *const restrict input_raw, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips, const int clip_is_floor, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const size_t width, const size_t height)
Definition gather.c:488
__DT_CLONE_TARGETS__ void _interpolate_and_mask(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips_in, const dt_aligned_pixel_t det_scale, const dt_aligned_pixel_t white_balance, const uint32_t filters, const size_t width, const size_t height)
Definition gather.c:67
__DT_CLONE_TARGETS__ void _remosaic_and_replace(const float *const restrict input, const float *const restrict input_raw, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips, const int clip_is_floor, const uint32_t filters, const size_t width, const size_t height)
Definition gather.c:457
__DT_CLONE_TARGETS__ void _interpolate_and_mask_xtrans(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t white_balance, const dt_iop_roi_t *const roi_in, const int32_t lookup[6][6][32], const uint8_t(*const xtrans)[6], const size_t width, const size_t height)
Definition gather.c:317
__DT_CLONE_TARGETS__ void _build_xtrans_bilinear_lookup(int32_t lookup[6][6][32], const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6])
Definition gather.c:277
__DT_CLONE_TARGETS__ void _remosaic_and_replace_passthrough(const float *const restrict input, const float *const restrict input_raw, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips, const int clip_is_floor, const size_t width, const size_t height)
Definition gather.c:514
GdkRGBA color[]
Definition geotagging.c:541
float *const restrict const size_t k
#define R
uint32_t width
Definition mipmap_cache.c:0
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 for_each_channel(_var,...)
Definition simd.h:87
#define for_four_channels(_var,...)
Definition simd.h:89
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
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