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