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 right from the demosaicer for normal raws
45 or from rawprepare in case of monochromes. This means some additional housekeeping for the
46 pixelpipe.
47 If any mask in any module selects a threshold of != 0.0 we leave a flag in the pipe struct
48 telling a) we want a DM and b) we want it from either demosaic or from rawprepare.
49 If such a flag has not been previously set we will force a pipeline reprocessing.
50
51 gboolean dt_dev_write_rawdetail_mask(dt_dev_pixelpipe_iop_t *piece, float *const rgb, const dt_iop_roi_t *const roi_in, const int mode, const dt_aligned_pixel_t wb);
52 or it's _cl equivalent write a preliminary mask holding signal-change values for every pixel.
53 These mask values are calculated as
54 a) get Y0 for every pixel
55 b) apply a scharr operator on it
56
57 This raw detail mask (RM) is not scaled but only cropped to the roi of the writing module (demosaic
58 or rawprepare).
59 The pipe gets roi copy of the writing module so we can later scale/distort the LM.
60
61 Calculating the RM is done for performance and lower mem pressure reasons, so we don't have to
62 pass full data to the module. Also the RM can be used by other modules.
63
64 If a mask uses the details refinement step it takes the raw details mask RM and calculates an
65 intermediate mask (IM) which is still not scaled but has the roi of the writing module.
66
67 For every pixel we calculate the IM value via a sigmoid function with the threshold and RM as parameters.
68
69 At last the IM is slightly blurred to avoid hard transitions, as there still is no scaling we can use
70 a constant sigma. As the blur_9x9 is pretty fast both in openmp/cl code paths - much faster than dt
71 gaussians - it is used here.
72 Now we have an unscaled detail mask which requires to be transformed through the pipeline using
73
74 float *dt_dev_distort_detail_mask(const dt_dev_pixelpipe_t *pipe, float *src, const dt_iop_module_t *target_module)
75
76 returning a pointer to a distorted mask (DT) with same size as used in the module wanting the refinement.
77 This DM is finally used to refine the original mask.
78
79 All other refinements and parametric parameters are untouched.
80
81 Some additional comments:
82 1. intentionally this details mask refinement has only been implemented for raws. Especially for compressed
83 inmages like jpegs or 8bit input the algo didn't work as good because of input precision and compression artifacts.
84 2. In the gui the slider is above the rest of the refinemt sliders to emphasize that blurring & feathering use the
85 mask corrected by detail refinemnt.
86 3. Of course credit goes to Ingo @heckflosse from rt team for the original idea. (in the rt world this is knowb
87 as details mask)
88 4. Thanks to rawfiner for pointing out how to use Y0 and scharr for better maths.
89
90 hanno@schwalm-brmouseemen.de 21/04/29
91*/
92
93void dt_masks_extend_border(float *const restrict mask, const int width, const int height, const int border)
94{
95 if(border <= 0) return;
96 const int max_col = width - border - 1;
97#ifdef _OPENMP
98 #pragma omp parallel for simd default(none) \
99 dt_omp_firstprivate(mask, width, height, border, max_col) \
100 schedule(simd:static) aligned(mask : 64) if((size_t)width * height > 10000)
101 #endif
102 for(int row = border; row < height - border; row++)
103 {
104 float *const rowptr = mask + (size_t)(row * width);
105 for(int i = 0; i < border; i++)
106 {
107 rowptr[i] = rowptr[border];
108 rowptr[width - i - 1] = rowptr[max_col];
109 }
110 }
111 const float *const top_row = mask + (size_t)(border * width);
112 const float *const bot_row = mask + (size_t)(height - border - 1) * width;
113#ifdef _OPENMP
114 #pragma omp parallel for simd default(none) \
115 dt_omp_firstprivate(mask, width, height, border, max_col, top_row, bot_row) \
116 schedule(simd:static) aligned(mask : 64) if((size_t)width * height > 10000)
117 #endif
118 for(int col = 0; col < width; col++)
119 {
120 const int c = MIN(max_col, MAX(col, border));
121 const float top = top_row[c];
122 const float bot = bot_row[c];
123 for(int i = 0; i < border; i++)
124 {
125 mask[col + i * width] = top;
126 mask[col + (height - i - 1) * width] = bot;
127 }
128 }
129}
130
131void _masks_blur_5x5_coeff(float *c, const float sigma)
132{
133 float kernel[5][5];
134 const float temp = -2.0f * sqf(sigma);
135 const float range = sqf(3.0f * 0.84f);
136 float sum = 0.0f;
137 for(int k = -2; k <= 2; k++)
138 {
139 for(int j = -2; j <= 2; j++)
140 {
141 if((sqf(k) + sqf(j)) <= range)
142 {
143 kernel[k + 2][j + 2] = expf((sqf(k) + sqf(j)) / temp);
144 sum += kernel[k + 2][j + 2];
145 }
146 else
147 kernel[k + 2][j + 2] = 0.0f;
148 }
149 }
150 for(int i = 0; i < 5; i++)
151 {
152#if defined(__GNUC__)
153 #pragma GCC ivdep
154#endif
155 for(int j = 0; j < 5; j++)
156 kernel[i][j] /= sum;
157 }
158 /* c21 */ c[0] = kernel[0][1];
159 /* c20 */ c[1] = kernel[0][2];
160 /* c11 */ c[2] = kernel[1][1];
161 /* c10 */ c[3] = kernel[1][2];
162 /* c00 */ c[4] = kernel[2][2];
163}
164#define FAST_BLUR_5 ( \
165 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])) + \
166 blurmat[1] * (src[i - w2] + src[i - 2] + src[i + 2] + src[i + w2]) + \
167 blurmat[2] * (src[i - w1 - 1] + src[i - w1 + 1] + src[i + w1 - 1] + src[i + w1 + 1]) + \
168 blurmat[3] * (src[i - w1] + src[i - 1] + src[i + 1] + src[i + w1]) + \
169 blurmat[4] * src[i] )
170
171void dt_masks_blur_9x9_coeff(float *c, const float sigma)
172{
173 float kernel[9][9];
174 const float temp = -2.0f * sqf(sigma);
175 const float range = sqf(3.0f * 1.5f);
176 float sum = 0.0f;
177 for(int k = -4; k <= 4; k++)
178 {
179 for(int j = -4; j <= 4; j++)
180 {
181 if((sqf(k) + sqf(j)) <= range)
182 {
183 kernel[k + 4][j + 4] = expf((sqf(k) + sqf(j)) / temp);
184 sum += kernel[k + 4][j + 4];
185 }
186 else
187 kernel[k + 4][j + 4] = 0.0f;
188 }
189 }
190 for(int i = 0; i < 9; i++)
191 {
192#if defined(__GNUC__)
193 #pragma GCC ivdep
194#endif
195 for(int j = 0; j < 9; j++)
196 kernel[i][j] /= sum;
197 }
198 /* c00 */ c[0] = kernel[4][4];
199 /* c10 */ c[1] = kernel[3][4];
200 /* c11 */ c[2] = kernel[3][3];
201 /* c20 */ c[3] = kernel[2][4];
202 /* c21 */ c[4] = kernel[2][3];
203 /* c22 */ c[5] = kernel[2][2];
204 /* c30 */ c[6] = kernel[1][4];
205 /* c31 */ c[7] = kernel[1][3];
206 /* c32 */ c[8] = kernel[1][2];
207 /* c33 */ c[9] = kernel[1][1];
208 /* c40 */ c[10] = kernel[0][4];
209 /* c41 */ c[11] = kernel[0][3];
210 /* c42 */ c[12] = kernel[0][2];
211}
212
213#define FAST_BLUR_9 ( \
214 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]) + \
215 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]) + \
216 blurmat[10] * (src[i - w4] + src[i - 4] + src[i + 4] + src[i + w4]) + \
217 blurmat[9] * (src[i - w3 - 3] + src[i - w3 + 3] + src[i + w3 - 3] + src[i + w3 + 3]) + \
218 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]) + \
219 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]) + \
220 blurmat[6] * (src[i - w3] + src[i - 3] + src[i + 3] + src[i + w3]) + \
221 blurmat[5] * (src[i - w2 - 2] + src[i - w2 + 2] + src[i + w2 - 2] + src[i + w2 + 2]) + \
222 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]) + \
223 blurmat[3] * (src[i - w2] + src[i - 2] + src[i + 2] + src[i + w2]) + \
224 blurmat[2] * (src[i - w1 - 1] + src[i - w1 + 1] + src[i + w1 - 1] + src[i + w1 + 1]) + \
225 blurmat[1] * (src[i - w1] + src[i - 1] + src[i + 1] + src[i + w1]) + \
226 blurmat[0] * src[i] )
227
228void dt_masks_blur_9x9(float *const restrict src, float *const restrict out, const int width, const int height, const float sigma)
229{
230 float blurmat[13];
231 dt_masks_blur_9x9_coeff(blurmat, sigma);
232
233 const int w1 = width;
234 const int w2 = 2*width;
235 const int w3 = 3*width;
236 const int w4 = 4*width;
237#ifdef _OPENMP
238 #pragma omp parallel for simd default(none) \
239 dt_omp_firstprivate(blurmat, src, out, width, height, w1, w2, w3, w4) \
240 schedule(simd:static) aligned(src, out : 64) if((size_t)width * height > 50000)
241 #endif
242 for(int row = 4; row < height - 4; row++)
243 {
244 const int row_off = row * width;
245 for(int col = 4; col < width - 4; col++)
246 {
247 const int i = row_off + col;
248 out[i] = fminf(1.0f, fmaxf(0.0f, FAST_BLUR_9));
249 }
250 }
252}
253
254void _masks_blur_13x13_coeff(float *c, const float sigma)
255{
256 float kernel[13][13];
257 const float temp = -2.0f * sqf(sigma);
258 const float range = sqf(3.0f * 2.0f);
259 float sum = 0.0f;
260 for(int k = -6; k <= 6; k++)
261 {
262 for(int j = -6; j <= 6; j++)
263 {
264 if((sqf(k) + sqf(j)) <= range)
265 {
266 kernel[k + 6][j + 6] = expf((sqf(k) + sqf(j)) / temp);
267 sum += kernel[k + 6][j + 6];
268 }
269 else
270 kernel[k + 6][j + 6] = 0.0f;
271 }
272 }
273 for(int i = 0; i < 13; i++)
274 {
275#if defined(__GNUC__)
276 #pragma GCC ivdep
277#endif
278 for(int j = 0; j < 13; j++)
279 kernel[i][j] /= sum;
280 }
281 /* c60 */ c[0] = kernel[0][6];
282 /* c53 */ c[1] = kernel[1][3];
283 /* c52 */ c[2] = kernel[1][4];
284 /* c51 */ c[3] = kernel[1][5];
285 /* c50 */ c[4] = kernel[1][6];
286 /* c44 */ c[5] = kernel[2][2];
287 /* c42 */ c[6] = kernel[2][4];
288 /* c41 */ c[7] = kernel[2][5];
289 /* c40 */ c[8] = kernel[2][6];
290 /* c33 */ c[9] = kernel[3][3];
291 /* c32 */ c[10] = kernel[3][4];
292 /* c31 */ c[11] = kernel[3][5];
293 /* c30 */ c[12] = kernel[3][6];
294 /* c22 */ c[13] = kernel[4][4];
295 /* c21 */ c[14] = kernel[4][5];
296 /* c20 */ c[15] = kernel[4][6];
297 /* c11 */ c[16] = kernel[5][5];
298 /* c10 */ c[17] = kernel[5][6];
299 /* c00 */ c[18] = kernel[6][6];
300}
301
302
303void dt_masks_calc_rawdetail_mask(float *const restrict src, float *const restrict mask, float *const restrict tmp,
304 const int width, const int height, const dt_aligned_pixel_t wb)
305{
306 const int msize = width * height;
307#ifdef _OPENMP
308 #pragma omp parallel for simd default(none) \
309 dt_omp_firstprivate(tmp, src, msize, wb) \
310 schedule(simd:static) aligned(tmp, src : 64) if(msize > 50000)
311#endif
312 for(int idx =0; idx < msize; idx++)
313 {
314 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]);
315 tmp[idx] = sqrtf(val); // add a gamma. sqrtf should make noise variance the same for all image
316 }
317
318 const float scale = 1.0f / 16.0f;
319#ifdef _OPENMP
320 #pragma omp parallel for simd default(none) \
321 dt_omp_firstprivate(mask, tmp, width, height, scale) \
322 schedule(simd:static) aligned(mask, tmp : 64) if((size_t)width * height > 50000)
323 #endif
324 for(int row = 1; row < height - 1; row++)
325 {
326 for(int col = 1, idx = row * width + col; col < width - 1; col++, idx++)
327 {
328 // scharr operator
329 const float gx = 47.0f * (tmp[idx-width-1] - tmp[idx-width+1])
330 + 162.0f * (tmp[idx-1] - tmp[idx+1])
331 + 47.0f * (tmp[idx+width-1] - tmp[idx+width+1]);
332 const float gy = 47.0f * (tmp[idx-width-1] - tmp[idx+width-1])
333 + 162.0f * (tmp[idx-width] - tmp[idx+width])
334 + 47.0f * (tmp[idx-width+1] - tmp[idx+width+1]);
335 const float gradient_magnitude = sqrtf(sqf(gx / 256.0f) + sqf(gy / 256.0f));
336 mask[idx] = scale * gradient_magnitude;
337 // Original code from rt
338 // tmp[idx] = scale * sqrtf(sqf(src[idx+1] - src[idx-1]) + sqf(src[idx + width] - src[idx - width]) +
339 // sqf(src[idx+2] - src[idx-2]) + sqf(src[idx + 2*width] - src[idx - 2*width]));
340 }
341 }
343}
344
345static inline float calcBlendFactor(float val, float threshold)
346{
347 // sigmoid function
348 // result is in ]0;1] range
349 // inflexion point is at (x, y) (threshold, 0.5)
350 return 1.0f / (1.0f + dt_fast_expf(16.0f - (16.0f / threshold) * val));
351}
352
353void 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)
354{
355 const int msize = width * height;
356#ifdef _OPENMP
357 #pragma omp parallel for simd default(none) \
358 dt_omp_firstprivate(src, tmp, msize, threshold, detail, out) \
359 schedule(simd:static) aligned(src, tmp, out : 64) if(msize > 50000)
360#endif
361 for(int idx = 0; idx < msize; idx++)
362 {
363 const float blend = calcBlendFactor(src[idx], threshold);
364 tmp[idx] = detail ? blend : 1.0f - blend;
365 }
366 dt_masks_blur_9x9(tmp, out, width, height, 2.0f);
367}
368#undef FAST_BLUR_5
369#undef FAST_BLUR_9
370
371// clang-format off
372// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
373// vim: shiftwidth=2 expandtab tabstop=2 cindent
374// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
375// 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:669
const float c
Definition colorspaces_inline_conversions.h:1365
const float threshold
Definition colorspaces_inline_conversions.h:340
static const dt_colormatrix_t dt_aligned_pixel_t out
Definition colorspaces_inline_conversions.h:184
const float top
Definition colorspaces_inline_conversions.h:672
static const int row
Definition colorspaces_inline_conversions.h:175
void dt_masks_blur_9x9_coeff(float *c, const float sigma)
Definition detail.c:171
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:228
void _masks_blur_13x13_coeff(float *c, const float sigma)
Definition detail.c:254
void dt_masks_extend_border(float *const restrict mask, const int width, const int height, const int border)
Definition detail.c:93
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:353
static float calcBlendFactor(float val, float threshold)
Definition detail.c:345
void _masks_blur_5x5_coeff(float *c, const float sigma)
Definition detail.c:131
#define FAST_BLUR_9
Definition detail.c:213
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:303
#define w2
Definition lmmse.c:65
#define w1
Definition lmmse.c:64
#define w4
Definition lmmse.c:67
#define w3
Definition lmmse.c:66
static float sqf(const float x)
Definition math.h:223
static float dt_fast_expf(const float x)
Definition math.h:290
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29