Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
detail.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2021 Hanno Schwalm.
4 Copyright (C) 2021 luzpaz.
5 Copyright (C) 2021 Pascal Obry.
6 Copyright (C) 2021 Ralf Brown.
7 Copyright (C) 2021 Roman Lebedev.
8 Copyright (C) 2022 Martin Bařinka.
9 Copyright (C) 2023, 2025 Aurélien PIERRE.
10
11 darktable is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
15
16 darktable is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with darktable. If not, see <http://www.gnu.org/licenses/>.
23*/
24
25/* Declares what this file implements. This file is textually #included by masks.c rather than
26 * compiled on its own, so the include has to live HERE and not in the host: clang-tidy attributes
27 * a symbol's use to the file that names it, and an include placed in masks.c for detail.c's
28 * benefit reads as unused there. */
30
31/* How are "detail masks" implemented?
32
33 The detail masks (DM) are used by the dual demosaicer and as a further refinement step for
34 shape / parametric masks.
35 They contain threshold weighed values of pixel-wise local signal changes so they can be
36 understood as "areas with or without local detail".
37
38 As the DM using algorithms (like dual demosaicing, sharpening ...) are all pixel peeping we
39 want the "original data" from the sensor to calculate it.
40 (Calculating the mask from the modules roi might not detect such regions at all because of
41 scaling / rotating artifacts, some blurring earlier in the pipeline, color changes ...)
42
43 In all cases the user interface is pretty simple, we just pass a threshold value, which
44 is in the range of -1.0 to 1.0 by an additional slider in the masks refinement section.
45 Positive values will select regions with lots of local detail, negatives select for flat areas.
46 (The dual demosaicer only wants positives as we always look for high frequency content.)
47 A threshold value of 0.0 means bypassing.
48
49 So the first important point is:
50 We make sure taking the input data for the DM from a dedicated hidden pipeline stage placed
51 right after demosaic. This means some additional housekeeping for the pixelpipe.
52 If any mask in any module selects a threshold of != 0.0 we leave a flag in the pipe struct
53 telling that we want a DM from that dedicated stage. If such a flag has not been previously
54 set we will force a pipeline reprocessing.
55
56 The hidden `detailmask` module writes a preliminary mask holding signal-change values for every pixel
57 in its CPU and OpenCL `process()` callbacks.
58 These mask values are calculated as
59 a) get Y0 for every pixel
60 b) apply a scharr operator on it
61
62 This raw detail mask (RM) is not scaled but only cropped to the roi of the writing module.
63 The pipe gets roi copy of the writing module so we can later scale/distort the LM.
64
65 Calculating the RM is done for performance and lower mem pressure reasons, so we don't have to
66 pass full data to the module. Also the RM can be used by other modules.
67
68 If a mask uses the details refinement step it takes the raw details mask RM and calculates an
69 intermediate mask (IM) which is still not scaled but has the roi of the writing module.
70
71 For every pixel we calculate the IM value via a sigmoid function with the threshold and RM as parameters.
72
73 At last the IM is slightly blurred to avoid hard transitions, as there still is no scaling we can use
74 a constant sigma. As the blur_9x9 is pretty fast both in openmp/cl code paths - much faster than dt
75 gaussians - it is used here.
76 Now we have an unscaled detail mask which requires to be transformed through the pipeline using
77
78 float *dt_dev_distort_detail_mask(const dt_dev_pixelpipe_t *pipe, float *src, const dt_iop_module_t *target_module)
79
80 returning a pointer to a distorted mask (DT) with same size as used in the module wanting the refinement.
81 This DM is finally used to refine the original mask.
82
83 All other refinements and parametric parameters are untouched.
84
85 Some additional comments:
86 1. the detail mask is authored from the RGBA float pipeline stage immediately after demosaic,
87 which keeps the source buffer format stable across RAW and non-RAW inputs.
88 2. In the gui the slider is above the rest of the refinemt sliders to emphasize that blurring & feathering use the
89 mask corrected by detail refinemnt.
90 3. Of course credit goes to Ingo @heckflosse from rt team for the original idea. (in the rt world this is knowb
91 as details mask)
92 4. Thanks to rawfiner for pointing out how to use Y0 and scharr for better maths.
93
94 hanno@schwalm-brmouseemen.de 21/04/29
95*/
96
98void dt_masks_extend_border(float *const restrict mask, const int width, const int height, const int border)
99{
100 if(border <= 0) return;
101 const int max_col = width - border - 1;
102 __OMP_PARALLEL_FOR_SIMD__(aligned(mask : 64) if((size_t)width * height > 10000))
103 for(int row = border; row < height - border; row++)
104 {
105 float *const rowptr = mask + (size_t)(row * width);
106 for(int i = 0; i < border; i++)
107 {
108 rowptr[i] = rowptr[border];
109 rowptr[width - i - 1] = rowptr[max_col];
110 }
111 }
112 const float *const top_row = mask + (size_t)(border * width);
113 const float *const bot_row = mask + (size_t)(height - border - 1) * width;
114 __OMP_FOR_SIMD__(aligned(mask : 64) if((size_t)width * height > 10000))
115 for(int col = 0; col < width; col++)
116 {
117 const int c = MIN(max_col, MAX(col, border));
118 const float top = top_row[c];
119 const float bot = bot_row[c];
120 for(int i = 0; i < border; i++)
121 {
122 mask[col + i * width] = top;
123 mask[col + (height - i - 1) * width] = bot;
124 }
125 }
126}
127
128void _masks_blur_5x5_coeff(float *c, const float sigma)
129{
130 float kernel[5][5];
131 const float temp = -2.0f * sqf(sigma);
132 const float range = sqf(3.0f * 0.84f);
133 float sum = 0.0f;
134 for(int k = -2; k <= 2; k++)
135 {
136 for(int j = -2; j <= 2; j++)
137 {
138 if((sqf(k) + sqf(j)) <= range)
139 {
140 kernel[k + 2][j + 2] = expf((sqf(k) + sqf(j)) / temp);
141 sum += kernel[k + 2][j + 2];
142 }
143 else
144 kernel[k + 2][j + 2] = 0.0f;
145 }
146 }
147 for(int i = 0; i < 5; i++)
148 for(int j = 0; j < 5; j++)
149 kernel[i][j] /= sum;
150
151 // FIXME: are you for real ? managing arrays with loops and index shift much ?
152 /* c21 */ c[0] = kernel[0][1];
153 /* c20 */ c[1] = kernel[0][2];
154 /* c11 */ c[2] = kernel[1][1];
155 /* c10 */ c[3] = kernel[1][2];
156 /* c00 */ c[4] = kernel[2][2];
157}
158#define FAST_BLUR_5 ( \
159 blurmat[0] * ((src[i - w2 - 1] + src[i - w2 + 1]) + (src[i - w1 - 2] + src[i - w1 + 2]) + (src[i + w1 - 2] + src[i + w1 + 2]) + (src[i + w2 - 1] + src[i + w2 + 1])) + \
160 blurmat[1] * (src[i - w2] + src[i - 2] + src[i + 2] + src[i + w2]) + \
161 blurmat[2] * (src[i - w1 - 1] + src[i - w1 + 1] + src[i + w1 - 1] + src[i + w1 + 1]) + \
162 blurmat[3] * (src[i - w1] + src[i - 1] + src[i + 1] + src[i + w1]) + \
163 blurmat[4] * src[i] )
164
165void dt_masks_blur_9x9_coeff(float *c, const float sigma)
166{
167 float kernel[9][9];
168 const float temp = -2.0f * sqf(sigma);
169 const float range = sqf(3.0f * 1.5f);
170 float sum = 0.0f;
171 for(int k = -4; k <= 4; k++)
172 {
173 for(int j = -4; j <= 4; j++)
174 {
175 if((sqf(k) + sqf(j)) <= range)
176 {
177 kernel[k + 4][j + 4] = expf((sqf(k) + sqf(j)) / temp);
178 sum += kernel[k + 4][j + 4];
179 }
180 else
181 kernel[k + 4][j + 4] = 0.0f;
182 }
183 }
184 for(int i = 0; i < 9; i++)
185 for(int j = 0; j < 9; j++)
186 kernel[i][j] /= sum;
187
188 // FIXME: are you for real ? managing arrays with loops and index shift much ?
189 /* c00 */ c[0] = kernel[4][4];
190 /* c10 */ c[1] = kernel[3][4];
191 /* c11 */ c[2] = kernel[3][3];
192 /* c20 */ c[3] = kernel[2][4];
193 /* c21 */ c[4] = kernel[2][3];
194 /* c22 */ c[5] = kernel[2][2];
195 /* c30 */ c[6] = kernel[1][4];
196 /* c31 */ c[7] = kernel[1][3];
197 /* c32 */ c[8] = kernel[1][2];
198 /* c33 */ c[9] = kernel[1][1];
199 /* c40 */ c[10] = kernel[0][4];
200 /* c41 */ c[11] = kernel[0][3];
201 /* c42 */ c[12] = kernel[0][2];
202}
203
204// FIMXE: ever heard about loop unrolling ???
205#define FAST_BLUR_9 ( \
206 blurmat[12] * (src[i - w4 - 2] + src[i - w4 + 2] + src[i - w2 - 4] + src[i - w2 + 4] + src[i + w2 - 4] + src[i + w2 + 4] + src[i + w4 - 2] + src[i + w4 + 2]) + \
207 blurmat[11] * (src[i - w4 - 1] + src[i - w4 + 1] + src[i - w1 - 4] + src[i - w1 + 4] + src[i + w1 - 4] + src[i + w1 + 4] + src[i + w4 - 1] + src[i + w4 + 1]) + \
208 blurmat[10] * (src[i - w4] + src[i - 4] + src[i + 4] + src[i + w4]) + \
209 blurmat[9] * (src[i - w3 - 3] + src[i - w3 + 3] + src[i + w3 - 3] + src[i + w3 + 3]) + \
210 blurmat[8] * (src[i - w3 - 2] + src[i - w3 + 2] + src[i - w2 - 3] + src[i - w2 + 3] + src[i + w2 - 3] + src[i + w2 + 3] + src[i + w3 - 2] + src[i + w3 + 2]) + \
211 blurmat[7] * (src[i - w3 - 1] + src[i - w3 + 1] + src[i - w1 - 3] + src[i - w1 + 3] + src[i + w1 - 3] + src[i + w1 + 3] + src[i + w3 - 1] + src[i + w3 + 1]) + \
212 blurmat[6] * (src[i - w3] + src[i - 3] + src[i + 3] + src[i + w3]) + \
213 blurmat[5] * (src[i - w2 - 2] + src[i - w2 + 2] + src[i + w2 - 2] + src[i + w2 + 2]) + \
214 blurmat[4] * (src[i - w2 - 1] + src[i - w2 + 1] + src[i - w1 - 2] + src[i - w1 + 2] + src[i + w1 - 2] + src[i + w1 + 2] + src[i + w2 - 1] + src[i + w2 + 1]) + \
215 blurmat[3] * (src[i - w2] + src[i - 2] + src[i + 2] + src[i + w2]) + \
216 blurmat[2] * (src[i - w1 - 1] + src[i - w1 + 1] + src[i + w1 - 1] + src[i + w1 + 1]) + \
217 blurmat[1] * (src[i - w1] + src[i - 1] + src[i + 1] + src[i + w1]) + \
218 blurmat[0] * src[i] )
219
220void dt_masks_blur_9x9(float *const restrict src, float *const restrict out, const int width, const int height, const float sigma)
221{
222 float blurmat[13];
224
225 const int w1 = width;
226 const int w2 = 2*width;
227 const int w3 = 3*width;
228 const int w4 = 4*width;
229 __OMP_FOR_SIMD__(aligned(src, out : 64) if((size_t)width * height > 50000))
230 for(int row = 4; row < height - 4; row++)
231 {
232 const int row_off = row * width;
233 for(int col = 4; col < width - 4; col++)
234 {
235 const int i = row_off + col;
236 out[i] = fminf(1.0f, fmaxf(0.0f, FAST_BLUR_9));
237 }
238 }
240}
241
242void _masks_blur_13x13_coeff(float *c, const float sigma)
243{
244 float kernel[13][13];
245 const float temp = -2.0f * sqf(sigma);
246 const float range = sqf(3.0f * 2.0f);
247 float sum = 0.0f;
248 for(int k = -6; k <= 6; k++)
249 {
250 for(int j = -6; j <= 6; j++)
251 {
252 if((sqf(k) + sqf(j)) <= range)
253 {
254 kernel[k + 6][j + 6] = expf((sqf(k) + sqf(j)) / temp);
255 sum += kernel[k + 6][j + 6];
256 }
257 else
258 kernel[k + 6][j + 6] = 0.0f;
259 }
260 }
261 for(int i = 0; i < 13; i++)
262 for(int j = 0; j < 13; j++)
263 kernel[i][j] /= sum;
264
265 // FIXME: are you for real ? managing arrays with loops and index shift much ?
266 /* c60 */ c[0] = kernel[0][6];
267 /* c53 */ c[1] = kernel[1][3];
268 /* c52 */ c[2] = kernel[1][4];
269 /* c51 */ c[3] = kernel[1][5];
270 /* c50 */ c[4] = kernel[1][6];
271 /* c44 */ c[5] = kernel[2][2];
272 /* c42 */ c[6] = kernel[2][4];
273 /* c41 */ c[7] = kernel[2][5];
274 /* c40 */ c[8] = kernel[2][6];
275 /* c33 */ c[9] = kernel[3][3];
276 /* c32 */ c[10] = kernel[3][4];
277 /* c31 */ c[11] = kernel[3][5];
278 /* c30 */ c[12] = kernel[3][6];
279 /* c22 */ c[13] = kernel[4][4];
280 /* c21 */ c[14] = kernel[4][5];
281 /* c20 */ c[15] = kernel[4][6];
282 /* c11 */ c[16] = kernel[5][5];
283 /* c10 */ c[17] = kernel[5][6];
284 /* c00 */ c[18] = kernel[6][6];
285}
286
287
289void dt_masks_calc_rawdetail_mask(float *const restrict src, float *const restrict mask, float *const restrict tmp,
290 const int width, const int height, const dt_aligned_pixel_t wb)
291{
292 const int msize = width * height;
293 __OMP_FOR_SIMD__(aligned(tmp, src : 64) if(msize > 50000))
294 for(int idx =0; idx < msize; idx++)
295 {
296 const float val = 0.333333333f * (fmaxf(src[4 * idx], 0.0f) / wb[0] + fmaxf(src[4 * idx + 1], 0.0f) / wb[1] + fmaxf(src[4 * idx + 2], 0.0f) / wb[2]);
297 tmp[idx] = sqrtf(val); // add a gamma. sqrtf should make noise variance the same for all image
298 }
299
300 const float scale = 1.0f / 16.0f;
301 __OMP_PARALLEL_FOR_SIMD__(aligned(mask, tmp : 64) if((size_t)width * height > 50000))
302 for(int row = 1; row < height - 1; row++)
303 {
304 for(int col = 1, idx = row * width + col; col < width - 1; col++, idx++)
305 {
306 // scharr operator
307 const float gx = 47.0f * (tmp[idx-width-1] - tmp[idx-width+1])
308 + 162.0f * (tmp[idx-1] - tmp[idx+1])
309 + 47.0f * (tmp[idx+width-1] - tmp[idx+width+1]);
310 const float gy = 47.0f * (tmp[idx-width-1] - tmp[idx+width-1])
311 + 162.0f * (tmp[idx-width] - tmp[idx+width])
312 + 47.0f * (tmp[idx-width+1] - tmp[idx+width+1]);
313 const float gradient_magnitude = dt_fast_hypotf(gx / 256.0f, gy / 256.0f);
314 mask[idx] = scale * gradient_magnitude;
315 // Original code from rt
316 // tmp[idx] = scale * sqrtf(sqf(src[idx+1] - src[idx-1]) + sqf(src[idx + width] - src[idx - width]) +
317 // sqf(src[idx+2] - src[idx-2]) + sqf(src[idx + 2*width] - src[idx - 2*width]));
318 }
319 }
321}
322
323static inline float calcBlendFactor(float val, float threshold)
324{
325 // sigmoid function
326 // result is in ]0;1] range
327 // inflexion point is at (x, y) (threshold, 0.5)
328 return 1.0f / (1.0f + dt_fast_expf(16.0f - (16.0f / threshold) * val));
329}
330
331void dt_masks_calc_detail_mask(float *const restrict src, float *const restrict out, float *const restrict tmp, const int width, const int height, const float threshold, const gboolean detail)
332{
333 const int msize = width * height;
334 __OMP_FOR_SIMD__(aligned(src, tmp, out : 64) if(msize > 50000))
335 for(int idx = 0; idx < msize; idx++)
336 {
337 const float blend = calcBlendFactor(src[idx], threshold);
338 tmp[idx] = detail ? blend : 1.0f - blend;
339 }
340 dt_masks_blur_9x9(tmp, out, width, height, 2.0f);
341}
342#undef FAST_BLUR_5
343#undef FAST_BLUR_9
344
345// clang-format off
346// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
347// vim: shiftwidth=2 expandtab tabstop=2 cindent
348// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
349// clang-format on
const float threshold
const dt_colormatrix_t dt_aligned_pixel_t out
const float top
static const int row
__DT_CLONE_TARGETS__ void dt_masks_calc_rawdetail_mask(float *const restrict src, float *const restrict mask, float *const restrict tmp, const int width, const int height, const dt_aligned_pixel_t wb)
Definition detail.c:289
void dt_masks_blur_9x9_coeff(float *c, const float sigma)
Definition detail.c:165
void dt_masks_blur_9x9(float *const restrict src, float *const restrict out, const int width, const int height, const float sigma)
Definition detail.c:220
void _masks_blur_13x13_coeff(float *c, const float sigma)
Definition detail.c:242
__DT_CLONE_TARGETS__ void dt_masks_extend_border(float *const restrict mask, const int width, const int height, const int border)
Definition detail.c:98
void dt_masks_calc_detail_mask(float *const restrict src, float *const restrict out, float *const restrict tmp, const int width, const int height, const float threshold, const gboolean detail)
Definition detail.c:331
static float calcBlendFactor(float val, float threshold)
Definition detail.c:323
void _masks_blur_5x5_coeff(float *c, const float sigma)
Definition detail.c:128
#define FAST_BLUR_9
Definition detail.c:205
static float kernel(const float *x, const float *y)
#define w2
Definition lmmse.c:60
#define w1
Definition lmmse.c:59
#define w4
Definition lmmse.c:62
#define w3
Definition lmmse.c:61
float *const restrict const size_t k
The detail-mask pixel math: a Scharr-style detail estimate over a raw or scene-referred buffer,...
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_FOR_SIMD__(...)
Definition openmp.h:97
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float sigma
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29