Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
interpolation.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2012 Christian Tellefsen.
4 Copyright (C) 2012 Edouard Gomez.
5 Copyright (C) 2012 Jérémy Rosen.
6 Copyright (C) 2012 Richard Wonka.
7 Copyright (C) 2012-2016, 2019 Tobias Ellinghaus.
8 Copyright (C) 2012, 2014-2017 Ulrich Pegelow.
9 Copyright (C) 2013 Simon Spannagel.
10 Copyright (C) 2014-2016 Roman Lebedev.
11 Copyright (C) 2017-2018 luzpaz.
12 Copyright (C) 2019 Andreas Schneider.
13 Copyright (C) 2019, 2021, 2024-2026 Aurélien PIERRE.
14 Copyright (C) 2020-2021 Pascal Obry.
15 Copyright (C) 2020-2021 Ralf Brown.
16 Copyright (C) 2020-2021 Roman Khatko.
17 Copyright (C) 2021-2022 Hanno Schwalm.
18 Copyright (C) 2022 Martin Bařinka.
19 Copyright (C) 2024 Alynx Zhou.
20
21 darktable is free software: you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation, either version 3 of the License, or
24 (at your option) any later version.
25
26 darktable is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
30
31 You should have received a copy of the GNU General Public License
32 along with darktable. If not, see <http://www.gnu.org/licenses/>.
33*/
34
35#include "pixel/interpolation.h"
36#include "system/macros.h"
37#include "system/openmp.h"
39#include "system/mem_alloc.h"
40#include "system/simd.h"
42#include "math/math.h"
43#include "common/conf.h"
44#include "system/atomic.h" // the generation-keyed userpref cache below
45
46#include <assert.h>
47#include <glib.h>
48#include <inttypes.h>
49#include <stddef.h>
50#include <stdint.h>
51
54{
55 BORDER_REPLICATE, // aaaa|abcdefg|gggg
56 BORDER_WRAP, // defg|abcdefg|abcd
57 BORDER_MIRROR, // edcb|abcdefg|fedc
58 BORDER_CLAMP // ....|abcdefg|....
59};
60
61/* Supporting them all might be overkill, let the compiler trim all
62 * unnecessary modes in clip for resampling codepath*/
63#define RESAMPLING_BORDER_MODE BORDER_REPLICATE
64
65/* Supporting them all might be overkill, let the compiler trim all
66 * unnecessary modes in interpolation codepath */
67#define INTERPOLATION_BORDER_MODE BORDER_MIRROR
68
69// Defines the maximum kernel half length
70// !! Make sure to sync this with the filter array !!
71#define MAX_HALF_FILTER_WIDTH 3
72
73// Add *verbose* (like one msg per pixel out) debug message to stderr
74#define DEBUG_PRINT_VERBOSE 0
75
76/* --------------------------------------------------------------------------
77 * Debug helpers
78 * ------------------------------------------------------------------------*/
79
80
81/* --------------------------------------------------------------------------
82 * Generic helpers
83 * ------------------------------------------------------------------------*/
84
89static inline __attribute__((always_inline)) ssize_t _clip(ssize_t i,
90 const ssize_t min,
91 const ssize_t max,
92 enum border_mode mode)
93{
94 switch(mode)
95 {
97 if(i < min)
98 {
99 i = min;
100 }
101 else if(i > max)
102 {
103 i = max;
104 }
105 break;
106 case BORDER_MIRROR:
107 if(i < min)
108 {
109 // i == min - 1 --> min + 1
110 // i == min - 2 --> min + 2, etc.
111 // but as min == 0 in all current cases, this really optimizes to i = -i
112 i = min + (min - i);
113 }
114 else if(i > max)
115 {
116 // i == max + 1 --> max - 1
117 // i == max + 2 --> max - 2, etc.
118 i = max - (i - max);
119 }
120 break;
121 case BORDER_WRAP:
122 if(i < min)
123 {
124 i = 1 + max - (min - i);
125 }
126 else if(i > max)
127 {
128 i = min + (i - max) - 1;
129 }
130 break;
131 case BORDER_CLAMP:
132 if(i < min || i > max)
133 {
134 /* Should not be used as is, we prevent -1 usage, filtering the taps
135 * we clip the sample indexes for. So understand this function is
136 * specific to its caller. */
137 i = -1;
138 }
139 break;
140 }
141
142 return i;
143}
144
145static inline __attribute__((always_inline)) void _prepare_tap_boundaries(int *tap_first,
146 int *tap_last,
147 const enum border_mode mode,
148 const int filterwidth,
149 const int t,
150 const int max)
151{
152 /* Check lower bound pixel index and skip as many pixels as necessary to
153 * fall into range */
154 *tap_first = 0;
155 if(mode == BORDER_CLAMP && t < 0)
156 {
157 *tap_first = -t;
158 }
159
160 // Same for upper bound pixel
161 *tap_last = filterwidth;
162 if(mode == BORDER_CLAMP && t + filterwidth >= max)
163 {
164 *tap_last = max - t;
165 }
166}
167
168/* --------------------------------------------------------------------------
169 * Interpolation kernels
170 * ------------------------------------------------------------------------*/
171
172/* --------------------------------------------------------------------------
173 * Bilinear interpolation
174 * ------------------------------------------------------------------------*/
175
176static float _maketaps_bilinear(float *taps,
177 const size_t num_taps,
178 const float width,
179 const float first_tap,
180 const float interval)
181{
182 static const dt_aligned_pixel_simd_t bootstrap = { 0.0f, 1.0f, 2.0f, 3.0f };
183 const dt_aligned_pixel_simd_t interval_v = dt_simd_set1(interval);
184 const dt_aligned_pixel_simd_t iter = dt_simd_set1(4.0f * interval);
185 dt_aligned_pixel_simd_t vt = dt_simd_set1(first_tap) + bootstrap * interval_v;
186
187 const int runs = (num_taps + 3) / 4;
188
189 for(size_t i = 0; i < runs; i++)
190 {
191 dt_store_simd_aligned(taps + 4 * i, dt_simd_set1(1.0f) - dt_simd_abs(vt));
192 vt += iter;
193 }
194 return 1.0f; //kernel norm is 1.0f by construction
195}
196
197/* --------------------------------------------------------------------------
198 * Bicubic interpolation
199 * ------------------------------------------------------------------------*/
200
201static float _maketaps_bicubic(float *taps,
202 const size_t num_taps,
203 const float width,
204 const float first_tap,
205 const float interval)
206{
207 static const dt_aligned_pixel_simd_t bootstrap = { 0.0f, 1.0f, 2.0f, 3.0f };
208 const dt_aligned_pixel_simd_t half = dt_simd_set1(0.5f);
209 const dt_aligned_pixel_simd_t two = dt_simd_set1(2.0f);
210 const dt_aligned_pixel_simd_t three = dt_simd_set1(3.0f);
211 const dt_aligned_pixel_simd_t four = dt_simd_set1(4.0f);
212 const dt_aligned_pixel_simd_t five = dt_simd_set1(5.0f);
213 const dt_aligned_pixel_simd_t eight = dt_simd_set1(8.0f);
214 const dt_aligned_pixel_simd_t interval_v = dt_simd_set1(interval);
215 const dt_aligned_pixel_simd_t iter = dt_simd_set1(4.0f * interval);
216 dt_aligned_pixel_simd_t vt = dt_simd_set1(first_tap) + bootstrap * interval_v;
217
218 const int runs = (num_taps + 3) / 4;
219
220 for(size_t i = 0; i < runs; i++)
221 {
222 const dt_aligned_pixel_simd_t vt_abs = dt_simd_abs(vt);
223 const dt_aligned_pixel_simd_t t2 = vt * vt;
224 const dt_aligned_pixel_simd_t t5 = five * vt_abs;
225 const dt_aligned_pixel_simd_t r12 = (vt_abs * (t5 - eight - t2) + four) * half;
226 const dt_aligned_pixel_simd_t r01 = ((three * t2 - t5) * vt_abs + two) * half;
227 dt_aligned_pixel_simd_t taps4 = r12;
229 taps4[c] = (vt_abs[c] <= 1.0f) ? r01[c] : r12[c];
230 dt_store_simd_aligned(taps + 4 * i, taps4);
231 vt += iter;
232 }
233 return 1.0f; //kernel norm is 1.0f by construction
234}
235
236/* --------------------------------------------------------------------------
237 * Mitchell-Netravali interpolation (B = C = 1/3)
238 *
239 * A separable cubic from the Mitchell-Netravali (B,C) family (SIGGRAPH 1988).
240 * B = C = 1/3 is the classic general-purpose reconstruction filter: it trades a
241 * hair of sharpness for drastically reduced ringing versus interpolating cubics
242 * (Catmull-Rom) and windowed-sinc (Lanczos). Its negative excursion is tiny
243 * (~3% vs Lanczos3's much larger overshoot), so it is effectively halo-free on
244 * photographic content and never blows alpha/colour far out of range at edges.
245 *
246 * Piecewise weights (already divided by 6), support [-2, 2]:
247 * |t| < 1 : (7/6)|t|^3 - 2|t|^2 + 8/9
248 * 1<=|t|<2: -(7/18)|t|^3 + 2|t|^2 - (10/3)|t| + 16/9
249 * It is a partition of unity (taps sum to 1 on the integer grid), so the
250 * upsampling norm is 1 by construction like bilinear/bicubic; the downsampling
251 * path renormalizes from the summed taps separately.
252 * ------------------------------------------------------------------------*/
253
254static float _maketaps_mitchell(float *taps,
255 const size_t num_taps,
256 const float width,
257 const float first_tap,
258 const float interval)
259{
260 static const dt_aligned_pixel_simd_t bootstrap = { 0.0f, 1.0f, 2.0f, 3.0f };
261 const dt_aligned_pixel_simd_t c7_6 = dt_simd_set1(7.0f / 6.0f);
262 const dt_aligned_pixel_simd_t c2 = dt_simd_set1(2.0f);
263 const dt_aligned_pixel_simd_t c8_9 = dt_simd_set1(8.0f / 9.0f);
264 const dt_aligned_pixel_simd_t c7_18 = dt_simd_set1(7.0f / 18.0f);
265 const dt_aligned_pixel_simd_t c10_3 = dt_simd_set1(10.0f / 3.0f);
266 const dt_aligned_pixel_simd_t c16_9 = dt_simd_set1(16.0f / 9.0f);
267 const dt_aligned_pixel_simd_t interval_v = dt_simd_set1(interval);
268 const dt_aligned_pixel_simd_t iter = dt_simd_set1(4.0f * interval);
269 dt_aligned_pixel_simd_t vt = dt_simd_set1(first_tap) + bootstrap * interval_v;
270
271 const int runs = (num_taps + 3) / 4;
272
273 for(size_t i = 0; i < runs; i++)
274 {
275 const dt_aligned_pixel_simd_t a = dt_simd_abs(vt);
276 const dt_aligned_pixel_simd_t a2 = a * a;
277 const dt_aligned_pixel_simd_t a3 = a2 * a;
278 // inner lobe (|t| < 1) and outer lobe (1 <= |t| < 2)
279 const dt_aligned_pixel_simd_t r01 = c7_6 * a3 - c2 * a2 + c8_9;
280 const dt_aligned_pixel_simd_t r12 = c2 * a2 - c7_18 * a3 - c10_3 * a + c16_9;
281 dt_aligned_pixel_simd_t taps4 = r12;
283 taps4[c] = (a[c] <= 1.0f) ? r01[c] : r12[c];
284 dt_store_simd_aligned(taps + 4 * i, taps4);
285 vt += iter;
286 }
287 return 1.0f; // kernel norm is 1.0f by construction (partition of unity)
288}
289
290/* --------------------------------------------------------------------------
291 * All our known interpolators
292 * ------------------------------------------------------------------------*/
293
294/* !!! !!! !!!
295 * Make sure MAX_HALF_FILTER_WIDTH is at least equal to the maximum width
296 * of this filter list. Otherwise bad things will happen
297 * !!! !!! !!!
298 */
299static const struct dt_interpolation dt_interpolator[] = {
301 .name = "bilinear",
302 .width = 1,
303 .maketaps = &_maketaps_bilinear,
304 },
306 .name = "bicubic",
307 .width = 2,
308 .maketaps = &_maketaps_bicubic,
309 },
311 .name = "mitchell",
312 .width = 2,
313 .maketaps = &_maketaps_mitchell,
314 },
315};
316
317/* --------------------------------------------------------------------------
318 * Kernel utility methods
319 * ------------------------------------------------------------------------*/
320
321static inline __attribute__((always_inline)) float _compute_upsampling_kernel(const struct dt_interpolation *itor,
322 float *kernel,
323 int *first,
324 float t)
325{
326 // find first pixel contributing to the filter's kernel. We need
327 // floorf() because a simple cast to int truncates toward zero,
328 // yielding an incorrect result for the slightly-negative positions
329 // that can occur at the top and left edges when doing perspective
330 // correction
331 int f = (int)floorf(t) - itor->width + 1;
332 if(first)
333 {
334 *first = f;
335 }
336
337 /* Find closest integer position and then offset that to match first
338 * filtered sample position */
339 t = t - (float)f;
340
341 // compute the taps and return the kernel norm
342 return itor->maketaps(kernel, 2*itor->width, itor->width, t, -1.0f);
343}
344
355static inline void _compute_downsampling_kernel(const struct dt_interpolation *itor,
356 int *taps,
357 int *first,
358 float *kernel,
359 float *norm,
360 const float outoinratio,
361 const int xout)
362{
363 // Keep this at hand
364 const float w = (float)itor->width;
365
366 /* Compute the phase difference between output pixel and its
367 * input corresponding input pixel */
368 const float xin = ceil_fast(((float)xout - w) / outoinratio);
369 if(first)
370 {
371 *first = (int)xin;
372 }
373
374 // Compute first interpolator parameter
375 float t = xin * outoinratio - (float)xout;
376
377 // Compute all filter taps
378 int num_taps = *taps = (int)((w - t) / outoinratio);
379 itor->maketaps(kernel, num_taps, itor->width, t, outoinratio);
380 // compute the kernel norm if requested
381 if (norm)
382 {
383 float n = 0.0f;
384 for(size_t i = 0; i < num_taps; i++)
385 n += kernel[i];
386 *norm = n;
387 }
388}
389
390/* --------------------------------------------------------------------------
391 * Sample interpolation function (see usage in iop/lens.c and iop/clipping.c)
392 * ------------------------------------------------------------------------*/
393
394#define MAX_KERNEL_REQ ((2 * (MAX_HALF_FILTER_WIDTH) + 3) & (~3))
395
398 const float *in,
399 const float x,
400 const float y,
401 const int width,
402 const int height,
403 const int samplestride,
404 const int linestride)
405{
406 assert(itor->width < (MAX_HALF_FILTER_WIDTH + 1));
407
408 float DT_ALIGNED_ARRAY kernelh[MAX_KERNEL_REQ];
409 float DT_ALIGNED_ARRAY kernelv[MAX_KERNEL_REQ];
410
411 // Compute both horizontal and vertical kernels
412 float normh = _compute_upsampling_kernel(itor, kernelh, NULL, x);
413 float normv = _compute_upsampling_kernel(itor, kernelv, NULL, y);
414
415 int ix = (int)x;
416 int iy = (int)y;
417
418 /* Now 2 cases, the pixel + filter width goes outside the image
419 * in that case we have to use index clipping to keep all reads
420 * in the input image (slow path) or we are sure it won't fall
421 * outside and can do more simple code */
422 float r;
423 if(ix >= (itor->width - 1) && iy >= (itor->width - 1) && ix < (width - itor->width)
424 && iy < (height - itor->width))
425 {
426 // Inside image boundary case
427
428 // Go to top left pixel
429 in = (float *)in + linestride * iy + ix * samplestride;
430 in = in - (itor->width - 1) * (samplestride + linestride);
431
432 // Apply the kernel
433 float s = 0.f;
434 for(int i = 0; i < 2 * itor->width; i++)
435 {
436 float h = 0.0f;
437 for(int j = 0; j < 2 * itor->width; j++)
438 {
439 h += kernelh[j] * in[j * samplestride];
440 }
441 s += kernelv[i] * h;
442 in += linestride;
443 }
444 r = fmaxf(0.0f, s / (normh * normv));
445 }
446 else if(ix >= 0 && iy >= 0 && ix < width && iy < height)
447 {
448 // At least a valid coordinate
449
450 // Point to the upper left pixel index wise
451 iy -= itor->width - 1;
452 ix -= itor->width - 1;
453
454 static const enum border_mode bordermode = INTERPOLATION_BORDER_MODE;
455 assert(bordermode != BORDER_CLAMP); // XXX in clamp mode, norms would be wrong
456
457 int xtap_first;
458 int xtap_last;
459 _prepare_tap_boundaries(&xtap_first, &xtap_last,
460 bordermode, 2 * itor->width, ix, width);
461
462 int ytap_first;
463 int ytap_last;
464 _prepare_tap_boundaries(&ytap_first, &ytap_last,
465 bordermode, 2 * itor->width, iy, height);
466
467 // Apply the kernel
468 float s = 0.f;
469 for(ssize_t i = ytap_first; i < ytap_last; i++)
470 {
471 const ssize_t clip_y = _clip(iy + i, 0, height - 1, bordermode);
472 float h = 0.0f;
473 for(ssize_t j = xtap_first; j < xtap_last; j++)
474 {
475 const ssize_t clip_x = _clip(ix + j, 0, width - 1, bordermode);
476 const float *ipixel = in + clip_y * linestride + clip_x * samplestride;
477 h += kernelh[j] * ipixel[0];
478 }
479 s += kernelv[i] * h;
480 }
481
482 r = fmaxf(0.0f, s / (normh * normv));
483 }
484 else
485 {
486 // invalid coordinate
487 r = 0.0f;
488 }
489 return r;
490}
491
492/* --------------------------------------------------------------------------
493 * Pixel interpolation function (see usage in iop/lens.c and iop/clipping.c)
494 * ------------------------------------------------------------------------*/
495
498 const float *in,
499 float *out,
500 const float x,
501 const float y,
502 const int width,
503 const int height,
504 const int linestride)
505{
506 assert(itor->width < (MAX_HALF_FILTER_WIDTH + 1));
507
508 // Quite a bit of space for kernels
509 float DT_ALIGNED_ARRAY kernelh[MAX_KERNEL_REQ];
510 float DT_ALIGNED_ARRAY kernelv[MAX_KERNEL_REQ];
511
512 // Compute both horizontal and vertical kernels
513 float normh = _compute_upsampling_kernel(itor, kernelh, NULL, x);
514 float normv = _compute_upsampling_kernel(itor, kernelv, NULL, y);
515
516 // Precompute the inverse of the filter norm for later use
517 const float oonorm = (1.f / (normh * normv));
518
519 /* Now 2 cases, the pixel + filter width goes outside the image
520 * in that case we have to use index clipping to keep all reads
521 * in the input image (slow path) or we are sure it won't fall
522 * outside and can do more simple code */
523 int ix = (int)x;
524 int iy = (int)y;
525
526 if(ix >= (itor->width - 1)
527 && iy >= (itor->width - 1)
528 && ix < (width - itor->width)
529 && iy < (height - itor->width))
530 {
531 // Inside image boundary case
532
533 // Go to top left pixel
534 in = (float *)in + linestride * iy + ix * 4;
535 in = in - (itor->width - 1) * (4 + linestride);
536
537 const size_t itor_width = 2 * itor->width;
538
539 // Apply the kernel
540 dt_aligned_pixel_simd_t pixel = dt_simd_set1(0.0f);
541 for(size_t i = 0; i < itor_width; i++)
542 {
543 dt_aligned_pixel_simd_t h = dt_simd_set1(0.0f);
544 for(size_t j = 0; j < itor_width; j++)
545 h += dt_load_simd_aligned(in + 4 * j) * dt_simd_set1(kernelh[j]);
546 pixel += h * dt_simd_set1(kernelv[i]);
547 in += linestride;
548 }
549
550 dt_store_simd(out, dt_simd_max_zero(pixel * dt_simd_set1(oonorm)));
551 }
552 else if(ix >= 0 && iy >= 0 && ix < width && iy < height)
553 {
554 // At least a valid coordinate
555
556 // Point to the upper left pixel index wise
557 iy -= itor->width - 1;
558 ix -= itor->width - 1;
559
560 static const enum border_mode bordermode = INTERPOLATION_BORDER_MODE;
561 assert(bordermode != BORDER_CLAMP); // XXX in clamp mode, norms would be wrong
562
563 int xtap_first;
564 int xtap_last;
565 _prepare_tap_boundaries(&xtap_first, &xtap_last,
566 bordermode, 2 * itor->width, ix, width);
567
568 int ytap_first;
569 int ytap_last;
570 _prepare_tap_boundaries(&ytap_first, &ytap_last,
571 bordermode, 2 * itor->width, iy, height);
572
573 // Apply the kernel
574 dt_aligned_pixel_simd_t pixel = dt_simd_set1(0.0f);
575 for(ssize_t i = ytap_first; i < ytap_last; i++)
576 {
577 const ssize_t clip_y = _clip(iy + i, 0, height - 1, bordermode);
578 dt_aligned_pixel_simd_t h = dt_simd_set1(0.0f);
579 const float *ipixel = in + clip_y * linestride;
580 for(ssize_t j = xtap_first; j < xtap_last; j++)
581 {
582 const ssize_t clip_x = _clip(ix + j, 0, width - 1, bordermode);
583 h += dt_load_simd_aligned(ipixel + 4 * clip_x) * dt_simd_set1(kernelh[j]);
584 }
585 pixel += h * dt_simd_set1(kernelv[i]);
586 }
587
588 dt_store_simd(out, dt_simd_max_zero(pixel * dt_simd_set1(oonorm)));
589 }
590 else
591 {
592 // data for *out has no valid *in location so just set to zero.
594 }
595}
596
597/* --------------------------------------------------------------------------
598 * Interpolation factory
599 * ------------------------------------------------------------------------*/
600
601/* The two preference-driven answers, cached against the conf generation.
602 *
603 * This resolves a preference to one of a handful of static kernels, and it used to do that
604 * by reading conf and strcmp-ing the name on EVERY call -- from ~30 call sites, several of
605 * them per tile, on pipeline threads. The arithmetic was never the problem: dt_conf_get_*
606 * takes the application-wide conf mutex, the one the GUI thread holds constantly, so this
607 * was pipeline threads contending with the GUI to re-derive a constant.
608 *
609 * Unlike a module, this is a library function with no commit_params() to settle it in, so
610 * the answer is cached here and re-derived when dt_conf_generation() moves.
611 *
612 * The race is benign by construction: two threads may both re-derive, and they resolve the
613 * same preference to the same static pointer. The pointer is published BEFORE the generation
614 * that vouches for it, so a reader that sees a current generation cannot see a stale pointer;
615 * both stores are sequentially consistent, which orders them.
616 *
617 * NOTE this does not make the interpolator part of any cache key. A module whose pixels
618 * depend on which kernel is chosen still owes that to its own hash -- changing the preference
619 * mid-session re-derives here but invalidates no pixel the pipe has already cached. That gap
620 * predates this cache and is not closed by it. */
621static dt_atomic_uint64 _userpref_generation = 0;
622static dt_atomic_ptr _userpref_itor = NULL;
623static dt_atomic_uint64 _userpref_warp_generation = 0;
624static dt_atomic_ptr _userpref_warp_itor = NULL;
625
626static const struct dt_interpolation *_resolve_by_name(const char *conf_key)
627{
628 const char *uipref = dt_conf_get_string_const(conf_key);
629 for(int i = DT_INTERPOLATION_FIRST; uipref && i < DT_INTERPOLATION_LAST; i++)
630 {
631 if(!strcmp(uipref, dt_interpolator[i].name)) return &dt_interpolator[i];
632 }
633 return NULL;
634}
635
636static const struct dt_interpolation *_userpref_cached(const char *conf_key,
637 dt_atomic_uint64 *generation,
638 dt_atomic_ptr *slot)
639{
640 const uint64_t now = dt_conf_generation();
641 if(dt_atomic_get_uint64(generation) == now)
642 return (const struct dt_interpolation *)dt_atomic_get_ptr(slot);
643
644 const struct dt_interpolation *itor = _resolve_by_name(conf_key);
645 dt_atomic_set_ptr(slot, (void *)itor);
646 dt_atomic_set_uint64(generation, now);
647 return itor;
648}
649
651{
652 const struct dt_interpolation *itor = NULL;
653
655 {
656 itor = _userpref_cached("plugins/lighttable/export/pixel_interpolator",
658
659 /* In the case the search failed (!uipref or name not found),
660 * prepare later search pass with default fallback */
662 }
664 {
665 itor = _userpref_cached("plugins/lighttable/export/pixel_interpolator_warp",
667
668 /* In the case the search failed (!uipref or name not found),
669 * prepare later search pass with default fallback */
671 }
672 if(IS_NULL_PTR(itor))
673 {
674 // Did not find the userpref one or we've been asked for a specific one
676 {
677 if(dt_interpolator[i].id == type)
678 {
679 itor = &dt_interpolator[i];
680 break;
681 }
683 {
684 itor = &dt_interpolator[i];
685 }
686 }
687 }
688
689 return itor;
690}
691
692/* --------------------------------------------------------------------------
693 * Image resampling
694 * ------------------------------------------------------------------------*/
695
736static gboolean _prepare_resampling_plan(const struct dt_interpolation *itor,
737 const int in,
738 const int in_x0,
739 const int out,
740 const int out_x0,
741 const float scale,
742 int **plength,
743 float **pkernel,
744 int **pindex,
745 int **pmeta)
746{
747 // Safe return values
748 *plength = NULL;
749 *pkernel = NULL;
750 *pindex = NULL;
751 if(pmeta)
752 {
753 *pmeta = NULL;
754 }
755
756 if(scale == 1.f)
757 {
758 // No resampling required
759 return FALSE;
760 }
761
762 // Compute common upsampling/downsampling memory requirements
763 int maxtapsapixel;
764 if(scale > 1.f)
765 {
766 // Upscale... the easy one. The values are exact
767 maxtapsapixel = 2 * itor->width;
768 }
769 else
770 {
771 // Downscale... going for worst case values memory wise
772 maxtapsapixel = ceil_fast((float)2 * (float)itor->width / scale);
773 }
774
775 int nlengths = out;
776 const int nindex = maxtapsapixel * out;
777 const int nkernel = maxtapsapixel * out;
778 const size_t lengthreq = dt_round_size(nlengths * sizeof(int), DT_CACHELINE_BYTES);
779 const size_t indexreq = dt_round_size(nindex * sizeof(int), DT_CACHELINE_BYTES);
780 const size_t kernelreq = dt_round_size(nkernel * sizeof(float), DT_CACHELINE_BYTES);
781 const size_t scratchreq = dt_round_size(maxtapsapixel * sizeof(float) + 4 * sizeof(float), DT_CACHELINE_BYTES);
782 // NB: because sse versions compute four taps a time
783 const size_t metareq = dt_round_size(pmeta ? 4 * sizeof(int) * out : 0, DT_CACHELINE_BYTES);
784
785 const size_t totalreq = kernelreq + lengthreq + indexreq + scratchreq + metareq;
786 void *blob = dt_pixelpipe_cache_alloc_align_cache(totalreq, 0);
787 if(IS_NULL_PTR(blob)) return TRUE;
788
789 int *lengths = (int *)blob;
790 blob = (char *)blob + lengthreq;
791 int *index = (int *)blob;
792 blob = (char *)blob + indexreq;
793 float *kernel = (float *)blob;
794 blob = (char *)blob + kernelreq;
795 // Not `scratchreq ? ... : NULL`, unlike meta below: scratchreq is rounded up from at least
796 // 4 floats of headroom, so it is unconditionally non-zero and the NULL case cannot happen.
797 // Spelling it as a maybe-NULL made three later uses read as possible NULL dereferences --
798 // to a static analyser and to anyone reading the code. metareq IS conditional (pmeta), so
799 // meta keeps its ternary.
800 float *scratchpad = (float *)blob;
801 blob = (char *)blob + scratchreq;
802 int *meta = metareq ? (int *)blob : NULL;
803// blob = (char *)blob + metareq;
804
805 /* setting this as a const should help the compilers trim all unnecessary
806 * codepaths */
807 const enum border_mode bordermode = RESAMPLING_BORDER_MODE;
808
809 /* Upscale and downscale differ in subtle points, getting rid of code
810 * duplication might have been tricky and i prefer keeping the code
811 * as straight as possible */
812 if(scale > 1.f)
813 {
814 int kidx = 0;
815 int iidx = 0;
816 int lidx = 0;
817 int midx = 0;
818 for(int x = 0; x < out; x++)
819 {
820 if(meta)
821 {
822 meta[midx++] = lidx;
823 meta[midx++] = kidx;
824 meta[midx++] = iidx;
825 }
826
827 // Projected position in input samples
828 float fx = (float)(out_x0 + x) / scale - in_x0;
829
830 // Compute the filter kernel at that position
831 int first;
832 (void)_compute_upsampling_kernel(itor, scratchpad, &first, fx);
833
834 /* Check lower and higher bound pixel index and skip as many pixels as
835 * necessary to fall into range */
836 int tap_first;
837 int tap_last;
838 _prepare_tap_boundaries(&tap_first, &tap_last, bordermode, 2 * itor->width, first, in);
839
840 // Track number of taps that will be used
841 lengths[lidx++] = tap_last - tap_first;
842
843 // Precompute the inverse of the norm
844 float norm = 0.f;
845 for(int tap = tap_first; tap < tap_last; tap++)
846 {
847 norm += scratchpad[tap];
848 }
849 norm = 1.f / norm;
850
851 /* Unlike single pixel or single sample code, here it's interesting to
852 * precompute the normalized filter kernel as this will avoid dividing
853 * by the norm for all processed samples/pixels
854 * NB: use the same loop to put in place the index list */
855 first += tap_first;
856 for(int tap = tap_first; tap < tap_last; tap++)
857 {
858 kernel[kidx++] = scratchpad[tap] * norm;
859 index[iidx++] = _clip(first++, 0, in - 1, bordermode);
860 }
861 }
862 }
863 else
864 {
865 int kidx = 0;
866 int iidx = 0;
867 int lidx = 0;
868 int midx = 0;
869 for(int x = 0; x < out; x++)
870 {
871 if(meta)
872 {
873 meta[midx++] = lidx;
874 meta[midx++] = kidx;
875 meta[midx++] = iidx;
876 }
877
878 // Compute downsampling kernel centered on output position
879 int taps;
880 int first;
881 _compute_downsampling_kernel(itor, &taps, &first, scratchpad, NULL, scale, out_x0 + x);
882
883 /* Check lower and higher bound pixel index and skip as many pixels as
884 * necessary to fall into range */
885 int tap_first;
886 int tap_last;
887 _prepare_tap_boundaries(&tap_first, &tap_last, bordermode, taps, first, in);
888
889 // Track number of taps that will be used
890 lengths[lidx++] = tap_last - tap_first;
891
892 // Precompute the inverse of the norm
893 float norm = 0.f;
894 for(int tap = tap_first; tap < tap_last; tap++)
895 {
896 norm += scratchpad[tap];
897 }
898 norm = 1.f / norm;
899
900 /* Unlike single pixel or single sample code, here it's interesting to
901 * precompute the normalized filter kernel as this will avoid dividing
902 * by the norm for all processed samples/pixels
903 * NB: use the same loop to put in place the index list */
904 first += tap_first;
905 for(int tap = tap_first; tap < tap_last; tap++)
906 {
907 kernel[kidx++] = scratchpad[tap] * norm;
908 index[iidx++] = _clip(first++, 0, in - 1, bordermode);
909 }
910 }
911 }
912
913 // Validate plan wrt caller
914 *plength = lengths;
915 *pindex = index;
916 *pkernel = kernel;
917 if(pmeta)
918 {
919 *pmeta = meta;
920 }
921
922 return FALSE;
923}
924
925#define TILE_ROWS 128
926
928static void _interpolation_resample_plain(const struct dt_interpolation *itor,
929 float *const restrict out,
930 const dt_iop_roi_t *const roi_out,
931 const float *const restrict in,
932 const dt_iop_roi_t *const roi_in)
933{
934 int *hindex = NULL;
935 int *hlength = NULL;
936 float *hkernel = NULL;
937 int *vindex = NULL;
938 int *vlength = NULL;
939 float *vkernel = NULL;
940 int *vmeta = NULL;
941
942 const int32_t in_stride_floats = roi_in->width * 4;
943 const int32_t out_stride_floats = roi_out->width * 4;
944
945 // Fast code path for 1:1 copy, only cropping area can change
946 if(roi_out->scale == 1.f || roi_out->scale == roi_in->scale)
947 {
948 const size_t x0 = (roi_out->x - roi_in->x) * 4 * sizeof(float);
949 const size_t y0 = (roi_out->y - roi_in->y);
951 for(int yt = 0; yt < roi_out->height; yt += TILE_ROWS)
952 {
953 const int y_end = MIN(yt + TILE_ROWS, roi_out->height);
954 for(int y = yt; y < y_end; y++)
955 memcpy((char *)__builtin_assume_aligned(out, 64) + (size_t)out_stride_floats * sizeof(float) * y,
956 (char *)__builtin_assume_aligned(in, 64) + (size_t)in_stride_floats * sizeof(float) * (y + y0) + x0,
957 out_stride_floats * sizeof(float));
958 }
959
960
961 // All done, so easy case
962 return;
963 }
964
965 // Generic non 1:1 case... much more complicated :D
966
967 // The actual resampling ratio between the two buffers,
968 // not the absolute pipeline scale
969 const float resample_scale = roi_out->scale / roi_in->scale;
970
971 if(_prepare_resampling_plan(itor, roi_in->width, roi_in->x,
972 roi_out->width, roi_out->x, resample_scale,
973 &hlength, &hkernel, &hindex, NULL))
974 goto exit;
975
976 if(_prepare_resampling_plan(itor, roi_in->height, roi_in->y,
977 roi_out->height, roi_out->y, resample_scale,
978 &vlength, &vkernel, &vindex, &vmeta))
979 goto exit;
980
981 const size_t height = roi_out->height;
982 const size_t width = roi_out->width;
983
984 // Process each output line
986 for(size_t oy = 0; oy < height; oy++)
987 {
988 // Initialize column resampling indexes
989 int vlidx = vmeta[3 * oy + 0]; // V(ertical) L(ength) I(n)d(e)x
990 int vkidx = vmeta[3 * oy + 1]; // V(ertical) K(ernel) I(n)d(e)x
991 int viidx = vmeta[3 * oy + 2]; // V(ertical) I(ndex) I(n)d(e)x
992
993 // Initialize row resampling indexes
994 int hlidx = 0; // H(orizontal) L(ength) I(n)d(e)x
995 int hkidx = 0; // H(orizontal) K(ernel) I(n)d(e)x
996
997 // Number of lines contributing to the output line
998 int vl = vlength[vlidx++]; // V(ertical) L(ength)
999
1000 // Process each output column
1001 for(size_t ox = 0; ox < width; ox++)
1002 {
1003 // This will hold the resulting pixel
1004 dt_aligned_pixel_simd_t vs = dt_simd_set1(0.0f);
1005
1006 // Number of horizontal samples contributing to the output
1007 const int hl = hlength[hlidx++]; // H(orizontal) L(ength)
1008 const int *const column_hindex = hindex + hkidx;
1009 const float *const column_hkernel = hkernel + hkidx;
1010 const int *const column_vindex = vindex + viidx;
1011 const float *const column_vkernel = vkernel + vkidx;
1012
1013 for(size_t iy = 0; iy < vl; iy++)
1014 {
1015 // This is our input line
1016 const size_t baseidx_vindex = (size_t)column_vindex[iy] * in_stride_floats;
1017
1018 dt_aligned_pixel_simd_t vhs = dt_simd_set1(0.0f);
1019
1020 for(size_t ix = 0; ix < hl; ix++)
1021 {
1022 // Apply the precomputed filter kernel
1023 const size_t baseidx = baseidx_vindex + (size_t)column_hindex[ix] * 4;
1024 const float htap = column_hkernel[ix];
1025 vhs += dt_load_simd_aligned(in + baseidx) * dt_simd_set1(htap);
1026 }
1027
1028 // Accumulate contribution from this line
1029 const float vtap = column_vkernel[iy];
1030 vs += vhs * dt_simd_set1(vtap);
1031 }
1032
1033 // Output pixel is ready
1034 const size_t baseidx = (size_t)oy * out_stride_floats + (size_t)ox * 4;
1035
1036 // Clip negative RGB that may be produced by Lanczos undershooting
1037 // Negative RGB are invalid values no matter the RGB space (light is positive)
1038 dt_aligned_pixel_t pixel;
1039 dt_store_simd_aligned(pixel, dt_simd_max_zero(vs));
1040 copy_pixel_nontemporal(out + baseidx, pixel);
1041
1042 // The vertical support is fixed for the whole output row. Only the
1043 // horizontal plan advances from one output column to the next.
1044 hkidx += hl;
1045 }
1046 }
1047
1048
1050
1051exit:
1052 /* Free the resampling plans. It's nasty to optimize allocs like that, but
1053 * it simplifies the code :-D. The length array is in fact the only memory
1054 * allocated. */
1057}
1058
1063 float *out,
1064 const dt_iop_roi_t *const roi_out,
1065 const float *const in,
1066 const dt_iop_roi_t *const roi_in)
1067{
1068 return _interpolation_resample_plain(itor, out, roi_out, in, roi_in);
1069}
1070
1077 float *out,
1078 const dt_iop_roi_t *const roi_out,
1079 const float *const in,
1080 const dt_iop_roi_t *const roi_in)
1081{
1082 dt_iop_roi_t oroi = *roi_out;
1083 //oroi.x = oroi.y = 0;
1084
1085 dt_iop_roi_t iroi = *roi_in;
1086 //iroi.x = iroi.y = 0;
1087
1088 dt_interpolation_resample(itor, out, &oroi, in, &iroi);
1089}
1090
1091#ifdef HAVE_OPENCL
1092/* The kernels this subsystem compiles, owned HERE. They used to be handed to
1093 * common/opencl.c, parked on the application-wide dt_opencl_t, and read back from it --
1094 * a round trip through a god-struct that added nothing but an ordering. opencl.c still
1095 * calls init/free, because the kernels must be built after the devices exist, but the
1096 * pointer never leaves this file. */
1098
1100{
1103
1104 const int program = 2; // basic.cl, from programs.conf
1106 dt_opencl_create_kernel(program, "interpolation_resample");
1108}
1109
1111{
1114 if(IS_NULL_PTR(g)) return;
1115 // destroy kernels
1116 dt_opencl_free_kernel(g->kernel_interpolation_resample);
1117 dt_free(g);
1118}
1119
1120static uint32_t roundToNextPowerOfTwo(uint32_t x)
1121{
1122 x--;
1123 x |= x >> 1;
1124 x |= x >> 2;
1125 x |= x >> 4;
1126 x |= x >> 8;
1127 x |= x >> 16;
1128 x++;
1129 return x;
1130}
1131
1136 const int devid,
1137 cl_mem dev_out,
1138 const dt_iop_roi_t *const roi_out,
1139 cl_mem dev_in,
1140 const dt_iop_roi_t *const roi_in)
1141{
1142 int *hindex = NULL;
1143 int *hlength = NULL;
1144 float *hkernel = NULL;
1145 int *hmeta = NULL;
1146 int *vindex = NULL;
1147 int *vlength = NULL;
1148 float *vkernel = NULL;
1149 int *vmeta = NULL;
1150
1151 cl_int err = DT_OPENCL_DEFAULT_ERROR;
1152
1153 cl_mem dev_hindex = NULL;
1154 cl_mem dev_hlength = NULL;
1155 cl_mem dev_hkernel = NULL;
1156 cl_mem dev_hmeta = NULL;
1157 cl_mem dev_vindex = NULL;
1158 cl_mem dev_vlength = NULL;
1159 cl_mem dev_vkernel = NULL;
1160 cl_mem dev_vmeta = NULL;
1161
1162 // Fast code path for 1:1 copy, only cropping area can change
1163 if(roi_out->scale == 1.f || roi_out->scale == roi_in->scale)
1164 {
1165 size_t iorigin[] = { roi_out->x - roi_in->x, roi_out->y - roi_in->y, 0 };
1166 size_t oorigin[] = { 0, 0, 0 };
1167 size_t region[] = { roi_out->width, roi_out->height, 1 };
1168
1169 // copy original input from dev_in -> dev_out as starting point
1170 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, iorigin, oorigin, region);
1171 if(err != CL_SUCCESS) goto error;
1172
1173 // All done, so easy case
1174 return CL_SUCCESS;
1175 }
1176
1177 // Generic non 1:1 case... much more complicated :D
1178
1179 // The actual resampling ratio between the two buffers,
1180 // not the absolute pipeline scale
1181 const float resample_scale = roi_out->scale / roi_in->scale;
1182
1183 if(_prepare_resampling_plan(itor, roi_in->width, roi_in->x,
1184 roi_out->width, roi_out->x, resample_scale,
1185 &hlength, &hkernel, &hindex, &hmeta))
1186 goto error;
1187
1188 if(_prepare_resampling_plan(itor, roi_in->height, roi_in->y,
1189 roi_out->height, roi_out->y, resample_scale,
1190 &vlength, &vkernel, &vindex, &vmeta))
1191 goto error;
1192
1193 int hmaxtaps = -1, vmaxtaps = -1;
1194 for(int k = 0; k < roi_out->width; k++) hmaxtaps = MAX(hmaxtaps, hlength[k]);
1195 for(int k = 0; k < roi_out->height; k++) vmaxtaps = MAX(vmaxtaps, vlength[k]);
1196
1197 // strategy: process image column-wise (local[0] = 1). For each row generate
1198 // a number of parallel work items each taking care of one horizontal convolution,
1199 // then sum over work items to do the vertical convolution
1200
1202 const int width = roi_out->width;
1203 const int height = roi_out->height;
1204
1205 // make sure blocksize is not too large
1206 const int taps = roundToNextPowerOfTwo(vmaxtaps);
1207 // the number of work items per row rounded up to a power of 2
1208 // (for quick recursive reduction)
1209
1210 int vblocksize;
1211
1214 { .xoffset = 0,
1215 .xfactor = 1,
1216 .yoffset = 0,
1217 .yfactor = 1,
1218 .cellsize = 4 * sizeof(float),
1219 .overhead = hmaxtaps * sizeof(float) + hmaxtaps * sizeof(int),
1220 .sizex = 1,
1221 .sizey = (1 << 16) * taps };
1222
1223 if(dt_opencl_local_buffer_opt(devid, kernel, &locopt))
1224 vblocksize = locopt.sizey;
1225 else
1226 vblocksize = 1;
1227
1228 if(vblocksize < taps)
1229 {
1230 // our strategy does not work: the vertical number of taps exceeds
1231 // the vertical workgroupsize; there is no point in continuing on
1232 // the GPU - that would be way too slow; let's delegate the stuff
1233 // to the CPU then.
1234 err = CL_INVALID_WORK_GROUP_SIZE;
1235 goto error;
1236 }
1237
1238 size_t sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUP(height * taps, vblocksize), 1 };
1239 size_t local[3] = { 1, vblocksize, 1 };
1240
1241 // store resampling plan to device memory hindex, vindex, hkernel,
1242 // vkernel: (v|h)maxtaps might be too small, so store a bit more
1243 // than needed
1244 err = -999;
1245
1246 dev_hindex = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * width * (hmaxtaps + 1), hindex);
1247 if(IS_NULL_PTR(dev_hindex)) goto error;
1248
1249 dev_hlength = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * width, hlength);
1250 if(IS_NULL_PTR(dev_hlength)) goto error;
1251
1252 dev_hkernel = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * width * (hmaxtaps + 1), hkernel);
1253 if(IS_NULL_PTR(dev_hkernel)) goto error;
1254
1255 dev_hmeta = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * width * 3, hmeta);
1256 if(IS_NULL_PTR(dev_hmeta)) goto error;
1257
1258 dev_vindex = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * height * (vmaxtaps + 1), vindex);
1259 if(IS_NULL_PTR(dev_vindex)) goto error;
1260
1261 dev_vlength = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * height, vlength);
1262 if(IS_NULL_PTR(dev_vlength)) goto error;
1263
1264 dev_vkernel = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * height * (vmaxtaps + 1), vkernel);
1265 if(IS_NULL_PTR(dev_vkernel)) goto error;
1266
1267 dev_vmeta = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * height * 3, vmeta);
1268 if(IS_NULL_PTR(dev_vmeta)) goto error;
1269
1270 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
1271 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
1272 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
1273 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
1274 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_hmeta);
1275 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), (void *)&dev_vmeta);
1276 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), (void *)&dev_hlength);
1277 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(cl_mem), (void *)&dev_vlength);
1278 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), (void *)&dev_hindex);
1279 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(cl_mem), (void *)&dev_vindex);
1280 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), (void *)&dev_hkernel);
1281 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(cl_mem), (void *)&dev_vkernel);
1282 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(int), (void *)&hmaxtaps);
1283 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(int), (void *)&taps);
1284 dt_opencl_set_kernel_arg(devid, kernel, 14, hmaxtaps * sizeof(float), NULL);
1285 dt_opencl_set_kernel_arg(devid, kernel, 15, hmaxtaps * sizeof(int), NULL);
1286 dt_opencl_set_kernel_arg(devid, kernel, 16, vblocksize * 4 * sizeof(float), NULL);
1287 err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
1288
1289error:
1290
1291 dt_opencl_release_mem_object(dev_hindex);
1292 dt_opencl_release_mem_object(dev_hlength);
1293 dt_opencl_release_mem_object(dev_hkernel);
1295 dt_opencl_release_mem_object(dev_vindex);
1296 dt_opencl_release_mem_object(dev_vlength);
1297 dt_opencl_release_mem_object(dev_vkernel);
1301 return err;
1302}
1303
1310 const int devid,
1311 cl_mem dev_out,
1312 const dt_iop_roi_t *const roi_out,
1313 cl_mem dev_in,
1314 const dt_iop_roi_t *const roi_in)
1315{
1316 dt_iop_roi_t oroi = *roi_out;
1317 //oroi.x = oroi.y = 0;
1318
1319 dt_iop_roi_t iroi = *roi_in;
1320 //iroi.x = iroi.y = 0;
1321
1322 return dt_interpolation_resample_cl(itor, devid, dev_out, &oroi, dev_in, &iroi);
1323}
1324#endif
1325
1327 float *out,
1328 const dt_iop_roi_t *const roi_out,
1329 const float *const in,
1330 const dt_iop_roi_t *const roi_in)
1331{
1332 int *hindex = NULL;
1333 int *hlength = NULL;
1334 float *hkernel = NULL;
1335 int *vindex = NULL;
1336 int *vlength = NULL;
1337 float *vkernel = NULL;
1338 int *vmeta = NULL;
1339
1340
1341 const size_t out_stride = roi_out->width * sizeof(float);
1342 const size_t in_stride = roi_in->width * sizeof(float);
1343
1344 // Fast code path for 1:1 copy, only cropping area can change
1345 if(roi_out->scale == 1.f || roi_out->scale == roi_in->scale)
1346 {
1347 const size_t x0 = (roi_out->x - roi_in->x) * sizeof(float);
1348 const size_t y0 = (roi_out->y - roi_in->y);
1350 for(int y = 0; y < roi_out->height; y++)
1351 {
1352 float *i = (float *)((char *)in + in_stride * (y + y0) + x0);
1353 float *o = (float *)((char *)out + out_stride * y);
1354 memcpy(o, i, out_stride);
1355 }
1356 // All done, so easy case
1357 return;
1358 }
1359
1360 // Generic non 1:1 case... much more complicated :D
1361
1362 // Prepare resampling plans once and for all
1363 if(_prepare_resampling_plan(itor, roi_in->width, roi_in->x,
1364 roi_out->width, roi_out->x, roi_out->scale,
1365 &hlength, &hkernel, &hindex, NULL))
1366 goto exit;
1367
1368 if(_prepare_resampling_plan(itor, roi_in->height, roi_in->y,
1369 roi_out->height, roi_out->y, roi_out->scale,
1370 &vlength, &vkernel, &vindex, &vmeta))
1371 goto exit;
1372
1373 // Process each output line
1375 for(int oy = 0; oy < roi_out->height; oy++)
1376 {
1377 // Initialize column resampling indexes
1378 int vlidx = vmeta[3 * oy + 0]; // V(ertical) L(ength) I(n)d(e)x
1379 int vkidx = vmeta[3 * oy + 1]; // V(ertical) K(ernel) I(n)d(e)x
1380 int viidx = vmeta[3 * oy + 2]; // V(ertical) I(ndex) I(n)d(e)x
1381
1382 // Initialize row resampling indexes
1383 int hlidx = 0; // H(orizontal) L(ength) I(n)d(e)x
1384 int hkidx = 0; // H(orizontal) K(ernel) I(n)d(e)x
1385 int hiidx = 0; // H(orizontal) I(ndex) I(n)d(e)x
1386
1387 // Number of lines contributing to the output line
1388 int vl = vlength[vlidx++]; // V(ertical) L(ength)
1389
1390 // Process each output column
1391 for(int ox = 0; ox < roi_out->width; ox++)
1392 {
1393 // This will hold the resulting pixel
1394 float vs = 0.0f;
1395
1396 // Number of horizontal samples contributing to the output
1397 const int hl = hlength[hlidx++]; // H(orizontal) L(ength)
1398 for(int iy = 0; iy < vl; iy++)
1399 {
1400 // This is our input line
1401 const float *i = (float *)((char *)in + in_stride * vindex[viidx++]);
1402
1403 float vhs = 0.0f;
1404
1405 for(int ix = 0; ix < hl; ix++)
1406 {
1407 // Apply the precomputed filter kernel
1408 const size_t baseidx = (size_t)hindex[hiidx++];
1409 const float htap = hkernel[hkidx++];
1410 vhs += i[baseidx] * htap;
1411 }
1412
1413 // Accumulate contribution from this line
1414 const float vtap = vkernel[vkidx++];
1415 vs += vhs * vtap;
1416
1417 // Reset horizontal resampling context
1418 hkidx -= hl;
1419 hiidx -= hl;
1420 }
1421
1422 // Output pixel is ready
1423 float *o = (float *)((char *)out + (size_t)oy * out_stride
1424 + (size_t)ox * sizeof(float));
1425 *o = vs;
1426
1427 // Reset vertical resampling context
1428 viidx -= vl;
1429 vkidx -= vl;
1430
1431 // Progress in horizontal context
1432 hiidx += hl;
1433 hkidx += hl;
1434 }
1435 }
1436
1437 exit:
1438 /* Free the resampling plans. It's nasty to optimize allocs like that, but
1439 * it simplifies the code :-D. The length array is in fact the only memory
1440 * allocated. */
1443}
1444
1449 float *out,
1450 const dt_iop_roi_t *const roi_out,
1451 const float *const in,
1452 const dt_iop_roi_t *const roi_in)
1453{
1454 return _interpolation_resample_1c_plain(itor, out, roi_out, in, roi_in);
1455}
1456
1462 float *out,
1463 const dt_iop_roi_t *const roi_out,
1464 const float *const in,
1465 const dt_iop_roi_t *const roi_in)
1466{
1467 dt_iop_roi_t oroi = *roi_out;
1468 //oroi.x = oroi.y = 0;
1469
1470 dt_iop_roi_t iroi = *roi_in;
1471 //iroi.x = iroi.y = 0;
1472
1473 dt_interpolation_resample_1c(itor, out, &oroi, in, &iroi);
1474}
1475
1476// clang-format off
1477// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1478// vim: shiftwidth=2 expandtab tabstop=2 cindent
1479// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1480// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
uint64_t dt_atomic_get_uint64(const dt_atomic_uint64 *var)
void dt_atomic_set_uint64(dt_atomic_uint64 *var, uint64_t value)
static void * dt_atomic_get_ptr(const dt_atomic_ptr *var)
Definition atomic.h:76
static void dt_atomic_set_ptr(dt_atomic_ptr *var, void *value)
Definition atomic.h:75
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
return vector dt_simd_set1(valid ?(scaling+NORM_MIN) :NORM_MIN)
static const float x
const float f
const int t
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
dt_store_simd_aligned(out, dt_mat3x4_mul_vec4(vin, dt_colormatrix_row_to_simd(matrix, 0), dt_colormatrix_row_to_simd(matrix, 1), dt_colormatrix_row_to_simd(matrix, 2)))
uint64_t dt_conf_generation(void)
A counter that advances whenever a conf value actually changes.
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
static CameraMetaData * meta
const struct dt_interpolation * dt_interpolation_new(enum dt_interpolation_type type)
static dt_atomic_ptr _userpref_itor
#define MAX_HALF_FILTER_WIDTH
__DT_CLONE_TARGETS__ void dt_interpolation_compute_pixel4c(const struct dt_interpolation *itor, const float *in, float *out, const float x, const float y, const int width, const int height, const int linestride)
static void _interpolation_resample_1c_plain(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
static float _maketaps_mitchell(float *taps, const size_t num_taps, const float width, const float first_tap, const float interval)
static const struct dt_interpolation * _resolve_by_name(const char *conf_key)
static uint32_t roundToNextPowerOfTwo(uint32_t x)
#define RESAMPLING_BORDER_MODE
void dt_interpolation_resample_roi(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
border_mode
@ BORDER_WRAP
@ BORDER_MIRROR
@ BORDER_CLAMP
@ BORDER_REPLICATE
static __DT_CLONE_TARGETS__ gboolean _prepare_resampling_plan(const struct dt_interpolation *itor, const int in, const int in_x0, const int out, const int out_x0, const float scale, int **plength, float **pkernel, int **pindex, int **pmeta)
void dt_interpolation_resample_roi_1c(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
void dt_interpolation_resample(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
int dt_interpolation_resample_cl(const struct dt_interpolation *itor, const int devid, cl_mem dev_out, const dt_iop_roi_t *const roi_out, cl_mem dev_in, const dt_iop_roi_t *const roi_in)
void dt_interpolation_resample_1c(const struct dt_interpolation *itor, float *out, const dt_iop_roi_t *const roi_out, const float *const in, const dt_iop_roi_t *const roi_in)
static dt_interpolation_cl_global_t * _interpolation_cl_global
void dt_interpolation_free_cl_global(void)
static const struct dt_interpolation * _userpref_cached(const char *conf_key, dt_atomic_uint64 *generation, dt_atomic_ptr *slot)
static float _maketaps_bilinear(float *taps, const size_t num_taps, const float width, const float first_tap, const float interval)
#define MAX_KERNEL_REQ
int dt_interpolation_resample_roi_cl(const struct dt_interpolation *itor, const int devid, cl_mem dev_out, const dt_iop_roi_t *const roi_out, cl_mem dev_in, const dt_iop_roi_t *const roi_in)
void dt_interpolation_init_cl_global(void)
static const struct dt_interpolation dt_interpolator[]
#define TILE_ROWS
__DT_CLONE_TARGETS__ float dt_interpolation_compute_sample(const struct dt_interpolation *itor, const float *in, const float x, const float y, const int width, const int height, const int samplestride, const int linestride)
#define INTERPOLATION_BORDER_MODE
static __DT_CLONE_TARGETS__ void _interpolation_resample_plain(const struct dt_interpolation *itor, float *const restrict out, const dt_iop_roi_t *const roi_out, const float *const restrict in, const dt_iop_roi_t *const roi_in)
static dt_atomic_uint64 _userpref_generation
static void _compute_downsampling_kernel(const struct dt_interpolation *itor, int *taps, int *first, float *kernel, float *norm, const float outoinratio, const int xout)
static dt_atomic_ptr _userpref_warp_itor
static dt_atomic_uint64 _userpref_warp_generation
static float _maketaps_bicubic(float *taps, const size_t num_taps, const float width, const float first_tap, const float interval)
dt_interpolation_type
@ DT_INTERPOLATION_BICUBIC
@ DT_INTERPOLATION_BILINEAR
@ DT_INTERPOLATION_DEFAULT
@ DT_INTERPOLATION_LAST
@ DT_INTERPOLATION_MITCHELL
@ DT_INTERPOLATION_USERPREF
@ DT_INTERPOLATION_DEFAULT_WARP
@ DT_INTERPOLATION_FIRST
@ DT_INTERPOLATION_USERPREF_WARP
static float kernel(const float *x, const float *y)
_lib_location_type_t type
Definition location.c:1
float *const restrict const size_t k
#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
static float ceil_fast(float x)
Definition math.h:324
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
static size_t dt_round_size(const size_t size, const size_t alignment)
Round size UP to the next multiple of alignment.
Definition mem_alloc.h:99
#define DT_CACHELINE_BYTES
Definition mem_alloc.h:66
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define o
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3786
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2808
int dt_opencl_enqueue_copy_image(const int devid, cl_mem src, cl_mem dst, size_t *orig_src, size_t *orig_dst, size_t *region)
Definition opencl.c:2737
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2603
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2618
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:61
#define ROUNDUP(a, n)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define dt_omploop_sfence()
Definition openmp.h:164
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
const char * name
Definition pdf.h:90
#define dt_pixelpipe_cache_alloc_align_cache(size, id)
#define dt_pixelpipe_cache_free_align(mem)
dt_store_simd(out, value)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
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 r
unsigned __int64 uint64_t
Definition strptime.c:75
dt_interpolation_func maketaps
enum dt_interpolation_type id
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29