Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
imagebuf.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2021 Ralf Brown.
4 Copyright (C) 2021-2022 Pascal Obry.
5 Copyright (C) 2022 Martin Bařinka.
6 Copyright (C) 2023, 2025-2026 Aurélien PIERRE.
7
8 darktable is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 darktable is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with darktable. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#include <stdarg.h>
23#include "system/macros.h"
24#include "system/openmp.h"
27#include "common/imagebuf.h"
28
29#ifdef _OPENMP
30static size_t parallel_imgop_minimum = 500000;
31#endif
32
33// Allocate one or more buffers as detailed in the given parameters. If any allocation fails, free all of them,
34// set the module's trouble flag, and return 1 (0 on success).
36 const struct dt_iop_roi_t *const roi_in,
37 const struct dt_iop_roi_t *const roi_out, ...)
38{
39 int err = 0;
40 va_list args;
41 // first pass: zero out all of the given buffer pointers
42 va_start(args,roi_out);
43 while (TRUE)
44 {
45 const int size = va_arg(args,int);
46 float **bufptr = va_arg(args,float**);
48 (void)va_arg(args,size_t*); // skip the extra pointer for per-thread allocations
49 if (size == 0 || IS_NULL_PTR(bufptr)) // end of arg list?
50 break;
51 *bufptr = NULL;
52 }
53 va_end(args);
54
55 // second pass: attempt to allocate the requested buffers
56 va_start(args,roi_out);
57 while (!err)
58 {
59 const int size = va_arg(args,int);
60 float **bufptr = va_arg(args,float**);
61 size_t *paddedsize = (size & DT_IMGSZ_PERTHREAD) ? va_arg(args,size_t*) : NULL;
62 if (size == 0 || IS_NULL_PTR(bufptr))
63 break;
64 const size_t channels = size & DT_IMGSZ_CH_MASK;
65 size_t nfloats;
67 {
69 nfloats = channels * roi_out->width * roi_out->height;
70 break;
72 nfloats = channels * roi_out->height;
73 break;
75 nfloats = channels * roi_out->width;
76 break;
78 nfloats = channels * MAX(roi_out->width, roi_out->height);
79 break;
81 nfloats = channels * roi_in->width * roi_in->height;
82 break;
84 nfloats = channels * roi_in->height;
85 break;
87 nfloats = channels * roi_in->width;
88 break;
90 nfloats = channels * MAX(roi_in->width, roi_in->height);
91 break;
92 default:
93 nfloats = 0;
94 break;
95 }
97 {
98 *bufptr = dt_pixelpipe_cache_alloc_perthread_float(nfloats,paddedsize);
99 if ((size & DT_IMGSZ_CLEARBUF) && *bufptr)
100 memset(*bufptr, 0, *paddedsize * dt_get_num_openmp_threads() * sizeof(float));
101 }
102 else
103 {
104 *bufptr = dt_pixelpipe_cache_alloc_align_float_cache(nfloats, 0);
105 if ((size & DT_IMGSZ_CLEARBUF) && *bufptr)
106 memset(*bufptr, 0, nfloats * sizeof(float));
107 }
108 if (!*bufptr)
109 {
110 err = 1;
111 break;
112 }
113 }
114 va_end(args);
115
116 // finally, check whether successful and clean up if something went wrong
117 if (err)
118 {
119 va_start(args,roi_out);
120 while (TRUE)
121 {
122 const int size = va_arg(args,int);
123 float **bufptr = va_arg(args,float**);
125 (void)va_arg(args,size_t*); // skip the extra pointer for per-thread allocations
126 if (size == 0 || IS_NULL_PTR(bufptr) || !*bufptr)
127 break; // end of arg list or this attempted allocation failed
129 *bufptr = NULL;
130 }
131 va_end(args);
132 // set the module's trouble flag
133 }
134 return err;
135}
136
137
138// Copy an image buffer, specifying the number of floats it contains. Use of this function is to be preferred
139// over a bare memcpy both because it helps document the purpose of the code and because it gives us a single
140// point where we can optimize performance on different architectures.
142void dt_iop_image_copy(float *const __restrict__ out, const float *const __restrict__ in, const size_t nfloats)
143{
144#ifdef _OPENMP
145 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
146 {
147 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
148 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
149 // memory won't be able to take advantage of more than four cores).
150#pragma omp parallel for simd aligned(in, out : 16) default(firstprivate)
151 for(size_t k = 0; k < nfloats; k++)
152 out[k] = in[k];
153 return;
154 }
155#endif // _OPENMP
156 // no OpenMP, or image too small to bother parallelizing
157 memcpy(out, in, nfloats * sizeof(float));
158}
159
160// Copy an image buffer, specifying the regions of interest. The output RoI may be larger than the input RoI,
161// in which case the result is optionally padded with zeros. If the output RoI is smaller than the input RoI,
162// only a portion of the input buffer will be copied.
163void dt_iop_copy_image_roi(float *const __restrict__ out, const float *const __restrict__ in, const size_t ch,
164 const dt_iop_roi_t *const __restrict__ roi_in,
165 const dt_iop_roi_t *const __restrict__ roi_out, const int zero_pad)
166{
167 if (roi_in->width == roi_out->width && roi_in->height == roi_out->height)
168 {
169 // fast path, just copy the entire contents of the buffer
170 dt_iop_image_copy_by_size(out, in, roi_out->width, roi_out->height, ch);
171 }
172 else if (roi_in->width <= roi_out->width && roi_in->height <= roi_out->height)
173 {
174 // output needs padding
175 fprintf(stderr,"copy_image_roi with larger output not yet implemented\n");
176 //TODO
177 }
178 else if (roi_in->width >= roi_out->width && roi_in->height >= roi_out->height)
179 {
180 // copy only a portion of the input
181 fprintf(stderr,"copy_image_roi with smaller output not yet implemented\n");
182 //TODO
183 }
184 else
185 {
186 // inconsistent RoIs!!
187 fprintf(stderr,"copy_image_roi called with inconsistent RoI!\n");
188 //TODO
189 }
190}
191
193void dt_iop_image_scaled_copy(float *const restrict buf, const float *const restrict src, const float scale,
194 const size_t width, const size_t height, const size_t ch)
195{
196 const size_t nfloats = width * height * ch;
197#ifdef _OPENMP
198 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
199 {
200 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
201 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
202 // memory won't be able to take advantage of more than four cores).
203#pragma omp parallel for simd aligned(buf, src : 16) default(firstprivate)
204 for(size_t k = 0; k < nfloats; k++)
205 buf[k] = scale * src[k];
206 return;
207 }
208#endif // _OPENMP
209 // no OpenMP, or image too small to bother parallelizing
210#ifdef _OPENMP
211#pragma omp simd aligned(buf, src : 16)
212#endif
213 for (size_t k = 0; k < nfloats; k++)
214 buf[k] = scale * src[k];
215}
216
218void dt_iop_image_fill(float *const buf, const float fill_value, const size_t width, const size_t height,
219 const size_t ch)
220{
221 const size_t nfloats = width * height * ch;
222#ifdef _OPENMP
223 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
224 {
225 const size_t nthreads = MIN(16,dt_get_num_openmp_threads());
226 // determine the number of 4-float vectors to be processed by each thread
227 const size_t chunksize = (((nfloats + nthreads - 1) / nthreads) + 3) / 4;
228#pragma omp parallel for default(firstprivate) num_threads(nthreads)
229 for(size_t chunk = 0; chunk < nthreads; chunk++)
230 {
231#pragma omp simd aligned(buf:16)
232 for(size_t k = 4 * chunk * chunksize; k < MIN(4*(chunk+1)*chunksize, nfloats); k++)
233 buf[k] = fill_value;
234 }
235 return;
236 }
237#endif // _OPENMP
238 // no OpenMP, or image too small to bother parallelizing
239 if (fill_value == 0.0f)
240 {
241 // take advantage of compiler intrinsic which is hopefully highly optimized
242 memset(buf, 0, sizeof(float) * nfloats);
243 }
244 else
245 {
246#ifdef _OPENMP
247#pragma omp simd aligned(buf:16)
248#endif
249 for (size_t k = 0; k < nfloats; k++)
250 buf[k] = fill_value;
251 }
252}
253
255void dt_iop_image_add_const(float *const buf, const float add_value, const size_t width, const size_t height,
256 const size_t ch)
257{
258 const size_t nfloats = width * height * ch;
259#ifdef _OPENMP
260 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
261 {
262 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
263 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
264 // memory won't be able to take advantage of more than four cores).
265#pragma omp parallel for simd aligned(buf:16) default(firstprivate)
266 for(size_t k = 0; k < nfloats; k++)
267 buf[k] += add_value;
268 return;
269 }
270#endif // _OPENMP
271 // no OpenMP, or image too small to bother parallelizing
272#ifdef _OPENMP
273#pragma omp simd aligned(buf:16)
274#endif
275 for (size_t k = 0; k < nfloats; k++)
276 buf[k] += add_value;
277}
278
280void dt_iop_image_add_image(float *const buf, const float* const other_image,
281 const size_t width, const size_t height, const size_t ch)
282{
283 const size_t nfloats = width * height * ch;
284#ifdef _OPENMP
285 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
286 {
287 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
288 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
289 // memory won't be able to take advantage of more than four cores).
290#pragma omp parallel for simd aligned(buf, other_image : 16) default(firstprivate)
291 for(size_t k = 0; k < nfloats; k++)
292 buf[k] += other_image[k];
293 return;
294 }
295#endif // _OPENMP
296 // no OpenMP, or image too small to bother parallelizing
297#ifdef _OPENMP
298#pragma omp simd aligned(buf, other_image : 16)
299#endif
300 for (size_t k = 0; k < nfloats; k++)
301 buf[k] += other_image[k];
302}
303
305void dt_iop_image_sub_image(float *const buf, const float* const other_image,
306 const size_t width, const size_t height, const size_t ch)
307{
308 const size_t nfloats = width * height * ch;
309#ifdef _OPENMP
310 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
311 {
312 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
313 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
314 // memory won't be able to take advantage of more than four cores).
315#pragma omp parallel for simd aligned(buf, other_image : 16) default(firstprivate)
316 for(size_t k = 0; k < nfloats; k++)
317 buf[k] -= other_image[k];
318 return;
319 }
320#endif // _OPENMP
321 // no OpenMP, or image too small to bother parallelizing
322#ifdef _OPENMP
323#pragma omp simd aligned(buf, other_image : 16)
324#endif
325 for (size_t k = 0; k < nfloats; k++)
326 buf[k] -= other_image[k];
327}
328
330void dt_iop_image_invert(float *const buf, const float max_value, const size_t width, const size_t height,
331 const size_t ch)
332{
333 const size_t nfloats = width * height * ch;
334#ifdef _OPENMP
335 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
336 {
337 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
338 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
339 // memory won't be able to take advantage of more than four cores).
340#pragma omp parallel for simd aligned(buf:16) default(firstprivate)
341 for(size_t k = 0; k < nfloats; k++)
342 buf[k] = max_value - buf[k];
343 return;
344 }
345#endif // _OPENMP
346 // no OpenMP, or image too small to bother parallelizing
347#ifdef _OPENMP
348#pragma omp simd aligned(buf:16)
349#endif
350 for (size_t k = 0; k < nfloats; k++)
351 buf[k] = max_value - buf[k];
352}
353
355void dt_iop_image_mul_const(float *const buf, const float mul_value, const size_t width, const size_t height,
356 const size_t ch)
357{
358 const size_t nfloats = width * height * ch;
359#ifdef _OPENMP
360 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
361 {
362 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
363 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
364 // memory won't be able to take advantage of more than four cores).
365#pragma omp parallel for simd aligned(buf:16) default(firstprivate)
366 for(size_t k = 0; k < nfloats; k++)
367 buf[k] *= mul_value;
368 return;
369 }
370#endif // _OPENMP
371 // no OpenMP, or image too small to bother parallelizing
372#ifdef _OPENMP
373#pragma omp simd aligned(buf:16)
374#endif
375 for (size_t k = 0; k < nfloats; k++)
376 buf[k] *= mul_value;
377}
378
380void dt_iop_image_div_const(float *const buf, const float div_value, const size_t width, const size_t height,
381 const size_t ch)
382{
383 const size_t nfloats = width * height * ch;
384#ifdef _OPENMP
385 if (nfloats > parallel_imgop_minimum) // is the copy big enough to outweigh threading overhead?
386 {
387 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
388 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
389 // memory won't be able to take advantage of more than four cores).
390#pragma omp parallel for simd aligned(buf:16) default(firstprivate)
391 for(size_t k = 0; k < nfloats; k++)
392 buf[k] /= div_value;
393 return;
394 }
395#endif // _OPENMP
396 // no OpenMP, or image too small to bother parallelizing
397#ifdef _OPENMP
398#pragma omp simd aligned(buf:16)
399#endif
400 for (size_t k = 0; k < nfloats; k++)
401 buf[k] /= div_value;
402}
403
404// elementwise: buf = lammda*buf + (1-lambda)*other
406void dt_iop_image_linear_blend(float *const restrict buf, const float lambda, const float *const restrict other,
407 const size_t width, const size_t height, const size_t ch)
408{
409 const size_t nfloats = width * height * ch;
410 const float lambda_1 = 1.0f - lambda;
411#ifdef _OPENMP
412 if (nfloats > parallel_imgop_minimum/2) // is the task big enough to outweigh threading overhead?
413 {
414 // we can gain a little by using a small number of threads in parallel, but not much since the memory bus
415 // quickly saturates (basically, each core can saturate a memory channel, so a system with quad-channel
416 // memory won't be able to take advantage of more than four cores).
417#pragma omp parallel for simd aligned(buf:16) default(firstprivate)
418 for(size_t k = 0; k < nfloats; k++)
419 buf[k] = lambda*buf[k] + lambda_1*other[k];
420 return;
421 }
422#endif // _OPENMP
423 // no OpenMP, or image too small to bother parallelizing
424#ifdef _OPENMP
425#pragma omp simd aligned(buf:16)
426#endif
427 for (size_t k = 0; k < nfloats; k++)
428 buf[k] = lambda*buf[k] + lambda_1*other[k];
429}
430
431
432// clang-format off
433// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
434// vim: shiftwidth=2 expandtab tabstop=2 cindent
435// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
436// clang-format on
#define TRUE
Definition ashift_lsd.c:162
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const dt_colormatrix_t dt_aligned_pixel_t out
int dt_get_num_openmp_threads(void)
Definition darktable.c:509
__DT_CLONE_TARGETS__ void dt_iop_image_add_image(float *const buf, const float *const other_image, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:280
__DT_CLONE_TARGETS__ void dt_iop_image_mul_const(float *const buf, const float mul_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:355
__DT_CLONE_TARGETS__ void dt_iop_image_copy(float *const __restrict__ out, const float *const __restrict__ in, const size_t nfloats)
Definition imagebuf.c:142
__DT_CLONE_TARGETS__ void dt_iop_image_sub_image(float *const buf, const float *const other_image, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:305
int dt_iop_alloc_image_buffers(struct dt_iop_module_t *const module, const struct dt_iop_roi_t *const roi_in, const struct dt_iop_roi_t *const roi_out,...)
Definition imagebuf.c:35
__DT_CLONE_TARGETS__ void dt_iop_image_add_const(float *const buf, const float add_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:255
void dt_iop_copy_image_roi(float *const __restrict__ out, const float *const __restrict__ in, const size_t ch, const dt_iop_roi_t *const __restrict__ roi_in, const dt_iop_roi_t *const __restrict__ roi_out, const int zero_pad)
Definition imagebuf.c:163
__DT_CLONE_TARGETS__ void dt_iop_image_invert(float *const buf, const float max_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:330
__DT_CLONE_TARGETS__ void dt_iop_image_fill(float *const buf, const float fill_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:218
__DT_CLONE_TARGETS__ void dt_iop_image_linear_blend(float *const restrict buf, const float lambda, const float *const restrict other, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:406
__DT_CLONE_TARGETS__ void dt_iop_image_scaled_copy(float *const restrict buf, const float *const restrict src, const float scale, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:193
__DT_CLONE_TARGETS__ void dt_iop_image_div_const(float *const buf, const float div_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:380
#define DT_IMGSZ_ROI_MASK
Definition imagebuf.h:57
#define DT_IMGSZ_LONGEST
Definition imagebuf.h:68
#define DT_IMGSZ_INPUT
Definition imagebuf.h:59
#define DT_IMGSZ_CLEARBUF
Definition imagebuf.h:62
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
#define DT_IMGSZ_OUTPUT
Definition imagebuf.h:58
#define DT_IMGSZ_WIDTH
Definition imagebuf.h:67
#define DT_IMGSZ_PERTHREAD
Definition imagebuf.h:61
#define DT_IMGSZ_HEIGHT
Definition imagebuf.h:66
#define DT_IMGSZ_CH_MASK
Definition imagebuf.h:55
#define DT_IMGSZ_DIM_MASK
Definition imagebuf.h:64
#define DT_IMGSZ_FULL
Definition imagebuf.h:65
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:65
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
Region of interest passed through the pixelpipe.
Definition format.h:49
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