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