Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
bspline.h
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2021-2023, 2025 Aurélien PIERRE.
4 Copyright (C) 2021 Pascal Obry.
5 Copyright (C) 2021 Ralf Brown.
6 Copyright (C) 2022 Martin Bařinka.
7 Copyright (C) 2023 Luca Zulberti.
8
9 Ansel is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 Ansel is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
21*/
22#ifndef DT_PIXEL_BSPLINE_H
23#define DT_PIXEL_BSPLINE_H
24
25#include "system/openmp.h"
26#include "system/mem_alloc.h"
27#include "system/simd.h"
29#include "pixel/dwt.h"
30#include "math/openmp_maths.h"
31#include "math/math.h"
32
33// B spline filter
34#define BSPLINE_FSIZE 5
35
36// The B spline best approximate a Gaussian of standard deviation :
37// see https://eng.aurelienpierre.com/2021/03/rotation-invariant-laplacian-for-2d-grids/
38#define B_SPLINE_SIGMA 1.0553651328015339f
39
40static inline float normalize_laplacian(const float sigma)
41{
42 // Normalize the wavelet scale to approximate a laplacian
43 // see https://eng.aurelienpierre.com/2021/03/rotation-invariant-laplacian-for-2d-grids/#Scaling-coefficient
44 return 2.f / sqf(sigma);
45}
46
47// Normalization scaling of the wavelet to approximate a laplacian:
48// 2 * sqrt(pi) / B_SPLINE_SIGMA^2 (NOT normalize_laplacian() above, which is 2 / sigma^2)
49#define B_SPLINE_TO_LAPLACIAN 3.182727439285017f
50#define B_SPLINE_TO_LAPLACIAN_2 10.129753952777762f // square
51
52static inline float equivalent_sigma_at_step(const float sigma, const unsigned int s)
53{
54 // If we stack several gaussian blurs of standard deviation sigma on top of each other,
55 // this is the equivalent standard deviation we get at the end (after s steps)
56 // First step is s = 0
57 // see
58 // https://eng.aurelienpierre.com/2021/03/rotation-invariant-laplacian-for-2d-grids/#Multi-scale-iterative-scheme
59 if(s == 0)
60 return sigma;
61 else
62 return sqrtf(sqf(equivalent_sigma_at_step(sigma, s - 1)) + sqf(exp2f((float)s) * sigma));
63}
64
65static inline unsigned int num_steps_to_reach_equivalent_sigma(const float sigma_filter, const float sigma_final)
66{
67 // The inverse of the above : compute the number of scales needed to reach the desired equivalent sigma_final
68 // after sequential blurs of constant sigma_filter
69 unsigned int s = 0;
70 float radius = sigma_filter;
71 while(radius < sigma_final)
72 {
73 ++s;
74 radius = sqrtf(sqf(radius) + sqf((float)(1 << s) * sigma_filter));
75 }
76 return s + 1;
77}
78
79static inline size_t decimated_bspline_size(const size_t size)
80{
81 return (size - 1u) / 2u + 1u;
82}
83static inline void sparse_scalar_product(const dt_aligned_pixel_t buf, const size_t indices[BSPLINE_FSIZE],
84 dt_aligned_pixel_t result, const gboolean clip_negatives)
85{
86 // scalar product of 2 3x5 vectors stored as RGB planes and B-spline filter,
87 // e.g. RRRRR - GGGGG - BBBBB
88 static const float filter[BSPLINE_FSIZE] = { 1.0f / 16.0f,
89 4.0f / 16.0f,
90 6.0f / 16.0f,
91 4.0f / 16.0f,
92 1.0f / 16.0f };
93
94 if(clip_negatives)
95 {
96 for_each_channel(c, aligned(buf,indices,result))
97 {
98 result[c] = MAX(0.0f, filter[0] * buf[indices[0] + c] +
99 filter[1] * buf[indices[1] + c] +
100 filter[2] * buf[indices[2] + c] +
101 filter[3] * buf[indices[3] + c] +
102 filter[4] * buf[indices[4] + c]);
103 }
104 }
105 else
106 {
107 for_each_channel(c, aligned(buf,indices,result))
108 {
109 result[c] = filter[0] * buf[indices[0] + c] +
110 filter[1] * buf[indices[1] + c] +
111 filter[2] * buf[indices[2] + c] +
112 filter[3] * buf[indices[3] + c] +
113 filter[4] * buf[indices[4] + c];
114 }
115 }
116}
117static inline void _bspline_vertical_pass(const float *const restrict in, float *const restrict temp,
118 size_t row, size_t width, size_t height, int mult, const gboolean clip_negatives)
119{
120 size_t DT_ALIGNED_ARRAY indices[BSPLINE_FSIZE];
121 // compute the index offsets of the pixels of interest; since the offsets are the same for the entire row,
122 // we only need to do this once and can then process the entire row
123 indices[0] = 4 * width * MAX((int)row - 2 * mult, 0);
124 indices[1] = 4 * width * MAX((int)row - mult, 0);
125 indices[2] = 4 * width * row;
126 indices[3] = 4 * width * MIN(row + mult, height-1);
127 indices[4] = 4 * width * MIN(row + 2 * mult, height-1);
128 for(size_t j = 0; j < width; j++)
129 {
130 // Compute the vertical blur of the current pixel and store it in the temp buffer for the row
131 sparse_scalar_product(in + j * 4, indices, temp + j * 4, clip_negatives);
132 }
133}
134
135__OMP_DECLARE_SIMD__(aligned(temp, out))
136static inline void _bspline_horizontal(const float *const restrict temp, float *const restrict out,
137 size_t col, size_t width, int mult, const gboolean clip_negatives)
138{
139 // Compute the array indices of the pixels of interest; since the offsets will change near the ends of
140 // the row, we need to recompute for each pixel
141 size_t DT_ALIGNED_ARRAY indices[BSPLINE_FSIZE];
142 indices[0] = 4 * MAX((int)col - 2 * mult, 0);
143 indices[1] = 4 * MAX((int)col - mult, 0);
144 indices[2] = 4 * col;
145 indices[3] = 4 * MIN(col + mult, width-1);
146 indices[4] = 4 * MIN(col + 2 * mult, width-1);
147 // Compute the horizontal blur of the already vertically-blurred pixel and store the result at the proper
148 // row/column location in the output buffer
149 sparse_scalar_product(temp, indices, out, clip_negatives);
150}
151
152__OMP_DECLARE_SIMD__(aligned(temp, out))
153static inline void _bspline_horizontal_decimated(const float *const restrict temp, float *const restrict out,
154 const size_t col, const size_t width,
155 const gboolean clip_negatives)
156{
157 // The vertical pass has already been evaluated on the fine grid. We now only
158 // sample the horizontal convolution on the even columns kept by the decimated
159 // spline pyramid.
160 const size_t center = col * 2u;
161 size_t DT_ALIGNED_ARRAY indices[BSPLINE_FSIZE];
162 indices[0] = 4 * MAX((int)center - 2, 0);
163 indices[1] = 4 * MAX((int)center - 1, 0);
164 indices[2] = 4 * center;
165 indices[3] = 4 * MIN(center + 1, width - 1);
166 indices[4] = 4 * MIN(center + 2, width - 1);
167 sparse_scalar_product(temp, indices, out, clip_negatives);
168}
169inline static void reduce_2D_Bspline(const float *const restrict in, float *const restrict out,
170 const size_t width, const size_t height,
171 float *const restrict tempbuf, const size_t padded_size,
172 const gboolean clip_negatives)
173{
174 const size_t coarse_width = decimated_bspline_size(width);
175 const size_t coarse_height = decimated_bspline_size(height);
176 const gboolean use_replicated_boundary = (coarse_width > 2u && coarse_height > 2u);
177 static const float filter[BSPLINE_FSIZE] = { 1.0f / 16.0f,
178 4.0f / 16.0f,
179 6.0f / 16.0f,
180 4.0f / 16.0f,
181 1.0f / 16.0f };
182 (void)tempbuf;
183 (void)padded_size;
185 for(size_t row = 0; row < coarse_height; ++row)
186 {
187 for(size_t col = 0; col < coarse_width; ++col)
188 {
189 dt_aligned_pixel_t accum = { 0.f };
190 const size_t sample_row = use_replicated_boundary ? CLAMP((int)row, 1, (int)coarse_height - 2) : row;
191 const size_t sample_col = use_replicated_boundary ? CLAMP((int)col, 1, (int)coarse_width - 2) : col;
192 const size_t center_row = sample_row * 2u;
193 const size_t center_col = sample_col * 2u;
194
195 // Evaluate the decimated 5x5 cardinal B-spline on the current grid. This
196 // is the Gaussian-pyramid reduce stage used to build the hybrid decimated
197 // wavelet stack.
198 for(int jj = -2; jj <= 2; ++jj)
199 {
200 const size_t yy = CLAMP((int)center_row + jj, 0, (int)height - 1);
201 for(int ii = -2; ii <= 2; ++ii)
202 {
203 const size_t xx = CLAMP((int)center_col + ii, 0, (int)width - 1);
204 const float weight = filter[ii + 2] * filter[jj + 2];
205 const size_t index = 4 * (yy * width + xx);
207 accum[c] += weight * in[index + c];
208 }
209 }
210
211 const size_t out_index = 4 * (row * coarse_width + col);
212 if(clip_negatives)
213 {
215 out[out_index + c] = MAX(accum[c], 0.f);
216 }
217 else
218 {
219 copy_pixel_nontemporal(out + out_index, accum);
220 }
221 }
222 }
223}
224inline static void expand_2D_Bspline(const float *const restrict in, float *const restrict out,
225 const size_t width, const size_t height,
226 const gboolean clip_negatives)
227{
228 const size_t coarse_width = decimated_bspline_size(width);
229 const size_t coarse_height = decimated_bspline_size(height);
230 const gboolean use_replicated_boundary = (width > 2u && height > 2u && coarse_width > 1u && coarse_height > 1u);
231 static const float filter[BSPLINE_FSIZE] = { 1.0f / 16.0f,
232 4.0f / 16.0f,
233 6.0f / 16.0f,
234 4.0f / 16.0f,
235 1.0f / 16.0f };
236 __OMP_PARALLEL_FOR__(collapse(2))
237 for(size_t row = 0; row < height; ++row)
238 for(size_t col = 0; col < width; ++col)
239 {
240 size_t sample_row = row;
241 size_t sample_col = col;
242 if(use_replicated_boundary)
243 {
244 const size_t max_row = (height & 1u) ? height - 2u : height - 3u;
245 const size_t max_col = (width & 1u) ? width - 2u : width - 3u;
246 sample_row = CLAMP((int)row, 1, (int)max_row);
247 sample_col = CLAMP((int)col, 1, (int)max_col);
248 }
249
250 const size_t center_row = sample_row >> 1;
251 const size_t center_col = sample_col >> 1;
252 dt_aligned_pixel_t accum = { 0.f };
253
254 // Rebuild the fine sample with the same parity-dependent Gaussian expand
255 // used by the local-laplacian pyramid, but on 4-channel spline data.
256 switch((sample_col & 1u) + 2u * (sample_row & 1u))
257 {
258 case 0:
259 {
260 for(int jj = -1; jj <= 1; ++jj)
261 for(int ii = -1; ii <= 1; ++ii)
262 {
263 const size_t yy = center_row + jj;
264 const size_t xx = center_col + ii;
265 const float weight = 4.f * filter[2 * (jj + 1)] * filter[2 * (ii + 1)];
266 const size_t index = 4 * (yy * coarse_width + xx);
268 accum[c] += weight * in[index + c];
269 }
270 break;
271 }
272 case 1:
273 {
274 for(int jj = -1; jj <= 1; ++jj)
275 for(int ii = 0; ii <= 1; ++ii)
276 {
277 const size_t yy = center_row + jj;
278 const size_t xx = center_col + ii;
279 const float weight = 4.f * filter[2 * (jj + 1)] * filter[2 * ii + 1];
280 const size_t index = 4 * (yy * coarse_width + xx);
282 accum[c] += weight * in[index + c];
283 }
284 break;
285 }
286 case 2:
287 {
288 for(int jj = 0; jj <= 1; ++jj)
289 for(int ii = -1; ii <= 1; ++ii)
290 {
291 const size_t yy = center_row + jj;
292 const size_t xx = center_col + ii;
293 const float weight = 4.f * filter[2 * jj + 1] * filter[2 * (ii + 1)];
294 const size_t index = 4 * (yy * coarse_width + xx);
296 accum[c] += weight * in[index + c];
297 }
298 break;
299 }
300 default:
301 {
302 for(int jj = 0; jj <= 1; ++jj)
303 for(int ii = 0; ii <= 1; ++ii)
304 {
305 const size_t yy = center_row + jj;
306 const size_t xx = center_col + ii;
307 const float weight = 4.f * filter[2 * jj + 1] * filter[2 * ii + 1];
308 const size_t index = 4 * (yy * coarse_width + xx);
310 accum[c] += weight * in[index + c];
311 }
312 break;
313 }
314 }
315
316 const size_t out_index = 4 * (row * width + col);
317 if(clip_negatives)
318 {
320 out[out_index + c] = MAX(accum[c], 0.f);
321 }
322 else
323 {
324 copy_pixel_nontemporal(out + out_index, accum);
325 }
326 }
327
328}
329inline static void blur_2D_Bspline(const float *const restrict in, float *const restrict out,
330 float *const restrict tempbuf,
331 const size_t width, const size_t height, const int mult, const gboolean clip_negatives)
332{
333 // À-trous B-spline interpolation/blur shifted by mult
335 for(size_t row = 0; row < height; row++)
336 {
337 // get a thread-private one-row temporary buffer
338 float *const temp = tempbuf + 4 * width * dt_get_thread_num();
339 // interleave the order in which we process the rows so that we minimize cache misses
340 const size_t i = dwt_interleave_rows(row, height, mult);
341 // Convolve B-spline filter over columns: for each pixel in the current row, compute vertical blur
342 _bspline_vertical_pass(in, temp, i, width, height, mult, clip_negatives);
343 // Convolve B-spline filter horizontally over current row
344 for(size_t j = 0; j < width; j++)
345 {
346 _bspline_horizontal(temp, out + (i * width + j) * 4, j, width, mult, clip_negatives);
347 }
348 }
349
350}
351inline static void decompose_2D_Bspline(const float *const restrict in,
352 float *const restrict HF,
353 float *const restrict LF,
354 const size_t width, const size_t height, const int mult,
355 float *const tempbuf, size_t padded_size)
356{
357 // Blur and compute the wavelet at once
359 for(size_t row = 0; row < height; row++)
360 {
361 // get a thread-private one-row temporary buffer
362 float *restrict DT_ALIGNED_ARRAY const temp = dt_get_perthread(tempbuf, padded_size);
363 // interleave the order in which we process the rows so that we minimize cache misses
364 const size_t i = dwt_interleave_rows(row, height, mult);
365 // Convolve B-spline filter over columns: for each pixel in the current row, compute vertical blur
366 _bspline_vertical_pass(in, temp, i, width, height, mult, TRUE); // always clip negatives
367 // Convolve B-spline filter horizontally over current row
368 for(size_t j = 0; j < width; j++)
369 {
370 const size_t index = 4U * (i * width + j);
371 _bspline_horizontal(temp, LF + index, j, width, mult, TRUE); // always clip negatives
372 // compute the HF component by subtracting the LF from the original input
374 HF[index + c] = in[index + c] - LF[index + c];
375 }
376 }
377
378}
379
380#endif // DT_PIXEL_BSPLINE_H
381
382// clang-format off
383// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
384// vim: shiftwidth=2 expandtab tabstop=2 cindent
385// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
386// clang-format on
#define TRUE
Definition ashift_lsd.c:162
static unsigned int num_steps_to_reach_equivalent_sigma(const float sigma_filter, const float sigma_final)
Definition bspline.h:65
static void _bspline_horizontal_decimated(const float *const restrict temp, float *const restrict out, const size_t col, const size_t width, const gboolean clip_negatives)
Definition bspline.h:153
static void reduce_2D_Bspline(const float *const restrict in, float *const restrict out, const size_t width, const size_t height, float *const restrict tempbuf, const size_t padded_size, const gboolean clip_negatives)
Definition bspline.h:169
static void _bspline_horizontal(const float *const restrict temp, float *const restrict out, size_t col, size_t width, int mult, const gboolean clip_negatives)
Definition bspline.h:136
static void sparse_scalar_product(const dt_aligned_pixel_t buf, const size_t indices[5], dt_aligned_pixel_t result, const gboolean clip_negatives)
Definition bspline.h:83
static float equivalent_sigma_at_step(const float sigma, const unsigned int s)
Definition bspline.h:52
static void blur_2D_Bspline(const float *const restrict in, float *const restrict out, float *const restrict tempbuf, const size_t width, const size_t height, const int mult, const gboolean clip_negatives)
Definition bspline.h:329
static void expand_2D_Bspline(const float *const restrict in, float *const restrict out, const size_t width, const size_t height, const gboolean clip_negatives)
Definition bspline.h:224
#define BSPLINE_FSIZE
Definition bspline.h:34
static float normalize_laplacian(const float sigma)
Definition bspline.h:40
static void decompose_2D_Bspline(const float *const restrict in, float *const restrict HF, float *const restrict LF, const size_t width, const size_t height, const int mult, float *const tempbuf, size_t padded_size)
Definition bspline.h:351
static size_t decimated_bspline_size(const size_t size)
Definition bspline.h:79
static void _bspline_vertical_pass(const float *const restrict in, float *const restrict temp, size_t row, size_t width, size_t height, int mult, const gboolean clip_negatives)
Definition bspline.h:117
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
static int dwt_interleave_rows(const int rowid, const int height, const int stride)
Definition dwt.h:93
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
static int dt_get_thread_num()
Index of the calling thread within its parallel region, 0 outside one.
Definition openmp.h:129
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define dt_get_perthread(buf, padsize)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
static void copy_pixel_nontemporal(float *const __restrict__ out, const float *const __restrict__ in)
Definition simd.h:207
#define for_four_channels(_var,...)
Definition simd.h:89
const float sigma
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29