Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
locallaplacian.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2016-2017 johannes hanika.
4 Copyright (C) 2016 Maximilian Trescher.
5 Copyright (C) 2017, 2019 luzpaz.
6 Copyright (C) 2017 Peter Budai.
7 Copyright (C) 2017 Ulrich Pegelow.
8 Copyright (C) 2019 Andreas Schneider.
9 Copyright (C) 2019-2020, 2025-2026 Aurélien PIERRE.
10 Copyright (C) 2019 Roman Lebedev.
11 Copyright (C) 2020 Hubert Kowalski.
12 Copyright (C) 2020-2021 Pascal Obry.
13 Copyright (C) 2020-2021 Ralf Brown.
14 Copyright (C) 2021 Chris Elston.
15 Copyright (C) 2021 Hanno Schwalm.
16 Copyright (C) 2022 Martin Bařinka.
17
18 darktable is free software: you can redistribute it and/or modify
19 it under the terms of the GNU General Public License as published by
20 the Free Software Foundation, either version 3 of the License, or
21 (at your option) any later version.
22
23 darktable is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27
28 You should have received a copy of the GNU General Public License
29 along with darktable. If not, see <http://www.gnu.org/licenses/>.
30*/
31
32#include "system/macros.h"
33#include "pixel/format.h" // dt_iop_roi_t
34#include "system/openmp.h"
37#include "math/math.h"
38
39#include <string.h>
40#include <stdint.h>
41#include <stdlib.h>
42#include <assert.h>
43#include <stdio.h>
44
45// the maximum number of levels for the gaussian pyramid
46#define max_levels 30
47// the number of segments for the piecewise linear interpolation
48#define num_gamma 6
49
50//#define DEBUG_DUMP
51
52// downsample width/height to given level
53static inline int dl(int size, const int level)
54{
55 for(int l=0;l<level;l++)
56 size = (size-1)/2+1;
57 return size;
58}
59
60#ifdef DEBUG_DUMP
61static void dump_PFM(const char *filename, const float* out, const uint32_t w, const uint32_t h)
62{
63 FILE *f = g_fopen(filename, "wb");
64 fprintf(f, "PF\n%d %d\n-1.0\n", w, h);
65 for(int j=0;j<h;j++)
66 for(int i=0;i<w;i++)
67 for(int c=0;c<3;c++)
68 fwrite(out + w*j+i, 1, sizeof(float), f);
69 fclose(f);
70}
71#define debug_dump_PFM dump_PFM
72#else
73#define debug_dump_PFM(f,b,w,h)
74#endif
75
76// needs a boundary of 1 or 2px around i,j or else it will crash.
77// (translates to a 1px boundary around the corresponding pixel in the coarse buffer)
78// more precisely, 1<=i<wd-1 for even wd and
79// 1<=i<wd-2 for odd wd (j likewise with ht)
80static inline float ll_expand_gaussian(
81 const float *const coarse,
82 const int i,
83 const int j,
84 const int wd,
85 const int ht)
86{
87 assert(i > 0);
88 assert(i < wd-1);
89 assert(j > 0);
90 assert(j < ht-1);
91 assert(j/2 + 1 < (ht-1)/2+1);
92 assert(i/2 + 1 < (wd-1)/2+1);
93 const int cw = (wd-1)/2+1;
94 const int ind = (j/2)*cw+i/2;
95 // case 0: case 1: case 2: case 3:
96 // x . x . x x . x . x x . x . x x . x . x
97 // . . . . . . . . . . . .[.]. . .[.]. . .
98 // x .[x]. x x[.]x . x x . x . x x . x . x
99 // . . . . . . . . . . . . . . . . . . . .
100 // x . x . x x . x . x x . x . x x . x . x
101 switch((i&1) + 2*(j&1))
102 {
103 case 0: // both are even, 3x3 stencil
104 return 4./256. * (
105 6.0f*(coarse[ind-cw] + coarse[ind-1] + 6.0f*coarse[ind] + coarse[ind+1] + coarse[ind+cw])
106 + coarse[ind-cw-1] + coarse[ind-cw+1] + coarse[ind+cw-1] + coarse[ind+cw+1]);
107 case 1: // i is odd, 2x3 stencil
108 return 4./256. * (
109 24.0*(coarse[ind] + coarse[ind+1]) +
110 4.0*(coarse[ind-cw] + coarse[ind-cw+1] + coarse[ind+cw] + coarse[ind+cw+1]));
111 case 2: // j is odd, 3x2 stencil
112 return 4./256. * (
113 24.0*(coarse[ind] + coarse[ind+cw]) +
114 4.0*(coarse[ind-1] + coarse[ind+1] + coarse[ind+cw-1] + coarse[ind+cw+1]));
115 default: // case 3: // both are odd, 2x2 stencil
116 return .25f * (coarse[ind] + coarse[ind+1] + coarse[ind+cw] + coarse[ind+cw+1]);
117 }
118}
119
120// helper to fill in one pixel boundary by copying it
121static inline void ll_fill_boundary1(
122 float *const input,
123 const int wd,
124 const int ht)
125{
126 for(int j=1;j<ht-1;j++) input[j*wd] = input[j*wd+1];
127 for(int j=1;j<ht-1;j++) input[j*wd+wd-1] = input[j*wd+wd-2];
128 memcpy(input, input+wd, sizeof(float)*wd);
129 memcpy(input+wd*(ht-1), input+wd*(ht-2), sizeof(float)*wd);
130}
131
132// helper to fill in two pixels boundary by copying it
133static inline void ll_fill_boundary2(
134 float *const input,
135 const int wd,
136 const int ht)
137{
138 for(int j=1;j<ht-1;j++) input[j*wd] = input[j*wd+1];
139 if(wd & 1) for(int j=1;j<ht-1;j++) input[j*wd+wd-1] = input[j*wd+wd-2];
140 else for(int j=1;j<ht-1;j++) input[j*wd+wd-1] = input[j*wd+wd-2] = input[j*wd+wd-3];
141 memcpy(input, input+wd, sizeof(float)*wd);
142 if(!(ht & 1)) memcpy(input+wd*(ht-2), input+wd*(ht-3), sizeof(float)*wd);
143 memcpy(input+wd*(ht-1), input+wd*(ht-2), sizeof(float)*wd);
144}
145
147 float *buf, // the buffer to be padded
148 const uint32_t w, // width of a line
149 const uint32_t h, // total height, including top and bottom padding
150 const uint32_t padding) // number of lines of padding on each side
151{
153 for(int j=0;j<padding;j++)
154 {
155 memcpy(buf + w*j, buf+padding*w, sizeof(float)*w);
156 memcpy(buf + w*(h-padding+j), buf+w*(h-padding-1), sizeof(float)*w);
157 }
158}
159
160static inline void gauss_expand(
161 const float *const input, // coarse input
162 float *const fine, // upsampled, blurry output
163 const int wd, // fine res
164 const int ht)
165{
166 __OMP_PARALLEL_FOR__(collapse(2))
167 for(int j=1;j<((ht-1)&~1);j++) // even ht: two px boundary. odd ht: one px.
168 for(int i=1;i<((wd-1)&~1);i++)
169 fine[j*wd+i] = ll_expand_gaussian(input, i, j, wd, ht);
170 ll_fill_boundary2(fine, wd, ht);
171}
172
173static inline void gauss_reduce(
174 const float *const input, // fine input buffer
175 float *const coarse, // coarse scale, blurred input buf
176 const int wd, // fine res
177 const int ht)
178{
179 // blur, store only coarse res
180 const int cw = (wd-1)/2+1, ch = (ht-1)/2+1;
181
182 // this is the scalar (non-simd) code:
183 const float w[5] = { 1.f/16.f, 4.f/16.f, 6.f/16.f, 4.f/16.f, 1.f/16.f };
184 memset(coarse, 0, sizeof(float)*cw*ch);
185 // direct 5x5 stencil only on required pixels:
186#ifdef _OPENMP
187 // DON'T parallelize the very smallest levels of the pyramid, as the threading overhead
188 // is greater than the time needed to do it sequentially
189#pragma omp parallel for default(firstprivate) if (ch*cw>500) \
190 collapse(2)
191#endif
192 for(int j=1;j<ch-1;j++)
193 for(int i=1;i<cw-1;i++)
194 {
195 for(int jj=-2;jj<=2;jj++)
196 for(int ii=-2;ii<=2;ii++)
197 coarse[j*cw+i] += input[(2*j+jj)*wd+2*i+ii] * w[ii+2] * w[jj+2];
198 }
199 ll_fill_boundary1(coarse, cw, ch);
200}
201
202// allocate output buffer with monochrome brightness channel from input, padded
203// up by max_supp on all four sides, dimensions written to wd2 ht2
204static inline float *ll_pad_input(
205 const float *const input,
206 const int wd,
207 const int ht,
208 const int max_supp,
209 int *wd2,
210 int *ht2,
212{
213 const int stride = 4;
214 *wd2 = 2*max_supp + wd;
215 *ht2 = 2*max_supp + ht;
216 float *const out = dt_pixelpipe_cache_alloc_align_float_cache((size_t) *wd2 * *ht2, 0);
217 if(IS_NULL_PTR(out)) return NULL;
218
219 if(b && b->mode == 2)
220 { // pad by preview buffer
221 __OMP_PARALLEL_FOR__(collapse(2)) // fill regular pixels:
222 for(int j=0;j<ht;j++) for(int i=0;i<wd;i++)
223 out[(j+max_supp)**wd2+i+max_supp] = input[stride*(wd*j+i)] * 0.01f; // L -> [0,1]
224
225 // for all out of roi pixels on the boundary we wish to pad:
226 // compute coordinate in full image.
227 // if not out of buf:
228 // compute padded preview pixel coordinate (clamp to padded preview buffer size)
229 // else
230 // pad as usual (hi-res sample and hold)
231#define LL_FILL(fallback) do {\
232 float isx = ((i - max_supp) + b->roi->x)/b->roi->scale;\
233 float isy = ((j - max_supp) + b->roi->y)/b->roi->scale;\
234 if(isx < 0 || isy >= b->buf->width\
235 || isy < 0 || isy >= b->buf->height)\
236 out[*wd2*j+i] = (fallback);\
237 else\
238 {\
239 int px = CLAMP(isx / (float)b->buf->width * b->wd + (b->pwd-b->wd)/2, 0, b->pwd-1);\
240 int py = CLAMP(isy / (float)b->buf->height * b->ht + (b->pht-b->ht)/2, 0, b->pht-1);\
241 /* TODO: linear interpolation?*/\
242 out[*wd2*j+i] = b->pad0[b->pwd*py+px];\
243 } } while(0)
244 __OMP_PARALLEL_FOR__(collapse(2)) // left border
245 for(int j=max_supp;j<*ht2-max_supp;j++) for(int i=0;i<max_supp;i++)
246 LL_FILL(input[stride*wd*(j-max_supp)]* 0.01f);
247 __OMP_PARALLEL_FOR__(collapse(2)) // right border
248 for(int j=max_supp;j<*ht2-max_supp;j++) for(int i=wd+max_supp;i<*wd2;i++)
249 LL_FILL(input[stride*((j-max_supp)*wd+wd-1)] * 0.01f);
250 __OMP_PARALLEL_FOR__(collapse(2)) // top border
251 for(int j=0;j<max_supp;j++) for(int i=0;i<*wd2;i++)
252 LL_FILL(out[*wd2*max_supp+i]);
253 __OMP_PARALLEL_FOR__(collapse(2)) // bottom border
254 for(int j=max_supp+ht;j<*ht2;j++) for(int i=0;i<*wd2;i++)
255 LL_FILL(out[*wd2*(max_supp+ht-1)+i]);
256#undef LL_FILL
257 }
258 else
259 { // pad by replication:
261 for(int j=0;j<ht;j++)
262 {
263 for(int i=0;i<max_supp;i++)
264 out[(j+max_supp)**wd2+i] = input[stride*wd*j]* 0.01f; // L -> [0,1]
265 for(int i=0;i<wd;i++)
266 out[(j+max_supp)**wd2+i+max_supp] = input[stride*(wd*j+i)] * 0.01f; // L -> [0,1]
267 for(int i=wd+max_supp;i<*wd2;i++)
268 out[(j+max_supp)**wd2+i] = input[stride*(j*wd+wd-1)] * 0.01f; // L -> [0,1]
269 }
270 pad_by_replication(out, *wd2, *ht2, max_supp);
271 }
272#ifdef DEBUG_DUMP
273 if(b && b->mode == 2)
274 {
275 dump_PFM("/tmp/padded.pfm",out,*wd2,*ht2);
276 }
277#endif
278 return out;
279}
280
281
282static inline float ll_laplacian(
283 const float *const coarse, // coarse res gaussian
284 const float *const fine, // fine res gaussian
285 const int i, // fine index
286 const int j,
287 const int wd, // fine width
288 const int ht) // fine height
289{
290 const float c = ll_expand_gaussian(coarse,
291 CLAMPS(i, 1, ((wd-1)&~1)-1), CLAMPS(j, 1, ((ht-1)&~1)-1), wd, ht);
292 return fine[j*wd+i] - c;
293}
294
295static inline float curve_scalar(
296 const float x,
297 const float g,
298 const float sigma,
299 const float shadows,
300 const float highlights,
301 const float clarity)
302{
303 const float c = x-g;
304 float val;
305 // blend in via quadratic bezier
306 if (c > 2*sigma) val = g + sigma + shadows * (c-sigma);
307 else if(c < -2*sigma) val = g - sigma + highlights * (c+sigma);
308 else if(c > 0.0f)
309 { // shadow contrast
310 const float t = CLAMPS(c / (2.0f*sigma), 0.0f, 1.0f);
311 const float t2 = t * t;
312 const float mt = 1.0f-t;
313 val = g + sigma * 2.0f*mt*t + t2*(sigma + sigma*shadows);
314 }
315 else
316 { // highlight contrast
317 const float t = CLAMPS(-c / (2.0f*sigma), 0.0f, 1.0f);
318 const float t2 = t * t;
319 const float mt = 1.0f-t;
320 val = g - sigma * 2.0f*mt*t + t2*(- sigma - sigma*highlights);
321 }
322 // midtone local contrast
323 val += clarity * c * expf(-c*c/(2.0*sigma*sigma/3.0f));
324 return val;
325}
326
327// scalar version
329 float *const out,
330 const float *const in,
331 const uint32_t w,
332 const uint32_t h,
333 const uint32_t padding,
334 const float g,
335 const float sigma,
336 const float shadows,
337 const float highlights,
338 const float clarity)
339{
341 for(uint32_t j=padding;j<h-padding;j++)
342 {
343 const float *in2 = in + j*w + padding;
344 float *out2 = out + j*w + padding;
345 for(uint32_t i=padding;i<w-padding;i++)
346 (*out2++) = curve_scalar(*(in2++), g, sigma, shadows, highlights, clarity);
347 out2 = out + j*w;
348 for(int i=0;i<padding;i++) out2[i] = out2[padding];
349 for(int i=w-padding;i<w;i++) out2[i] = out2[w-padding-1];
350 }
351 pad_by_replication(out, w, h, padding);
352}
353
355 const float *const input, // input buffer in some Labx or yuvx format
356 float *const out, // output buffer with colour
357 const int wd, // width and
358 const int ht, // height of the input buffer
359 const float sigma, // user param: separate shadows/mid-tones/highlights
360 const float shadows, // user param: lift shadows
361 const float highlights, // user param: compress highlights
362 const float clarity, // user param: increase clarity/local contrast
363 const int use_sse2, // flag whether to use SSE version
365{
366 if(wd <= 1 || ht <= 1) return 0;
367
368 // don't divide by 2 more often than we can:
369 const int num_levels = MIN(max_levels, 31-__builtin_clz(MIN(wd,ht)));
370 int last_level = num_levels-1;
371 if(b && b->mode == 2) // higher number here makes it less prone to aliasing and slower.
372 last_level = num_levels > 4 ? 4 : num_levels-1;
373 const int max_supp = 1<<last_level;
374 int w, h;
375 int err = 0;
376 // All pyramid buffer arrays must be declared and zero-initialized up here, before any
377 // `goto error`. The error path frees padded[]/output[]/buf[][]; if output/buf were declared
378 // further down (past an early `goto error` from a failed padded[] allocation), the goto would
379 // skip their `= {0}` initializers, leaving indeterminate pointers that the cleanup then frees
380 // -> SIGSEGV under memory pressure when an allocation fails (Sentry #129715026).
381 float *padded[max_levels] = {0};
382 float *output[max_levels] = {0};
383 float *buf[num_gamma][max_levels] = {{0}};
384 if(b && b->mode == 2)
385 padded[0] = ll_pad_input(input, wd, ht, max_supp, &w, &h, b);
386 else
387 padded[0] = ll_pad_input(input, wd, ht, max_supp, &w, &h, 0);
388 if(padded[0] == NULL)
389 {
390 err = 1;
391 goto error;
392 }
393
394 // allocate pyramid pointers for padded input
395 for(int l=1;l<=last_level;l++)
396 {
397 padded[l] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)dl(w,l) * dl(h,l), 0);
398 if(padded[l] == NULL)
399 {
400 err = 1;
401 goto error;
402 }
403 }
404
405 // allocate pyramid pointers for output (declared at the top of the function)
406 for(int l=0;l<=last_level;l++)
407 {
408 output[l] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)dl(w,l) * dl(h,l), 0);
409 if(output[l] == NULL)
410 {
411 err = 1;
412 goto error;
413 }
414 }
415
416 // create gauss pyramid of padded input, write coarse directly to output
417 for(int l=1;l<last_level;l++)
418 gauss_reduce(padded[l-1], padded[l], dl(w,l-1), dl(h,l-1));
419 gauss_reduce(padded[last_level-1], output[last_level], dl(w,last_level-1), dl(h,last_level-1));
420
421 // evenly sample brightness [0,1]:
422 float gamma[num_gamma] = {0.0f};
423 for(int k=0;k<num_gamma;k++) gamma[k] = (k+.5f)/(float)num_gamma;
424 // for(int k=0;k<num_gamma;k++) gamma[k] = k/(num_gamma-1.0f);
425
426 // allocate memory for intermediate laplacian pyramids (declared at the top of the function)
427 for(int k=0;k<num_gamma;k++) for(int l=0;l<=last_level;l++)
428 {
429 buf[k][l] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)dl(w,l)*dl(h,l), 0);
430 if(buf[k][l] == NULL)
431 {
432 err = 1;
433 goto error;
434 }
435 }
436
437 // the paper says remapping only level 3 not 0 does the trick, too
438 // (but i really like the additional octave of sharpness we get,
439 // willing to pay the cost).
440 for(int k=0;k<num_gamma;k++)
441 { // process images
442 apply_curve(buf[k][0], padded[0], w, h, max_supp, gamma[k], sigma, shadows, highlights, clarity);
443
444 // create gaussian pyramids
445 for(int l=1;l<=last_level;l++)
446 gauss_reduce(buf[k][l-1], buf[k][l], dl(w,l-1), dl(h,l-1));
447 }
448
449 // resample output[last_level] from preview
450 // requires to transform from padded/downsampled to full image and then
451 // to padded/downsampled in preview
452 if(b && b->mode == 2)
453 {
454 const float isize = powf(2.0f, last_level) / b->roi->scale; // pixel size of coarsest level in image space
455 const float psize = isize / b->buf->width * b->wd; // pixel footprint rescaled to preview buffer
456 const float pl = log2f(psize); // mip level in preview buffer
457 const int pl0 = CLAMP((int)pl, 0, b->num_levels-1), pl1 = CLAMP((int)(pl+1), 0, b->num_levels-1);
458 const float weight = CLAMP(pl-pl0, 0, 1); // weight between mip levels
459 const float mul0 = 1.0/powf(2.0f, pl0);
460 const float mul1 = 1.0/powf(2.0f, pl1);
461 const float mul = powf(2.0f, last_level);
462 const int pw = dl(w,last_level), ph = dl(h,last_level);
463 const int pw0 = dl(b->pwd, pl0), ph0 = dl(b->pht, pl0);
464 const int pw1 = dl(b->pwd, pl1), ph1 = dl(b->pht, pl1);
465 debug_dump_PFM("/tmp/coarse.pfm", b->output[pl0], pw0, ph0);
466 debug_dump_PFM("/tmp/oldcoarse.pfm", output[last_level], pw, ph);
467#ifdef _OPENMP
468#pragma omp parallel for collapse(2) default(shared)
469#endif
470 for(int j=0;j<ph;j++) for(int i=0;i<pw;i++)
471 {
472 // image coordinates in full buffer
473 float ix = ((i*mul - max_supp) + b->roi->x)/b->roi->scale;
474 float iy = ((j*mul - max_supp) + b->roi->y)/b->roi->scale;
475 // coordinates in padded preview buffer (
476 float px = CLAMP(ix / (float)b->buf->width * b->wd + (b->pwd-b->wd)/2.0f, 0, b->pwd);
477 float py = CLAMP(iy / (float)b->buf->height * b->ht + (b->pht-b->ht)/2.0f, 0, b->pht);
478 // trilinear lookup:
479 int px0 = CLAMP(px*mul0, 0, pw0-1);
480 int py0 = CLAMP(py*mul0, 0, ph0-1);
481 int px1 = CLAMP(px*mul1, 0, pw1-1);
482 int py1 = CLAMP(py*mul1, 0, ph1-1);
483#if 1
484 float f0x = CLAMP(px*mul0 - px0, 0.0f, 1.0f);
485 float f0y = CLAMP(py*mul0 - py0, 0.0f, 1.0f);
486 float f1x = CLAMP(px*mul1 - px1, 0.0f, 1.0f);
487 float f1y = CLAMP(py*mul1 - py1, 0.0f, 1.0f);
488 float c0 =
489 (1.0f-f0x)*(1.0f-f0y)*b->output[pl0][CLAMP(py0 , 0, ph0-1)*pw0 + CLAMP(px0 , 0, pw0-1)]+
490 ( f0x)*(1.0f-f0y)*b->output[pl0][CLAMP(py0 , 0, ph0-1)*pw0 + CLAMP(px0+1, 0, pw0-1)]+
491 (1.0f-f0x)*( f0y)*b->output[pl0][CLAMP(py0+1, 0, ph0-1)*pw0 + CLAMP(px0 , 0, pw0-1)]+
492 ( f0x)*( f0y)*b->output[pl0][CLAMP(py0+1, 0, ph0-1)*pw0 + CLAMP(px0+1, 0, pw0-1)];
493 float c1 =
494 (1.0f-f1x)*(1.0f-f1y)*b->output[pl1][CLAMP(py1 , 0, ph1-1)*pw1 + CLAMP(px1 , 0, pw1-1)]+
495 ( f1x)*(1.0f-f1y)*b->output[pl1][CLAMP(py1 , 0, ph1-1)*pw1 + CLAMP(px1+1, 0, pw1-1)]+
496 (1.0f-f1x)*( f1y)*b->output[pl1][CLAMP(py1+1, 0, ph1-1)*pw1 + CLAMP(px1 , 0, pw1-1)]+
497 ( f1x)*( f1y)*b->output[pl1][CLAMP(py1+1, 0, ph1-1)*pw1 + CLAMP(px1+1, 0, pw1-1)];
498#else
499 float c0 = b->output[pl0][py0*pw0 + px0];
500 float c1 = b->output[pl1][py1*pw1 + px1];
501#endif
502 output[last_level][j*pw+i] = weight * c1 + (1.0f-weight) * c0;
503 }
504 debug_dump_PFM("/tmp/newcoarse.pfm", output[last_level], pw, ph);
505 }
506
507 // assemble output pyramid coarse to fine
508 for(int l=last_level-1;l >= 0; l--)
509 {
510 const int pw = dl(w,l), ph = dl(h,l);
511
512 gauss_expand(output[l+1], output[l], pw, ph);
513 // go through all coefficients in the upsampled gauss buffer:
514 __OMP_PARALLEL_FOR__(collapse(2))
515 for(int j=0;j<ph;j++) for(int i=0;i<pw;i++)
516 {
517 const float v = padded[l][j*pw+i];
518 int hi = 1;
519 for(;hi<num_gamma-1 && gamma[hi] <= v;hi++);
520 int lo = hi-1;
521 const float a = CLAMPS((v - gamma[lo])/(gamma[hi]-gamma[lo]), 0.0f, 1.0f);
522 const float l0 = ll_laplacian(buf[lo][l+1], buf[lo][l], i, j, pw, ph);
523 const float l1 = ll_laplacian(buf[hi][l+1], buf[hi][l], i, j, pw, ph);
524 output[l][j*pw+i] += l0 * (1.0f-a) + l1 * a;
525 // we could do this to save on memory (no need for finest buf[][]).
526 // unfortunately it results in a quite noticeable loss of sharpness, i think
527 // the extra level is worth it.
528 // else if(l == 0) // use finest scale from input to not amplify noise (and use less memory)
529 // output[l][j*pw+i] += ll_laplacian(padded[l+1], padded[l], i, j, pw, ph);
530 }
531 }
532 __OMP_PARALLEL_FOR__(collapse(2))
533 for(int j=0;j<ht;j++) for(int i=0;i<wd;i++)
534 {
535 out[4*(j*wd+i)+0] = 100.0f * output[0][(j+max_supp)*w+max_supp+i]; // [0,1] -> L
536 out[4*(j*wd+i)+1] = input[4*(j*wd+i)+1]; // copy original colour channels
537 out[4*(j*wd+i)+2] = input[4*(j*wd+i)+2];
538 }
539 if(b && b->mode == 1)
540 { // output the buffers for later re-use
541 b->pad0 = padded[0];
542 b->wd = wd;
543 b->ht = ht;
544 b->pwd = w;
545 b->pht = h;
546 b->num_levels = num_levels;
547 for(int l=0;l<num_levels;l++) b->output[l] = output[l];
548 }
549
550error:;
551 // free all buffers except the ones passed out for preview rendering
552 const int keep_preview = (b && b->mode == 1 && err == 0);
553 for(int l=0;l<max_levels;l++)
554 {
555 if(!keep_preview || l)
557 if(!keep_preview)
559 for(int k=0; k<num_gamma;k++)
561 }
562 return err;
563}
564
565
566size_t local_laplacian_memory_use(const int width, // width of input image
567 const int height) // height of input image
568{
569 const int num_levels = MIN(max_levels, 31-__builtin_clz(MIN(width,height)));
570 const int max_supp = 1<<(num_levels-1);
571 const int paddwd = width + 2*max_supp;
572 const int paddht = height + 2*max_supp;
573
574 size_t memory_use = 0;
575
576 for(int l=0;l<num_levels;l++)
577 memory_use += sizeof(float) * (2 + num_gamma) * dl(paddwd, l) * dl(paddht, l);
578
579 return memory_use;
580}
581
582size_t local_laplacian_singlebuffer_size(const int width, // width of input image
583 const int height) // height of input image
584{
585 const int num_levels = MIN(max_levels, 31-__builtin_clz(MIN(width,height)));
586 const int max_supp = 1<<(num_levels-1);
587 const int paddwd = width + 2*max_supp;
588 const int paddht = height + 2*max_supp;
589
590 return sizeof(float) * dl(paddwd, 0) * dl(paddht, 0);
591}
592// clang-format off
593// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
594// vim: shiftwidth=2 expandtab tabstop=2 cindent
595// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
596// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
static const float x
const float f
const int t
const float l1
const float v
const dt_colormatrix_t dt_aligned_pixel_t out
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
size_t local_laplacian_memory_use(const int width, const int height)
static float * ll_pad_input(const float *const input, const int wd, const int ht, const int max_supp, int *wd2, int *ht2, local_laplacian_boundary_t *b)
static void pad_by_replication(float *buf, const uint32_t w, const uint32_t h, const uint32_t padding)
#define debug_dump_PFM(f, b, w, h)
#define LL_FILL(fallback)
static float ll_expand_gaussian(const float *const coarse, const int i, const int j, const int wd, const int ht)
#define num_gamma
int local_laplacian_internal(const float *const input, float *const out, const int wd, const int ht, const float sigma, const float shadows, const float highlights, const float clarity, const int use_sse2, local_laplacian_boundary_t *b)
static void gauss_reduce(const float *const input, float *const coarse, const int wd, const int ht)
#define max_levels
static int dl(int size, const int level)
static void ll_fill_boundary1(float *const input, const int wd, const int ht)
static void gauss_expand(const float *const input, float *const fine, const int wd, const int ht)
size_t local_laplacian_singlebuffer_size(const int width, const int height)
static float ll_laplacian(const float *const coarse, const float *const fine, const int i, const int j, const int wd, const int ht)
static float curve_scalar(const float x, const float g, const float sigma, const float shadows, const float highlights, const float clarity)
static void ll_fill_boundary2(float *const input, const int wd, const int ht)
void apply_curve(float *const out, const float *const in, const uint32_t w, const uint32_t h, const uint32_t padding, const float g, const float sigma, const float shadows, const float highlights, const float clarity)
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:65
#define CLAMPS(A, L, H)
Definition math.h:78
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:60
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
const float sigma
#define MIN(a, b)
Definition thinplate.c:32