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 /* isx against the WIDTH, isy against the HEIGHT. This read `isy >= b->buf->width`,
235 which never bounds-checked isx at all and tested isy twice. The comment above says
236 what the test is for -- "if not out of buf" -- so the intent is unambiguous. It is
237 not a memory-safety bug, because both coordinates are CLAMPed in the else branch,
238 but it picks the wrong branch: a pixel past the right edge sampled the preview with
239 px clamped to the last column instead of taking the hi-res fallback, and on a
240 portrait image every row below `width` took the fallback instead of the preview. */\
241 if(isx < 0 || isx >= b->buf->width\
242 || isy < 0 || isy >= b->buf->height)\
243 out[*wd2*j+i] = (fallback);\
244 else\
245 {\
246 int px = CLAMP(isx / (float)b->buf->width * b->wd + (b->pwd-b->wd)/2, 0, b->pwd-1);\
247 int py = CLAMP(isy / (float)b->buf->height * b->ht + (b->pht-b->ht)/2, 0, b->pht-1);\
248 /* TODO: linear interpolation?*/\
249 out[*wd2*j+i] = b->pad0[b->pwd*py+px];\
250 } } while(0)
251 __OMP_PARALLEL_FOR__(collapse(2)) // left border
252 for(int j=max_supp;j<*ht2-max_supp;j++) for(int i=0;i<max_supp;i++)
253 LL_FILL(input[stride*wd*(j-max_supp)]* 0.01f);
254 __OMP_PARALLEL_FOR__(collapse(2)) // right border
255 for(int j=max_supp;j<*ht2-max_supp;j++) for(int i=wd+max_supp;i<*wd2;i++)
256 LL_FILL(input[stride*((j-max_supp)*wd+wd-1)] * 0.01f);
257 __OMP_PARALLEL_FOR__(collapse(2)) // top border
258 for(int j=0;j<max_supp;j++) for(int i=0;i<*wd2;i++)
259 LL_FILL(out[*wd2*max_supp+i]);
260 __OMP_PARALLEL_FOR__(collapse(2)) // bottom border
261 for(int j=max_supp+ht;j<*ht2;j++) for(int i=0;i<*wd2;i++)
262 LL_FILL(out[*wd2*(max_supp+ht-1)+i]);
263#undef LL_FILL
264 }
265 else
266 { // pad by replication:
268 for(int j=0;j<ht;j++)
269 {
270 for(int i=0;i<max_supp;i++)
271 out[(j+max_supp)**wd2+i] = input[stride*wd*j]* 0.01f; // L -> [0,1]
272 for(int i=0;i<wd;i++)
273 out[(j+max_supp)**wd2+i+max_supp] = input[stride*(wd*j+i)] * 0.01f; // L -> [0,1]
274 for(int i=wd+max_supp;i<*wd2;i++)
275 out[(j+max_supp)**wd2+i] = input[stride*(j*wd+wd-1)] * 0.01f; // L -> [0,1]
276 }
277 pad_by_replication(out, *wd2, *ht2, max_supp);
278 }
279#ifdef DEBUG_DUMP
280 if(b && b->mode == 2)
281 {
282 dump_PFM("/tmp/padded.pfm",out,*wd2,*ht2);
284#endif
285 return out;
286}
287
288
289static inline float ll_laplacian(
290 const float *const coarse, // coarse res gaussian
291 const float *const fine, // fine res gaussian
292 const int i, // fine index
293 const int j,
294 const int wd, // fine width
295 const int ht) // fine height
297 const float c = ll_expand_gaussian(coarse,
298 CLAMPS(i, 1, ((wd-1)&~1)-1), CLAMPS(j, 1, ((ht-1)&~1)-1), wd, ht);
299 return fine[j*wd+i] - c;
300}
301
302static inline float curve_scalar(
303 const float x,
304 const float g,
305 const float sigma,
306 const float shadows,
307 const float highlights,
308 const float clarity)
309{
310 const float c = x-g;
311 float val;
312 // blend in via quadratic bezier
313 if (c > 2*sigma) val = g + sigma + shadows * (c-sigma);
314 else if(c < -2*sigma) val = g - sigma + highlights * (c+sigma);
315 else if(c > 0.0f)
316 { // shadow 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*shadows);
321 }
322 else
323 { // highlight contrast
324 const float t = CLAMPS(-c / (2.0f*sigma), 0.0f, 1.0f);
325 const float t2 = t * t;
326 const float mt = 1.0f-t;
327 val = g - sigma * 2.0f*mt*t + t2*(- sigma - sigma*highlights);
328 }
329 // midtone local contrast
330 val += clarity * c * expf(-c*c/(2.0*sigma*sigma/3.0f));
331 return val;
332}
333
334// scalar version
335void apply_curve(
336 float *const out,
337 const float *const in,
338 const uint32_t w,
339 const uint32_t h,
340 const uint32_t padding,
341 const float g,
342 const float sigma,
343 const float shadows,
344 const float highlights,
345 const float clarity)
346{
348 for(uint32_t j=padding;j<h-padding;j++)
349 {
350 const float *in2 = in + j*w + padding;
351 float *out2 = out + j*w + padding;
352 for(uint32_t i=padding;i<w-padding;i++)
353 (*out2++) = curve_scalar(*(in2++), g, sigma, shadows, highlights, clarity);
354 out2 = out + j*w;
355 for(int i=0;i<padding;i++) out2[i] = out2[padding];
356 for(int i=w-padding;i<w;i++) out2[i] = out2[w-padding-1];
357 }
358 pad_by_replication(out, w, h, padding);
359}
360
362 const float *const input, // input buffer in some Labx or yuvx format
363 float *const out, // output buffer with colour
364 const int wd, // width and
365 const int ht, // height of the input buffer
366 const float sigma, // user param: separate shadows/mid-tones/highlights
367 const float shadows, // user param: lift shadows
368 const float highlights, // user param: compress highlights
369 const float clarity, // user param: increase clarity/local contrast
370 const int use_sse2, // flag whether to use SSE version
372{
373 if(wd <= 1 || ht <= 1) return 0;
374
375 // don't divide by 2 more often than we can:
376 const int num_levels = MIN(max_levels, 31-__builtin_clz(MIN(wd,ht)));
377
378 /* Two levels are the minimum this can work with, and the guard above does not give that.
379 *
380 * 31-clz(x) is 1 for x of 2 or 3, so an input only 2 or 3 pixels on its short side yields
381 * num_levels == 1 and last_level == 0. The coarsest-level reduction below then reads
382 * padded[last_level-1], i.e. padded[-1] -- off the front of a stack array -- and hands
383 * whatever pointer lives there to gauss_reduce() as its input buffer.
384 *
385 * That is Sentry 137433784, and every number in its backtrace agrees: gauss_reduce was
386 * called with wd=5, ht=227, which is dl(w,-1) returning w unchanged (its loop body never
387 * runs for a negative level) with w = 2*max_supp + 3 = 5 and h = 2*max_supp + 225 = 227 for
388 * max_supp = 1<<0. So the image was three pixels wide, and the fault was the stencil
389 * dereferencing that garbage pointer -- not the index arithmetic, which is in bounds for
390 * those dimensions.
391 *
392 * Nothing is lost by declining: a 3-pixel-wide strip has no local contrast to enhance, and
393 * the caller already handles this returning 0 the same way it does for a 1-pixel input. */
394 if(num_levels < 2) return 0;
395
396 int last_level = num_levels-1;
397 if(b && b->mode == 2) // higher number here makes it less prone to aliasing and slower.
398 last_level = num_levels > 4 ? 4 : num_levels-1;
399 const int max_supp = 1<<last_level;
400 int w, h;
401 int err = 0;
402 // All pyramid buffer arrays must be declared and zero-initialized up here, before any
403 // `goto error`. The error path frees padded[]/output[]/buf[][]; if output/buf were declared
404 // further down (past an early `goto error` from a failed padded[] allocation), the goto would
405 // skip their `= {0}` initializers, leaving indeterminate pointers that the cleanup then frees
406 // -> SIGSEGV under memory pressure when an allocation fails (Sentry #129715026).
407 float *padded[max_levels] = {0};
408 float *output[max_levels] = {0};
409 float *buf[num_gamma][max_levels] = {{0}};
410 if(b && b->mode == 2)
411 padded[0] = ll_pad_input(input, wd, ht, max_supp, &w, &h, b);
412 else
413 padded[0] = ll_pad_input(input, wd, ht, max_supp, &w, &h, 0);
414 if(padded[0] == NULL)
415 {
416 err = 1;
417 goto error;
418 }
419
420 // allocate pyramid pointers for padded input
421 for(int l=1;l<=last_level;l++)
422 {
423 padded[l] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)dl(w,l) * dl(h,l), 0);
424 if(padded[l] == NULL)
425 {
426 err = 1;
427 goto error;
428 }
429 }
430
431 // allocate pyramid pointers for output (declared at the top of the function)
432 for(int l=0;l<=last_level;l++)
433 {
434 output[l] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)dl(w,l) * dl(h,l), 0);
435 if(output[l] == NULL)
436 {
437 err = 1;
438 goto error;
439 }
440 }
441
442 // create gauss pyramid of padded input, write coarse directly to output
443 for(int l=1;l<last_level;l++)
444 gauss_reduce(padded[l-1], padded[l], dl(w,l-1), dl(h,l-1));
445 gauss_reduce(padded[last_level-1], output[last_level], dl(w,last_level-1), dl(h,last_level-1));
446
447 // evenly sample brightness [0,1]:
448 float gamma[num_gamma] = {0.0f};
449 for(int k=0;k<num_gamma;k++) gamma[k] = (k+.5f)/(float)num_gamma;
450 // for(int k=0;k<num_gamma;k++) gamma[k] = k/(num_gamma-1.0f);
451
452 // allocate memory for intermediate laplacian pyramids (declared at the top of the function)
453 for(int k=0;k<num_gamma;k++) for(int l=0;l<=last_level;l++)
454 {
455 buf[k][l] = dt_pixelpipe_cache_alloc_align_float_cache((size_t)dl(w,l)*dl(h,l), 0);
456 if(buf[k][l] == NULL)
457 {
458 err = 1;
459 goto error;
460 }
461 }
462
463 // the paper says remapping only level 3 not 0 does the trick, too
464 // (but i really like the additional octave of sharpness we get,
465 // willing to pay the cost).
466 for(int k=0;k<num_gamma;k++)
467 { // process images
468 apply_curve(buf[k][0], padded[0], w, h, max_supp, gamma[k], sigma, shadows, highlights, clarity);
469
470 // create gaussian pyramids
471 for(int l=1;l<=last_level;l++)
472 gauss_reduce(buf[k][l-1], buf[k][l], dl(w,l-1), dl(h,l-1));
473 }
474
475 // resample output[last_level] from preview
476 // requires to transform from padded/downsampled to full image and then
477 // to padded/downsampled in preview
478 if(b && b->mode == 2)
479 {
480 const float isize = powf(2.0f, last_level) / b->roi->scale; // pixel size of coarsest level in image space
481 const float psize = isize / b->buf->width * b->wd; // pixel footprint rescaled to preview buffer
482 const float pl = log2f(psize); // mip level in preview buffer
483 const int pl0 = CLAMP((int)pl, 0, b->num_levels-1), pl1 = CLAMP((int)(pl+1), 0, b->num_levels-1);
484 const float weight = CLAMP(pl-pl0, 0, 1); // weight between mip levels
485 const float mul0 = 1.0/powf(2.0f, pl0);
486 const float mul1 = 1.0/powf(2.0f, pl1);
487 const float mul = powf(2.0f, last_level);
488 const int pw = dl(w,last_level), ph = dl(h,last_level);
489 const int pw0 = dl(b->pwd, pl0), ph0 = dl(b->pht, pl0);
490 const int pw1 = dl(b->pwd, pl1), ph1 = dl(b->pht, pl1);
491 debug_dump_PFM("/tmp/coarse.pfm", b->output[pl0], pw0, ph0);
492 debug_dump_PFM("/tmp/oldcoarse.pfm", output[last_level], pw, ph);
493#ifdef _OPENMP
494#pragma omp parallel for collapse(2) default(shared)
495#endif
496 for(int j=0;j<ph;j++) for(int i=0;i<pw;i++)
497 {
498 // image coordinates in full buffer
499 float ix = ((i*mul - max_supp) + b->roi->x)/b->roi->scale;
500 float iy = ((j*mul - max_supp) + b->roi->y)/b->roi->scale;
501 // coordinates in padded preview buffer (
502 float px = CLAMP(ix / (float)b->buf->width * b->wd + (b->pwd-b->wd)/2.0f, 0, b->pwd);
503 float py = CLAMP(iy / (float)b->buf->height * b->ht + (b->pht-b->ht)/2.0f, 0, b->pht);
504 // trilinear lookup:
505 int px0 = CLAMP(px*mul0, 0, pw0-1);
506 int py0 = CLAMP(py*mul0, 0, ph0-1);
507 int px1 = CLAMP(px*mul1, 0, pw1-1);
508 int py1 = CLAMP(py*mul1, 0, ph1-1);
509#if 1
510 float f0x = CLAMP(px*mul0 - px0, 0.0f, 1.0f);
511 float f0y = CLAMP(py*mul0 - py0, 0.0f, 1.0f);
512 float f1x = CLAMP(px*mul1 - px1, 0.0f, 1.0f);
513 float f1y = CLAMP(py*mul1 - py1, 0.0f, 1.0f);
514 float c0 =
515 (1.0f-f0x)*(1.0f-f0y)*b->output[pl0][CLAMP(py0 , 0, ph0-1)*pw0 + CLAMP(px0 , 0, pw0-1)]+
516 ( f0x)*(1.0f-f0y)*b->output[pl0][CLAMP(py0 , 0, ph0-1)*pw0 + CLAMP(px0+1, 0, pw0-1)]+
517 (1.0f-f0x)*( f0y)*b->output[pl0][CLAMP(py0+1, 0, ph0-1)*pw0 + CLAMP(px0 , 0, pw0-1)]+
518 ( f0x)*( f0y)*b->output[pl0][CLAMP(py0+1, 0, ph0-1)*pw0 + CLAMP(px0+1, 0, pw0-1)];
519 float c1 =
520 (1.0f-f1x)*(1.0f-f1y)*b->output[pl1][CLAMP(py1 , 0, ph1-1)*pw1 + CLAMP(px1 , 0, pw1-1)]+
521 ( f1x)*(1.0f-f1y)*b->output[pl1][CLAMP(py1 , 0, ph1-1)*pw1 + CLAMP(px1+1, 0, pw1-1)]+
522 (1.0f-f1x)*( f1y)*b->output[pl1][CLAMP(py1+1, 0, ph1-1)*pw1 + CLAMP(px1 , 0, pw1-1)]+
523 ( f1x)*( f1y)*b->output[pl1][CLAMP(py1+1, 0, ph1-1)*pw1 + CLAMP(px1+1, 0, pw1-1)];
524#else
525 float c0 = b->output[pl0][py0*pw0 + px0];
526 float c1 = b->output[pl1][py1*pw1 + px1];
527#endif
528 output[last_level][j*pw+i] = weight * c1 + (1.0f-weight) * c0;
529 }
530 debug_dump_PFM("/tmp/newcoarse.pfm", output[last_level], pw, ph);
531 }
532
533 // assemble output pyramid coarse to fine
534 for(int l=last_level-1;l >= 0; l--)
535 {
536 const int pw = dl(w,l), ph = dl(h,l);
537
538 gauss_expand(output[l+1], output[l], pw, ph);
539 // go through all coefficients in the upsampled gauss buffer:
540 __OMP_PARALLEL_FOR__(collapse(2))
541 for(int j=0;j<ph;j++) for(int i=0;i<pw;i++)
542 {
543 const float v = padded[l][j*pw+i];
544 int hi = 1;
545 for(;hi<num_gamma-1 && gamma[hi] <= v;hi++);
546 int lo = hi-1;
547 const float a = CLAMPS((v - gamma[lo])/(gamma[hi]-gamma[lo]), 0.0f, 1.0f);
548 const float l0 = ll_laplacian(buf[lo][l+1], buf[lo][l], i, j, pw, ph);
549 const float l1 = ll_laplacian(buf[hi][l+1], buf[hi][l], i, j, pw, ph);
550 output[l][j*pw+i] += l0 * (1.0f-a) + l1 * a;
551 // we could do this to save on memory (no need for finest buf[][]).
552 // unfortunately it results in a quite noticeable loss of sharpness, i think
553 // the extra level is worth it.
554 // else if(l == 0) // use finest scale from input to not amplify noise (and use less memory)
555 // output[l][j*pw+i] += ll_laplacian(padded[l+1], padded[l], i, j, pw, ph);
556 }
557 }
558 __OMP_PARALLEL_FOR__(collapse(2))
559 for(int j=0;j<ht;j++) for(int i=0;i<wd;i++)
560 {
561 out[4*(j*wd+i)+0] = 100.0f * output[0][(j+max_supp)*w+max_supp+i]; // [0,1] -> L
562 out[4*(j*wd+i)+1] = input[4*(j*wd+i)+1]; // copy original colour channels
563 out[4*(j*wd+i)+2] = input[4*(j*wd+i)+2];
564 }
565 if(b && b->mode == 1)
566 { // output the buffers for later re-use
567 b->pad0 = padded[0];
568 b->wd = wd;
569 b->ht = ht;
570 b->pwd = w;
571 b->pht = h;
572 b->num_levels = num_levels;
573 for(int l=0;l<num_levels;l++) b->output[l] = output[l];
574 }
575
576error:;
577 // free all buffers except the ones passed out for preview rendering
578 const int keep_preview = (b && b->mode == 1 && err == 0);
579 for(int l=0;l<max_levels;l++)
580 {
581 if(!keep_preview || l)
583 if(!keep_preview)
585 for(int k=0; k<num_gamma;k++)
587 }
588 return err;
589}
590
591
592size_t local_laplacian_memory_use(const int width, // width of input image
593 const int height) // height of input image
594{
595 const int num_levels = MIN(max_levels, 31-__builtin_clz(MIN(width,height)));
596 const int max_supp = 1<<(num_levels-1);
597 const int paddwd = width + 2*max_supp;
598 const int paddht = height + 2*max_supp;
599
600 size_t memory_use = 0;
601
602 for(int l=0;l<num_levels;l++)
603 memory_use += sizeof(float) * (2 + num_gamma) * dl(paddwd, l) * dl(paddht, l);
604
605 return memory_use;
606}
607
608size_t local_laplacian_singlebuffer_size(const int width, // width of input image
609 const int height) // height of input image
610{
611 const int num_levels = MIN(max_levels, 31-__builtin_clz(MIN(width,height)));
612 const int max_supp = 1<<(num_levels-1);
613 const int paddwd = width + 2*max_supp;
614 const int paddht = height + 2*max_supp;
615
616 return sizeof(float) * dl(paddwd, 0) * dl(paddht, 0);
617}
618// clang-format off
619// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
620// vim: shiftwidth=2 expandtab tabstop=2 cindent
621// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
622// 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
for(size_t c=0;c< 3;c++) sRGB[c]
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:96
#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:95
#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