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
45#include <assert.h>
46#include <glib.h>
47#include <inttypes.h>
48#include <stddef.h>
49#include <stdint.h>
50
53{
54 BORDER_REPLICATE, // aaaa|abcdefg|gggg
55 BORDER_WRAP, // defg|abcdefg|abcd
56 BORDER_MIRROR, // edcb|abcdefg|fedc
57 BORDER_CLAMP // ....|abcdefg|....
58};
59
60/* Supporting them all might be overkill, let the compiler trim all
61 * unnecessary modes in clip for resampling codepath*/
62#define RESAMPLING_BORDER_MODE BORDER_REPLICATE
63
64/* Supporting them all might be overkill, let the compiler trim all
65 * unnecessary modes in interpolation codepath */
66#define INTERPOLATION_BORDER_MODE BORDER_MIRROR
67
68// Defines the maximum kernel half length
69// !! Make sure to sync this with the filter array !!
70#define MAX_HALF_FILTER_WIDTH 3
71
72// Add *verbose* (like one msg per pixel out) debug message to stderr
73#define DEBUG_PRINT_VERBOSE 0
74
75/* --------------------------------------------------------------------------
76 * Debug helpers
77 * ------------------------------------------------------------------------*/
78
79
80/* --------------------------------------------------------------------------
81 * Generic helpers
82 * ------------------------------------------------------------------------*/
83
88static inline __attribute__((always_inline)) ssize_t _clip(ssize_t i,
89 const ssize_t min,
90 const ssize_t max,
91 enum border_mode mode)
92{
93 switch(mode)
94 {
96 if(i < min)
97 {
98 i = min;
99 }
100 else if(i > max)
101 {
102 i = max;
103 }
104 break;
105 case BORDER_MIRROR:
106 if(i < min)
107 {
108 // i == min - 1 --> min + 1
109 // i == min - 2 --> min + 2, etc.
110 // but as min == 0 in all current cases, this really optimizes to i = -i
111 i = min + (min - i);
112 }
113 else if(i > max)
114 {
115 // i == max + 1 --> max - 1
116 // i == max + 2 --> max - 2, etc.
117 i = max - (i - max);
118 }
119 break;
120 case BORDER_WRAP:
121 if(i < min)
122 {
123 i = 1 + max - (min - i);
124 }
125 else if(i > max)
126 {
127 i = min + (i - max) - 1;
128 }
129 break;
130 case BORDER_CLAMP:
131 if(i < min || i > max)
132 {
133 /* Should not be used as is, we prevent -1 usage, filtering the taps
134 * we clip the sample indexes for. So understand this function is
135 * specific to its caller. */
136 i = -1;
137 }
138 break;
139 }
140
141 return i;
142}
143
144static inline __attribute__((always_inline)) void _prepare_tap_boundaries(int *tap_first,
145 int *tap_last,
146 const enum border_mode mode,
147 const int filterwidth,
148 const int t,
149 const int max)
150{
151 /* Check lower bound pixel index and skip as many pixels as necessary to
152 * fall into range */
153 *tap_first = 0;
154 if(mode == BORDER_CLAMP && t < 0)
155 {
156 *tap_first = -t;
157 }
158
159 // Same for upper bound pixel
160 *tap_last = filterwidth;
161 if(mode == BORDER_CLAMP && t + filterwidth >= max)
162 {
163 *tap_last = max - t;
164 }
165}
166
167/* --------------------------------------------------------------------------
168 * Interpolation kernels
169 * ------------------------------------------------------------------------*/
170
171/* --------------------------------------------------------------------------
172 * Bilinear interpolation
173 * ------------------------------------------------------------------------*/
174
175static float _maketaps_bilinear(float *taps,
176 const size_t num_taps,
177 const float width,
178 const float first_tap,
179 const float interval)
180{
181 static const dt_aligned_pixel_simd_t bootstrap = { 0.0f, 1.0f, 2.0f, 3.0f };
182 const dt_aligned_pixel_simd_t interval_v = dt_simd_set1(interval);
183 const dt_aligned_pixel_simd_t iter = dt_simd_set1(4.0f * interval);
184 dt_aligned_pixel_simd_t vt = dt_simd_set1(first_tap) + bootstrap * interval_v;
185
186 const int runs = (num_taps + 3) / 4;
187
188 for(size_t i = 0; i < runs; i++)
189 {
190 dt_store_simd_aligned(taps + 4 * i, dt_simd_set1(1.0f) - dt_simd_abs(vt));
191 vt += iter;
192 }
193 return 1.0f; //kernel norm is 1.0f by construction
194}
195
196/* --------------------------------------------------------------------------
197 * Bicubic interpolation
198 * ------------------------------------------------------------------------*/
199
200static float _maketaps_bicubic(float *taps,
201 const size_t num_taps,
202 const float width,
203 const float first_tap,
204 const float interval)
205{
206 static const dt_aligned_pixel_simd_t bootstrap = { 0.0f, 1.0f, 2.0f, 3.0f };
207 const dt_aligned_pixel_simd_t half = dt_simd_set1(0.5f);
208 const dt_aligned_pixel_simd_t two = dt_simd_set1(2.0f);
209 const dt_aligned_pixel_simd_t three = dt_simd_set1(3.0f);
210 const dt_aligned_pixel_simd_t four = dt_simd_set1(4.0f);
211 const dt_aligned_pixel_simd_t five = dt_simd_set1(5.0f);
212 const dt_aligned_pixel_simd_t eight = dt_simd_set1(8.0f);
213 const dt_aligned_pixel_simd_t interval_v = dt_simd_set1(interval);
214 const dt_aligned_pixel_simd_t iter = dt_simd_set1(4.0f * interval);
215 dt_aligned_pixel_simd_t vt = dt_simd_set1(first_tap) + bootstrap * interval_v;
216
217 const int runs = (num_taps + 3) / 4;
218
219 for(size_t i = 0; i < runs; i++)
220 {
221 const dt_aligned_pixel_simd_t vt_abs = dt_simd_abs(vt);
222 const dt_aligned_pixel_simd_t t2 = vt * vt;
223 const dt_aligned_pixel_simd_t t5 = five * vt_abs;
224 const dt_aligned_pixel_simd_t r12 = (vt_abs * (t5 - eight - t2) + four) * half;
225 const dt_aligned_pixel_simd_t r01 = ((three * t2 - t5) * vt_abs + two) * half;
226 dt_aligned_pixel_simd_t taps4 = r12;
228 taps4[c] = (vt_abs[c] <= 1.0f) ? r01[c] : r12[c];
229 dt_store_simd_aligned(taps + 4 * i, taps4);
230 vt += iter;
231 }
232 return 1.0f; //kernel norm is 1.0f by construction
233}
234
235/* --------------------------------------------------------------------------
236 * Mitchell-Netravali interpolation (B = C = 1/3)
237 *
238 * A separable cubic from the Mitchell-Netravali (B,C) family (SIGGRAPH 1988).
239 * B = C = 1/3 is the classic general-purpose reconstruction filter: it trades a
240 * hair of sharpness for drastically reduced ringing versus interpolating cubics
241 * (Catmull-Rom) and windowed-sinc (Lanczos). Its negative excursion is tiny
242 * (~3% vs Lanczos3's much larger overshoot), so it is effectively halo-free on
243 * photographic content and never blows alpha/colour far out of range at edges.
244 *
245 * Piecewise weights (already divided by 6), support [-2, 2]:
246 * |t| < 1 : (7/6)|t|^3 - 2|t|^2 + 8/9
247 * 1<=|t|<2: -(7/18)|t|^3 + 2|t|^2 - (10/3)|t| + 16/9
248 * It is a partition of unity (taps sum to 1 on the integer grid), so the
249 * upsampling norm is 1 by construction like bilinear/bicubic; the downsampling
250 * path renormalizes from the summed taps separately.
251 * ------------------------------------------------------------------------*/
252
253static float _maketaps_mitchell(float *taps,
254 const size_t num_taps,
255 const float width,
256 const float first_tap,
257 const float interval)
258{
259 static const dt_aligned_pixel_simd_t bootstrap = { 0.0f, 1.0f, 2.0f, 3.0f };
260 const dt_aligned_pixel_simd_t c7_6 = dt_simd_set1(7.0f / 6.0f);
261 const dt_aligned_pixel_simd_t c2 = dt_simd_set1(2.0f);
262 const dt_aligned_pixel_simd_t c8_9 = dt_simd_set1(8.0f / 9.0f);
263 const dt_aligned_pixel_simd_t c7_18 = dt_simd_set1(7.0f / 18.0f);
264 const dt_aligned_pixel_simd_t c10_3 = dt_simd_set1(10.0f / 3.0f);
265 const dt_aligned_pixel_simd_t c16_9 = dt_simd_set1(16.0f / 9.0f);
266 const dt_aligned_pixel_simd_t interval_v = dt_simd_set1(interval);
267 const dt_aligned_pixel_simd_t iter = dt_simd_set1(4.0f * interval);
268 dt_aligned_pixel_simd_t vt = dt_simd_set1(first_tap) + bootstrap * interval_v;
269
270 const int runs = (num_taps + 3) / 4;
271
272 for(size_t i = 0; i < runs; i++)
273 {
274 const dt_aligned_pixel_simd_t a = dt_simd_abs(vt);
275 const dt_aligned_pixel_simd_t a2 = a * a;
276 const dt_aligned_pixel_simd_t a3 = a2 * a;
277 // inner lobe (|t| < 1) and outer lobe (1 <= |t| < 2)
278 const dt_aligned_pixel_simd_t r01 = c7_6 * a3 - c2 * a2 + c8_9;
279 const dt_aligned_pixel_simd_t r12 = c2 * a2 - c7_18 * a3 - c10_3 * a + c16_9;
280 dt_aligned_pixel_simd_t taps4 = r12;
282 taps4[c] = (a[c] <= 1.0f) ? r01[c] : r12[c];
283 dt_store_simd_aligned(taps + 4 * i, taps4);
284 vt += iter;
285 }
286 return 1.0f; // kernel norm is 1.0f by construction (partition of unity)
287}
288
289/* --------------------------------------------------------------------------
290 * All our known interpolators
291 * ------------------------------------------------------------------------*/
292
293/* !!! !!! !!!
294 * Make sure MAX_HALF_FILTER_WIDTH is at least equal to the maximum width
295 * of this filter list. Otherwise bad things will happen
296 * !!! !!! !!!
297 */
298static const struct dt_interpolation dt_interpolator[] = {
300 .name = "bilinear",
301 .width = 1,
302 .maketaps = &_maketaps_bilinear,
303 },
305 .name = "bicubic",
306 .width = 2,
307 .maketaps = &_maketaps_bicubic,
308 },
310 .name = "mitchell",
311 .width = 2,
312 .maketaps = &_maketaps_mitchell,
313 },
314};
315
316/* --------------------------------------------------------------------------
317 * Kernel utility methods
318 * ------------------------------------------------------------------------*/
319
320static inline __attribute__((always_inline)) float _compute_upsampling_kernel(const struct dt_interpolation *itor,
321 float *kernel,
322 int *first,
323 float t)
324{
325 // find first pixel contributing to the filter's kernel. We need
326 // floorf() because a simple cast to int truncates toward zero,
327 // yielding an incorrect result for the slightly-negative positions
328 // that can occur at the top and left edges when doing perspective
329 // correction
330 int f = (int)floorf(t) - itor->width + 1;
331 if(first)
332 {
333 *first = f;
334 }
335
336 /* Find closest integer position and then offset that to match first
337 * filtered sample position */
338 t = t - (float)f;
339
340 // compute the taps and return the kernel norm
341 return itor->maketaps(kernel, 2*itor->width, itor->width, t, -1.0f);
342}
343
354static inline void _compute_downsampling_kernel(const struct dt_interpolation *itor,
355 int *taps,
356 int *first,
357 float *kernel,
358 float *norm,
359 const float outoinratio,
360 const int xout)
361{
362 // Keep this at hand
363 const float w = (float)itor->width;
364
365 /* Compute the phase difference between output pixel and its
366 * input corresponding input pixel */
367 const float xin = ceil_fast(((float)xout - w) / outoinratio);
368 if(first)
369 {
370 *first = (int)xin;
371 }
372
373 // Compute first interpolator parameter
374 float t = xin * outoinratio - (float)xout;
375
376 // Compute all filter taps
377 int num_taps = *taps = (int)((w - t) / outoinratio);
378 itor->maketaps(kernel, num_taps, itor->width, t, outoinratio);
379 // compute the kernel norm if requested
380 if (norm)
381 {
382 float n = 0.0f;
383 for(size_t i = 0; i < num_taps; i++)
384 n += kernel[i];
385 *norm = n;
386 }
387}
388
389/* --------------------------------------------------------------------------
390 * Sample interpolation function (see usage in iop/lens.c and iop/clipping.c)
391 * ------------------------------------------------------------------------*/
392
393#define MAX_KERNEL_REQ ((2 * (MAX_HALF_FILTER_WIDTH) + 3) & (~3))
394
397 const float *in,
398 const float x,
399 const float y,
400 const int width,
401 const int height,
402 const int samplestride,
403 const int linestride)
404{
405 assert(itor->width < (MAX_HALF_FILTER_WIDTH + 1));
406
407 float DT_ALIGNED_ARRAY kernelh[MAX_KERNEL_REQ];
408 float DT_ALIGNED_ARRAY kernelv[MAX_KERNEL_REQ];
409
410 // Compute both horizontal and vertical kernels
411 float normh = _compute_upsampling_kernel(itor, kernelh, NULL, x);
412 float normv = _compute_upsampling_kernel(itor, kernelv, NULL, y);
413
414 int ix = (int)x;
415 int iy = (int)y;
416
417 /* Now 2 cases, the pixel + filter width goes outside the image
418 * in that case we have to use index clipping to keep all reads
419 * in the input image (slow path) or we are sure it won't fall
420 * outside and can do more simple code */
421 float r;
422 if(ix >= (itor->width - 1) && iy >= (itor->width - 1) && ix < (width - itor->width)
423 && iy < (height - itor->width))
424 {
425 // Inside image boundary case
426
427 // Go to top left pixel
428 in = (float *)in + linestride * iy + ix * samplestride;
429 in = in - (itor->width - 1) * (samplestride + linestride);
430
431 // Apply the kernel
432 float s = 0.f;
433 for(int i = 0; i < 2 * itor->width; i++)
434 {
435 float h = 0.0f;
436 for(int j = 0; j < 2 * itor->width; j++)
437 {
438 h += kernelh[j] * in[j * samplestride];
439 }
440 s += kernelv[i] * h;
441 in += linestride;
442 }
443 r = fmaxf(0.0f, s / (normh * normv));
444 }
445 else if(ix >= 0 && iy >= 0 && ix < width && iy < height)
446 {
447 // At least a valid coordinate
448
449 // Point to the upper left pixel index wise
450 iy -= itor->width - 1;
451 ix -= itor->width - 1;
452
453 static const enum border_mode bordermode = INTERPOLATION_BORDER_MODE;
454 assert(bordermode != BORDER_CLAMP); // XXX in clamp mode, norms would be wrong
455
456 int xtap_first;
457 int xtap_last;
458 _prepare_tap_boundaries(&xtap_first, &xtap_last,
459 bordermode, 2 * itor->width, ix, width);
460
461 int ytap_first;
462 int ytap_last;
463 _prepare_tap_boundaries(&ytap_first, &ytap_last,
464 bordermode, 2 * itor->width, iy, height);
465
466 // Apply the kernel
467 float s = 0.f;
468 for(ssize_t i = ytap_first; i < ytap_last; i++)
469 {
470 const ssize_t clip_y = _clip(iy + i, 0, height - 1, bordermode);
471 float h = 0.0f;
472 for(ssize_t j = xtap_first; j < xtap_last; j++)
473 {
474 const ssize_t clip_x = _clip(ix + j, 0, width - 1, bordermode);
475 const float *ipixel = in + clip_y * linestride + clip_x * samplestride;
476 h += kernelh[j] * ipixel[0];
477 }
478 s += kernelv[i] * h;
479 }
480
481 r = fmaxf(0.0f, s / (normh * normv));
482 }
483 else
484 {
485 // invalid coordinate
486 r = 0.0f;
487 }
488 return r;
489}
490
491/* --------------------------------------------------------------------------
492 * Pixel interpolation function (see usage in iop/lens.c and iop/clipping.c)
493 * ------------------------------------------------------------------------*/
494
497 const float *in,
498 float *out,
499 const float x,
500 const float y,
501 const int width,
502 const int height,
503 const int linestride)
504{
505 assert(itor->width < (MAX_HALF_FILTER_WIDTH + 1));
506
507 // Quite a bit of space for kernels
508 float DT_ALIGNED_ARRAY kernelh[MAX_KERNEL_REQ];
509 float DT_ALIGNED_ARRAY kernelv[MAX_KERNEL_REQ];
510
511 // Compute both horizontal and vertical kernels
512 float normh = _compute_upsampling_kernel(itor, kernelh, NULL, x);
513 float normv = _compute_upsampling_kernel(itor, kernelv, NULL, y);
514
515 // Precompute the inverse of the filter norm for later use
516 const float oonorm = (1.f / (normh * normv));
517
518 /* Now 2 cases, the pixel + filter width goes outside the image
519 * in that case we have to use index clipping to keep all reads
520 * in the input image (slow path) or we are sure it won't fall
521 * outside and can do more simple code */
522 int ix = (int)x;
523 int iy = (int)y;
524
525 if(ix >= (itor->width - 1)
526 && iy >= (itor->width - 1)
527 && ix < (width - itor->width)
528 && iy < (height - itor->width))
529 {
530 // Inside image boundary case
531
532 // Go to top left pixel
533 in = (float *)in + linestride * iy + ix * 4;
534 in = in - (itor->width - 1) * (4 + linestride);
535
536 const size_t itor_width = 2 * itor->width;
537
538 // Apply the kernel
539 dt_aligned_pixel_simd_t pixel = dt_simd_set1(0.0f);
540 for(size_t i = 0; i < itor_width; i++)
541 {
542 dt_aligned_pixel_simd_t h = dt_simd_set1(0.0f);
543 for(size_t j = 0; j < itor_width; j++)
544 h += dt_load_simd_aligned(in + 4 * j) * dt_simd_set1(kernelh[j]);
545 pixel += h * dt_simd_set1(kernelv[i]);
546 in += linestride;
547 }
548
549 dt_store_simd(out, dt_simd_max_zero(pixel * dt_simd_set1(oonorm)));
550 }
551 else if(ix >= 0 && iy >= 0 && ix < width && iy < height)
552 {
553 // At least a valid coordinate
554
555 // Point to the upper left pixel index wise
556 iy -= itor->width - 1;
557 ix -= itor->width - 1;
558
559 static const enum border_mode bordermode = INTERPOLATION_BORDER_MODE;
560 assert(bordermode != BORDER_CLAMP); // XXX in clamp mode, norms would be wrong
561
562 int xtap_first;
563 int xtap_last;
564 _prepare_tap_boundaries(&xtap_first, &xtap_last,
565 bordermode, 2 * itor->width, ix, width);
566
567 int ytap_first;
568 int ytap_last;
569 _prepare_tap_boundaries(&ytap_first, &ytap_last,
570 bordermode, 2 * itor->width, iy, height);
571
572 // Apply the kernel
573 dt_aligned_pixel_simd_t pixel = dt_simd_set1(0.0f);
574 for(ssize_t i = ytap_first; i < ytap_last; i++)
575 {
576 const ssize_t clip_y = _clip(iy + i, 0, height - 1, bordermode);
577 dt_aligned_pixel_simd_t h = dt_simd_set1(0.0f);
578 const float *ipixel = in + clip_y * linestride;
579 for(ssize_t j = xtap_first; j < xtap_last; j++)
580 {
581 const ssize_t clip_x = _clip(ix + j, 0, width - 1, bordermode);
582 h += dt_load_simd_aligned(ipixel + 4 * clip_x) * dt_simd_set1(kernelh[j]);
583 }
584 pixel += h * dt_simd_set1(kernelv[i]);
585 }
586
587 dt_store_simd(out, dt_simd_max_zero(pixel * dt_simd_set1(oonorm)));
588 }
589 else
590 {
591 // data for *out has no valid *in location so just set to zero.
593 }
594}
595
596/* --------------------------------------------------------------------------
597 * Interpolation factory
598 * ------------------------------------------------------------------------*/
599
601{
602 const struct dt_interpolation *itor = NULL;
603
605 {
606 // Find user preferred interpolation method
607 const char *uipref =
608 dt_conf_get_string_const("plugins/lighttable/export/pixel_interpolator");
609
610 for(int i = DT_INTERPOLATION_FIRST;
611 uipref && i < DT_INTERPOLATION_LAST;
612 i++)
613 {
614 if(!strcmp(uipref, dt_interpolator[i].name))
615 {
616 // Found the one
617 itor = &dt_interpolator[i];
618 break;
619 }
620 }
621
622 /* In the case the search failed (!uipref or name not found),
623 * prepare later search pass with default fallback */
625 }
627 {
628 // Find user preferred interpolation method
629 const char *uipref =
630 dt_conf_get_string_const("plugins/lighttable/export/pixel_interpolator_warp");
631 for(int i = DT_INTERPOLATION_FIRST;
632 uipref && i < DT_INTERPOLATION_LAST;
633 i++)
634 {
635 if(!strcmp(uipref, dt_interpolator[i].name))
636 {
637 // Found the one
638 itor = &dt_interpolator[i];
639 break;
640 }
641 }
642
643 /* In the case the search failed (!uipref or name not found),
644 * prepare later search pass with default fallback */
646 }
647 if(IS_NULL_PTR(itor))
648 {
649 // Did not find the userpref one or we've been asked for a specific one
651 {
652 if(dt_interpolator[i].id == type)
653 {
654 itor = &dt_interpolator[i];
655 break;
656 }
658 {
659 itor = &dt_interpolator[i];
660 }
661 }
662 }
663
664 return itor;
665}
666
667/* --------------------------------------------------------------------------
668 * Image resampling
669 * ------------------------------------------------------------------------*/
670
711static gboolean _prepare_resampling_plan(const struct dt_interpolation *itor,
712 const int in,
713 const int in_x0,
714 const int out,
715 const int out_x0,
716 const float scale,
717 int **plength,
718 float **pkernel,
719 int **pindex,
720 int **pmeta)
721{
722 // Safe return values
723 *plength = NULL;
724 *pkernel = NULL;
725 *pindex = NULL;
726 if(pmeta)
727 {
728 *pmeta = NULL;
729 }
730
731 if(scale == 1.f)
732 {
733 // No resampling required
734 return FALSE;
735 }
736
737 // Compute common upsampling/downsampling memory requirements
738 int maxtapsapixel;
739 if(scale > 1.f)
740 {
741 // Upscale... the easy one. The values are exact
742 maxtapsapixel = 2 * itor->width;
743 }
744 else
745 {
746 // Downscale... going for worst case values memory wise
747 maxtapsapixel = ceil_fast((float)2 * (float)itor->width / scale);
748 }
749
750 int nlengths = out;
751 const int nindex = maxtapsapixel * out;
752 const int nkernel = maxtapsapixel * out;
753 const size_t lengthreq = dt_round_size(nlengths * sizeof(int), DT_CACHELINE_BYTES);
754 const size_t indexreq = dt_round_size(nindex * sizeof(int), DT_CACHELINE_BYTES);
755 const size_t kernelreq = dt_round_size(nkernel * sizeof(float), DT_CACHELINE_BYTES);
756 const size_t scratchreq = dt_round_size(maxtapsapixel * sizeof(float) + 4 * sizeof(float), DT_CACHELINE_BYTES);
757 // NB: because sse versions compute four taps a time
758 const size_t metareq = dt_round_size(pmeta ? 4 * sizeof(int) * out : 0, DT_CACHELINE_BYTES);
759
760 const size_t totalreq = kernelreq + lengthreq + indexreq + scratchreq + metareq;
761 void *blob = dt_pixelpipe_cache_alloc_align_cache(totalreq, 0);
762 if(IS_NULL_PTR(blob)) return TRUE;
763
764 int *lengths = (int *)blob;
765 blob = (char *)blob + lengthreq;
766 int *index = (int *)blob;
767 blob = (char *)blob + indexreq;
768 float *kernel = (float *)blob;
769 blob = (char *)blob + kernelreq;
770 // Not `scratchreq ? ... : NULL`, unlike meta below: scratchreq is rounded up from at least
771 // 4 floats of headroom, so it is unconditionally non-zero and the NULL case cannot happen.
772 // Spelling it as a maybe-NULL made three later uses read as possible NULL dereferences --
773 // to a static analyser and to anyone reading the code. metareq IS conditional (pmeta), so
774 // meta keeps its ternary.
775 float *scratchpad = (float *)blob;
776 blob = (char *)blob + scratchreq;
777 int *meta = metareq ? (int *)blob : NULL;
778// blob = (char *)blob + metareq;
779
780 /* setting this as a const should help the compilers trim all unnecessary
781 * codepaths */
782 const enum border_mode bordermode = RESAMPLING_BORDER_MODE;
783
784 /* Upscale and downscale differ in subtle points, getting rid of code
785 * duplication might have been tricky and i prefer keeping the code
786 * as straight as possible */
787 if(scale > 1.f)
788 {
789 int kidx = 0;
790 int iidx = 0;
791 int lidx = 0;
792 int midx = 0;
793 for(int x = 0; x < out; x++)
794 {
795 if(meta)
796 {
797 meta[midx++] = lidx;
798 meta[midx++] = kidx;
799 meta[midx++] = iidx;
800 }
801
802 // Projected position in input samples
803 float fx = (float)(out_x0 + x) / scale - in_x0;
804
805 // Compute the filter kernel at that position
806 int first;
807 (void)_compute_upsampling_kernel(itor, scratchpad, &first, fx);
808
809 /* Check lower and higher bound pixel index and skip as many pixels as
810 * necessary to fall into range */
811 int tap_first;
812 int tap_last;
813 _prepare_tap_boundaries(&tap_first, &tap_last, bordermode, 2 * itor->width, first, in);
814
815 // Track number of taps that will be used
816 lengths[lidx++] = tap_last - tap_first;
817
818 // Precompute the inverse of the norm
819 float norm = 0.f;
820 for(int tap = tap_first; tap < tap_last; tap++)
821 {
822 norm += scratchpad[tap];
823 }
824 norm = 1.f / norm;
825
826 /* Unlike single pixel or single sample code, here it's interesting to
827 * precompute the normalized filter kernel as this will avoid dividing
828 * by the norm for all processed samples/pixels
829 * NB: use the same loop to put in place the index list */
830 first += tap_first;
831 for(int tap = tap_first; tap < tap_last; tap++)
832 {
833 kernel[kidx++] = scratchpad[tap] * norm;
834 index[iidx++] = _clip(first++, 0, in - 1, bordermode);
835 }
836 }
837 }
838 else
839 {
840 int kidx = 0;
841 int iidx = 0;
842 int lidx = 0;
843 int midx = 0;
844 for(int x = 0; x < out; x++)
845 {
846 if(meta)
847 {
848 meta[midx++] = lidx;
849 meta[midx++] = kidx;
850 meta[midx++] = iidx;
851 }
852
853 // Compute downsampling kernel centered on output position
854 int taps;
855 int first;
856 _compute_downsampling_kernel(itor, &taps, &first, scratchpad, NULL, scale, out_x0 + x);
857
858 /* Check lower and higher bound pixel index and skip as many pixels as
859 * necessary to fall into range */
860 int tap_first;
861 int tap_last;
862 _prepare_tap_boundaries(&tap_first, &tap_last, bordermode, taps, first, in);
863
864 // Track number of taps that will be used
865 lengths[lidx++] = tap_last - tap_first;
866
867 // Precompute the inverse of the norm
868 float norm = 0.f;
869 for(int tap = tap_first; tap < tap_last; tap++)
870 {
871 norm += scratchpad[tap];
872 }
873 norm = 1.f / norm;
874
875 /* Unlike single pixel or single sample code, here it's interesting to
876 * precompute the normalized filter kernel as this will avoid dividing
877 * by the norm for all processed samples/pixels
878 * NB: use the same loop to put in place the index list */
879 first += tap_first;
880 for(int tap = tap_first; tap < tap_last; tap++)
881 {
882 kernel[kidx++] = scratchpad[tap] * norm;
883 index[iidx++] = _clip(first++, 0, in - 1, bordermode);
884 }
885 }
886 }
887
888 // Validate plan wrt caller
889 *plength = lengths;
890 *pindex = index;
891 *pkernel = kernel;
892 if(pmeta)
893 {
894 *pmeta = meta;
895 }
896
897 return FALSE;
898}
899
900#define TILE_ROWS 128
901
903static void _interpolation_resample_plain(const struct dt_interpolation *itor,
904 float *const restrict out,
905 const dt_iop_roi_t *const roi_out,
906 const float *const restrict in,
907 const dt_iop_roi_t *const roi_in)
908{
909 int *hindex = NULL;
910 int *hlength = NULL;
911 float *hkernel = NULL;
912 int *vindex = NULL;
913 int *vlength = NULL;
914 float *vkernel = NULL;
915 int *vmeta = NULL;
916
917 const int32_t in_stride_floats = roi_in->width * 4;
918 const int32_t out_stride_floats = roi_out->width * 4;
919
920 // Fast code path for 1:1 copy, only cropping area can change
921 if(roi_out->scale == 1.f || roi_out->scale == roi_in->scale)
922 {
923 const size_t x0 = (roi_out->x - roi_in->x) * 4 * sizeof(float);
924 const size_t y0 = (roi_out->y - roi_in->y);
926 for(int yt = 0; yt < roi_out->height; yt += TILE_ROWS)
927 {
928 const int y_end = MIN(yt + TILE_ROWS, roi_out->height);
929 for(int y = yt; y < y_end; y++)
930 memcpy((char *)__builtin_assume_aligned(out, 64) + (size_t)out_stride_floats * sizeof(float) * y,
931 (char *)__builtin_assume_aligned(in, 64) + (size_t)in_stride_floats * sizeof(float) * (y + y0) + x0,
932 out_stride_floats * sizeof(float));
933 }
934
935
936 // All done, so easy case
937 return;
938 }
939
940 // Generic non 1:1 case... much more complicated :D
941
942 // The actual resampling ratio between the two buffers,
943 // not the absolute pipeline scale
944 const float resample_scale = roi_out->scale / roi_in->scale;
945
946 if(_prepare_resampling_plan(itor, roi_in->width, roi_in->x,
947 roi_out->width, roi_out->x, resample_scale,
948 &hlength, &hkernel, &hindex, NULL))
949 goto exit;
950
951 if(_prepare_resampling_plan(itor, roi_in->height, roi_in->y,
952 roi_out->height, roi_out->y, resample_scale,
953 &vlength, &vkernel, &vindex, &vmeta))
954 goto exit;
955
956 const size_t height = roi_out->height;
957 const size_t width = roi_out->width;
958
959 // Process each output line
961 for(size_t oy = 0; oy < height; oy++)
962 {
963 // Initialize column resampling indexes
964 int vlidx = vmeta[3 * oy + 0]; // V(ertical) L(ength) I(n)d(e)x
965 int vkidx = vmeta[3 * oy + 1]; // V(ertical) K(ernel) I(n)d(e)x
966 int viidx = vmeta[3 * oy + 2]; // V(ertical) I(ndex) I(n)d(e)x
967
968 // Initialize row resampling indexes
969 int hlidx = 0; // H(orizontal) L(ength) I(n)d(e)x
970 int hkidx = 0; // H(orizontal) K(ernel) I(n)d(e)x
971
972 // Number of lines contributing to the output line
973 int vl = vlength[vlidx++]; // V(ertical) L(ength)
974
975 // Process each output column
976 for(size_t ox = 0; ox < width; ox++)
977 {
978 // This will hold the resulting pixel
979 dt_aligned_pixel_simd_t vs = dt_simd_set1(0.0f);
980
981 // Number of horizontal samples contributing to the output
982 const int hl = hlength[hlidx++]; // H(orizontal) L(ength)
983 const int *const column_hindex = hindex + hkidx;
984 const float *const column_hkernel = hkernel + hkidx;
985 const int *const column_vindex = vindex + viidx;
986 const float *const column_vkernel = vkernel + vkidx;
987
988 for(size_t iy = 0; iy < vl; iy++)
989 {
990 // This is our input line
991 const size_t baseidx_vindex = (size_t)column_vindex[iy] * in_stride_floats;
992
993 dt_aligned_pixel_simd_t vhs = dt_simd_set1(0.0f);
994
995 for(size_t ix = 0; ix < hl; ix++)
996 {
997 // Apply the precomputed filter kernel
998 const size_t baseidx = baseidx_vindex + (size_t)column_hindex[ix] * 4;
999 const float htap = column_hkernel[ix];
1000 vhs += dt_load_simd_aligned(in + baseidx) * dt_simd_set1(htap);
1001 }
1002
1003 // Accumulate contribution from this line
1004 const float vtap = column_vkernel[iy];
1005 vs += vhs * dt_simd_set1(vtap);
1006 }
1007
1008 // Output pixel is ready
1009 const size_t baseidx = (size_t)oy * out_stride_floats + (size_t)ox * 4;
1010
1011 // Clip negative RGB that may be produced by Lanczos undershooting
1012 // Negative RGB are invalid values no matter the RGB space (light is positive)
1013 dt_aligned_pixel_t pixel;
1014 dt_store_simd_aligned(pixel, dt_simd_max_zero(vs));
1015 copy_pixel_nontemporal(out + baseidx, pixel);
1016
1017 // The vertical support is fixed for the whole output row. Only the
1018 // horizontal plan advances from one output column to the next.
1019 hkidx += hl;
1020 }
1021 }
1022
1023
1025
1026exit:
1027 /* Free the resampling plans. It's nasty to optimize allocs like that, but
1028 * it simplifies the code :-D. The length array is in fact the only memory
1029 * allocated. */
1032}
1033
1038 float *out,
1039 const dt_iop_roi_t *const roi_out,
1040 const float *const in,
1041 const dt_iop_roi_t *const roi_in)
1042{
1043 return _interpolation_resample_plain(itor, out, roi_out, in, roi_in);
1044}
1045
1052 float *out,
1053 const dt_iop_roi_t *const roi_out,
1054 const float *const in,
1055 const dt_iop_roi_t *const roi_in)
1056{
1057 dt_iop_roi_t oroi = *roi_out;
1058 //oroi.x = oroi.y = 0;
1059
1060 dt_iop_roi_t iroi = *roi_in;
1061 //iroi.x = iroi.y = 0;
1062
1063 dt_interpolation_resample(itor, out, &oroi, in, &iroi);
1064}
1065
1066#ifdef HAVE_OPENCL
1067/* The kernels this subsystem compiles, owned HERE. They used to be handed to
1068 * common/opencl.c, parked on the application-wide dt_opencl_t, and read back from it --
1069 * a round trip through a god-struct that added nothing but an ordering. opencl.c still
1070 * calls init/free, because the kernels must be built after the devices exist, but the
1071 * pointer never leaves this file. */
1073
1075{
1078
1079 const int program = 2; // basic.cl, from programs.conf
1081 dt_opencl_create_kernel(program, "interpolation_resample");
1083}
1084
1086{
1089 if(IS_NULL_PTR(g)) return;
1090 // destroy kernels
1091 dt_opencl_free_kernel(g->kernel_interpolation_resample);
1092 dt_free(g);
1093}
1094
1095static uint32_t roundToNextPowerOfTwo(uint32_t x)
1096{
1097 x--;
1098 x |= x >> 1;
1099 x |= x >> 2;
1100 x |= x >> 4;
1101 x |= x >> 8;
1102 x |= x >> 16;
1103 x++;
1104 return x;
1105}
1106
1111 const int devid,
1112 cl_mem dev_out,
1113 const dt_iop_roi_t *const roi_out,
1114 cl_mem dev_in,
1115 const dt_iop_roi_t *const roi_in)
1116{
1117 int *hindex = NULL;
1118 int *hlength = NULL;
1119 float *hkernel = NULL;
1120 int *hmeta = NULL;
1121 int *vindex = NULL;
1122 int *vlength = NULL;
1123 float *vkernel = NULL;
1124 int *vmeta = NULL;
1125
1126 cl_int err = DT_OPENCL_DEFAULT_ERROR;
1127
1128 cl_mem dev_hindex = NULL;
1129 cl_mem dev_hlength = NULL;
1130 cl_mem dev_hkernel = NULL;
1131 cl_mem dev_hmeta = NULL;
1132 cl_mem dev_vindex = NULL;
1133 cl_mem dev_vlength = NULL;
1134 cl_mem dev_vkernel = NULL;
1135 cl_mem dev_vmeta = NULL;
1136
1137 // Fast code path for 1:1 copy, only cropping area can change
1138 if(roi_out->scale == 1.f || roi_out->scale == roi_in->scale)
1139 {
1140 size_t iorigin[] = { roi_out->x - roi_in->x, roi_out->y - roi_in->y, 0 };
1141 size_t oorigin[] = { 0, 0, 0 };
1142 size_t region[] = { roi_out->width, roi_out->height, 1 };
1143
1144 // copy original input from dev_in -> dev_out as starting point
1145 err = dt_opencl_enqueue_copy_image(devid, dev_in, dev_out, iorigin, oorigin, region);
1146 if(err != CL_SUCCESS) goto error;
1147
1148 // All done, so easy case
1149 return CL_SUCCESS;
1150 }
1151
1152 // Generic non 1:1 case... much more complicated :D
1153
1154 // The actual resampling ratio between the two buffers,
1155 // not the absolute pipeline scale
1156 const float resample_scale = roi_out->scale / roi_in->scale;
1157
1158 if(_prepare_resampling_plan(itor, roi_in->width, roi_in->x,
1159 roi_out->width, roi_out->x, resample_scale,
1160 &hlength, &hkernel, &hindex, &hmeta))
1161 goto error;
1162
1163 if(_prepare_resampling_plan(itor, roi_in->height, roi_in->y,
1164 roi_out->height, roi_out->y, resample_scale,
1165 &vlength, &vkernel, &vindex, &vmeta))
1166 goto error;
1167
1168 int hmaxtaps = -1, vmaxtaps = -1;
1169 for(int k = 0; k < roi_out->width; k++) hmaxtaps = MAX(hmaxtaps, hlength[k]);
1170 for(int k = 0; k < roi_out->height; k++) vmaxtaps = MAX(vmaxtaps, vlength[k]);
1171
1172 // strategy: process image column-wise (local[0] = 1). For each row generate
1173 // a number of parallel work items each taking care of one horizontal convolution,
1174 // then sum over work items to do the vertical convolution
1175
1177 const int width = roi_out->width;
1178 const int height = roi_out->height;
1179
1180 // make sure blocksize is not too large
1181 const int taps = roundToNextPowerOfTwo(vmaxtaps);
1182 // the number of work items per row rounded up to a power of 2
1183 // (for quick recursive reduction)
1184
1185 int vblocksize;
1186
1189 { .xoffset = 0,
1190 .xfactor = 1,
1191 .yoffset = 0,
1192 .yfactor = 1,
1193 .cellsize = 4 * sizeof(float),
1194 .overhead = hmaxtaps * sizeof(float) + hmaxtaps * sizeof(int),
1195 .sizex = 1,
1196 .sizey = (1 << 16) * taps };
1197
1198 if(dt_opencl_local_buffer_opt(devid, kernel, &locopt))
1199 vblocksize = locopt.sizey;
1200 else
1201 vblocksize = 1;
1202
1203 if(vblocksize < taps)
1204 {
1205 // our strategy does not work: the vertical number of taps exceeds
1206 // the vertical workgroupsize; there is no point in continuing on
1207 // the GPU - that would be way too slow; let's delegate the stuff
1208 // to the CPU then.
1209 err = CL_INVALID_WORK_GROUP_SIZE;
1210 goto error;
1211 }
1212
1213 size_t sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUP(height * taps, vblocksize), 1 };
1214 size_t local[3] = { 1, vblocksize, 1 };
1215
1216 // store resampling plan to device memory hindex, vindex, hkernel,
1217 // vkernel: (v|h)maxtaps might be too small, so store a bit more
1218 // than needed
1219 err = -999;
1220
1221 dev_hindex = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * width * (hmaxtaps + 1), hindex);
1222 if(IS_NULL_PTR(dev_hindex)) goto error;
1223
1224 dev_hlength = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * width, hlength);
1225 if(IS_NULL_PTR(dev_hlength)) goto error;
1226
1227 dev_hkernel = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * width * (hmaxtaps + 1), hkernel);
1228 if(IS_NULL_PTR(dev_hkernel)) goto error;
1229
1230 dev_hmeta = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * width * 3, hmeta);
1231 if(IS_NULL_PTR(dev_hmeta)) goto error;
1232
1233 dev_vindex = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * height * (vmaxtaps + 1), vindex);
1234 if(IS_NULL_PTR(dev_vindex)) goto error;
1235
1236 dev_vlength = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * height, vlength);
1237 if(IS_NULL_PTR(dev_vlength)) goto error;
1238
1239 dev_vkernel = dt_opencl_copy_host_to_device_constant(devid, sizeof(float) * height * (vmaxtaps + 1), vkernel);
1240 if(IS_NULL_PTR(dev_vkernel)) goto error;
1241
1242 dev_vmeta = dt_opencl_copy_host_to_device_constant(devid, sizeof(int) * height * 3, vmeta);
1243 if(IS_NULL_PTR(dev_vmeta)) goto error;
1244
1245 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
1246 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
1247 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
1248 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
1249 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_hmeta);
1250 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), (void *)&dev_vmeta);
1251 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), (void *)&dev_hlength);
1252 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(cl_mem), (void *)&dev_vlength);
1253 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), (void *)&dev_hindex);
1254 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(cl_mem), (void *)&dev_vindex);
1255 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), (void *)&dev_hkernel);
1256 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(cl_mem), (void *)&dev_vkernel);
1257 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(int), (void *)&hmaxtaps);
1258 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(int), (void *)&taps);
1259 dt_opencl_set_kernel_arg(devid, kernel, 14, hmaxtaps * sizeof(float), NULL);
1260 dt_opencl_set_kernel_arg(devid, kernel, 15, hmaxtaps * sizeof(int), NULL);
1261 dt_opencl_set_kernel_arg(devid, kernel, 16, vblocksize * 4 * sizeof(float), NULL);
1262 err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
1263
1264error:
1265
1266 dt_opencl_release_mem_object(dev_hindex);
1267 dt_opencl_release_mem_object(dev_hlength);
1268 dt_opencl_release_mem_object(dev_hkernel);
1270 dt_opencl_release_mem_object(dev_vindex);
1271 dt_opencl_release_mem_object(dev_vlength);
1272 dt_opencl_release_mem_object(dev_vkernel);
1276 return err;
1277}
1278
1285 const int devid,
1286 cl_mem dev_out,
1287 const dt_iop_roi_t *const roi_out,
1288 cl_mem dev_in,
1289 const dt_iop_roi_t *const roi_in)
1290{
1291 dt_iop_roi_t oroi = *roi_out;
1292 //oroi.x = oroi.y = 0;
1293
1294 dt_iop_roi_t iroi = *roi_in;
1295 //iroi.x = iroi.y = 0;
1296
1297 return dt_interpolation_resample_cl(itor, devid, dev_out, &oroi, dev_in, &iroi);
1298}
1299#endif
1300
1302 float *out,
1303 const dt_iop_roi_t *const roi_out,
1304 const float *const in,
1305 const dt_iop_roi_t *const roi_in)
1306{
1307 int *hindex = NULL;
1308 int *hlength = NULL;
1309 float *hkernel = NULL;
1310 int *vindex = NULL;
1311 int *vlength = NULL;
1312 float *vkernel = NULL;
1313 int *vmeta = NULL;
1314
1315
1316 const size_t out_stride = roi_out->width * sizeof(float);
1317 const size_t in_stride = roi_in->width * sizeof(float);
1318
1319 // Fast code path for 1:1 copy, only cropping area can change
1320 if(roi_out->scale == 1.f || roi_out->scale == roi_in->scale)
1321 {
1322 const size_t x0 = (roi_out->x - roi_in->x) * sizeof(float);
1323 const size_t y0 = (roi_out->y - roi_in->y);
1325 for(int y = 0; y < roi_out->height; y++)
1326 {
1327 float *i = (float *)((char *)in + in_stride * (y + y0) + x0);
1328 float *o = (float *)((char *)out + out_stride * y);
1329 memcpy(o, i, out_stride);
1330 }
1331 // All done, so easy case
1332 return;
1333 }
1334
1335 // Generic non 1:1 case... much more complicated :D
1336
1337 // Prepare resampling plans once and for all
1338 if(_prepare_resampling_plan(itor, roi_in->width, roi_in->x,
1339 roi_out->width, roi_out->x, roi_out->scale,
1340 &hlength, &hkernel, &hindex, NULL))
1341 goto exit;
1342
1343 if(_prepare_resampling_plan(itor, roi_in->height, roi_in->y,
1344 roi_out->height, roi_out->y, roi_out->scale,
1345 &vlength, &vkernel, &vindex, &vmeta))
1346 goto exit;
1347
1348 // Process each output line
1350 for(int oy = 0; oy < roi_out->height; oy++)
1351 {
1352 // Initialize column resampling indexes
1353 int vlidx = vmeta[3 * oy + 0]; // V(ertical) L(ength) I(n)d(e)x
1354 int vkidx = vmeta[3 * oy + 1]; // V(ertical) K(ernel) I(n)d(e)x
1355 int viidx = vmeta[3 * oy + 2]; // V(ertical) I(ndex) I(n)d(e)x
1356
1357 // Initialize row resampling indexes
1358 int hlidx = 0; // H(orizontal) L(ength) I(n)d(e)x
1359 int hkidx = 0; // H(orizontal) K(ernel) I(n)d(e)x
1360 int hiidx = 0; // H(orizontal) I(ndex) I(n)d(e)x
1361
1362 // Number of lines contributing to the output line
1363 int vl = vlength[vlidx++]; // V(ertical) L(ength)
1364
1365 // Process each output column
1366 for(int ox = 0; ox < roi_out->width; ox++)
1367 {
1368 // This will hold the resulting pixel
1369 float vs = 0.0f;
1370
1371 // Number of horizontal samples contributing to the output
1372 const int hl = hlength[hlidx++]; // H(orizontal) L(ength)
1373 for(int iy = 0; iy < vl; iy++)
1374 {
1375 // This is our input line
1376 const float *i = (float *)((char *)in + in_stride * vindex[viidx++]);
1377
1378 float vhs = 0.0f;
1379
1380 for(int ix = 0; ix < hl; ix++)
1381 {
1382 // Apply the precomputed filter kernel
1383 const size_t baseidx = (size_t)hindex[hiidx++];
1384 const float htap = hkernel[hkidx++];
1385 vhs += i[baseidx] * htap;
1386 }
1387
1388 // Accumulate contribution from this line
1389 const float vtap = vkernel[vkidx++];
1390 vs += vhs * vtap;
1391
1392 // Reset horizontal resampling context
1393 hkidx -= hl;
1394 hiidx -= hl;
1395 }
1396
1397 // Output pixel is ready
1398 float *o = (float *)((char *)out + (size_t)oy * out_stride
1399 + (size_t)ox * sizeof(float));
1400 *o = vs;
1401
1402 // Reset vertical resampling context
1403 viidx -= vl;
1404 vkidx -= vl;
1405
1406 // Progress in horizontal context
1407 hiidx += hl;
1408 hkidx += hl;
1409 }
1410 }
1411
1412 exit:
1413 /* Free the resampling plans. It's nasty to optimize allocs like that, but
1414 * it simplifies the code :-D. The length array is in fact the only memory
1415 * allocated. */
1418}
1419
1424 float *out,
1425 const dt_iop_roi_t *const roi_out,
1426 const float *const in,
1427 const dt_iop_roi_t *const roi_in)
1428{
1429 return _interpolation_resample_1c_plain(itor, out, roi_out, in, roi_in);
1430}
1431
1437 float *out,
1438 const dt_iop_roi_t *const roi_out,
1439 const float *const in,
1440 const dt_iop_roi_t *const roi_in)
1441{
1442 dt_iop_roi_t oroi = *roi_out;
1443 //oroi.x = oroi.y = 0;
1444
1445 dt_iop_roi_t iroi = *roi_in;
1446 //iroi.x = iroi.y = 0;
1447
1448 dt_interpolation_resample_1c(itor, out, &oroi, in, &iroi);
1449}
1450
1451// clang-format off
1452// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1453// vim: shiftwidth=2 expandtab tabstop=2 cindent
1454// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1455// 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
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)))
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)
#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 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 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 void _compute_downsampling_kernel(const struct dt_interpolation *itor, int *taps, int *first, float *kernel, float *norm, const float outoinratio, const int xout)
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
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3713
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
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:2679
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
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:2545
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:2560
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#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
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