Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
blendif_lab.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020 Harold le Clément de Saint-Marcq.
4 Copyright (C) 2020-2021 Hubert Kowalski.
5 Copyright (C) 2020-2021 Ralf Brown.
6 Copyright (C) 2021 Chris Elston.
7 Copyright (C) 2021 Pascal Obry.
8 Copyright (C) 2022 Martin Bařinka.
9 Copyright (C) 2026 Aurélien PIERRE.
10
11 darktable is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
15
16 darktable is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with darktable. If not, see <http://www.gnu.org/licenses/>.
23*/
24
26#include "develop/iop_profile.h"
28#include "common/imagebuf.h"
29#include "develop/blend.h"
30#include "develop/imageop.h"
31#include "math/openmp_maths.h"
32#include <math.h>
33
34#define DT_BLENDIF_LAB_CH 4
35#define DT_BLENDIF_LAB_BCH 3
36
37
38typedef void(_blend_row_func)(const float *const restrict a, const float *const restrict b,
39 float *const restrict out, const float *const restrict mask, const size_t stride,
41
42
44static inline float _CLAMP(const float x, const float min, const float max)
45{
46 return fminf(fmaxf(x, min), max);
47}
48
49__OMP_DECLARE_SIMD__(aligned(XYZ, min, max: 16))
51{
52 for_each_channel(i) XYZ[i] = fminf(fmaxf(XYZ[i], min[i]), max[i]);
53}
54
55__OMP_DECLARE_SIMD__(uniform(parameters, invert_mask))
56static inline float _blendif_compute_factor(const float value, const unsigned int invert_mask,
57 const float *const restrict parameters)
58{
59 float factor = 0.0f;
60 if(value <= parameters[0])
61 {
62 // we are below the keyframe
63 factor = 0.0f;
64 }
65 else if(value < parameters[1])
66 {
67 // we are on the bottom slope of the keyframe
68 factor = (value - parameters[0]) * parameters[4];
69 }
70 else if(value <= parameters[2])
71 {
72 // we are on the ramp - constant part - of the keyframe
73 factor = 1.0f;
74 }
75 else if(value < parameters[3])
76 {
77 // we are on the top slope of the keyframe
78 factor = 1.0f - (value - parameters[2]) * parameters[5];
79 }
80 else
81 {
82 // we are above the keyframe
83 factor = 0.0f;
84 }
85 return invert_mask ? 1.0f - factor : factor; // inverted channel?
86}
87
88__OMP_DECLARE_SIMD__(aligned(pixels: 16) uniform(parameters, invert_mask, stride))
89static inline void _blendif_lab_l(const float *const restrict pixels, float *const restrict mask,
90 const size_t stride, const float *const restrict parameters,
91 const unsigned int invert_mask)
92{
93 for(size_t x = 0, j = 0; x < stride; x++, j += DT_BLENDIF_LAB_CH)
94 {
95 mask[x] *= _blendif_compute_factor(pixels[j + 0] / 100.0f, invert_mask, parameters);
96 }
97}
98
99__OMP_DECLARE_SIMD__(aligned(pixels: 16) uniform(parameters, invert_mask, stride))
100static inline void _blendif_lab_a(const float *const restrict pixels, float *const restrict mask,
101 const size_t stride, const float *const restrict parameters,
102 const unsigned int invert_mask)
103{
104 for(size_t x = 0, j = 0; x < stride; x++, j += DT_BLENDIF_LAB_CH)
105 {
106 mask[x] *= _blendif_compute_factor(pixels[j + 1] / 256.0f, invert_mask, parameters);
107 }
108}
109
110__OMP_DECLARE_SIMD__(aligned(pixels: 16) uniform(parameters, invert_mask, stride))
111static inline void _blendif_lab_b(const float *const restrict pixels, float *const restrict mask,
112 const size_t stride, const float *const restrict parameters,
113 const unsigned int invert_mask)
114{
115 for(size_t x = 0, j = 0; x < stride; x++, j += DT_BLENDIF_LAB_CH)
116 {
117 mask[x] *= _blendif_compute_factor(pixels[j + 2] / 256.0f, invert_mask, parameters);
118 }
119}
120
121__OMP_DECLARE_SIMD__(aligned(pixels, invert_mask: 16) uniform(parameters, invert_mask, stride))
122static inline void _blendif_lch(const float *const restrict pixels, float *const restrict mask,
123 const size_t stride, const float *const restrict parameters,
124 const unsigned int *const restrict invert_mask)
125{
126 const float c_scale = 1.0f / (128.0f * sqrtf(2.0f));
127 for(size_t x = 0, j = 0; x < stride; x++, j += DT_BLENDIF_LAB_CH)
128 {
130 dt_Lab_2_LCH(pixels + j, LCH);
131 float factor = 1.0f;
132 factor *= _blendif_compute_factor(LCH[1] * c_scale, invert_mask[0], parameters);
133 factor *= _blendif_compute_factor(LCH[2], invert_mask[1], parameters + DEVELOP_BLENDIF_PARAMETER_ITEMS);
134 mask[x] *= factor;
135 }
136}
137
138__OMP_DECLARE_SIMD__(aligned(pixels: 16) uniform(stride, blendif, parameters))
139static void _blendif_combine_channels(const float *const restrict pixels, float *const restrict mask,
140 const size_t stride, const unsigned int blendif,
141 const float *const restrict parameters)
142{
143 if(blendif & (1 << DEVELOP_BLENDIF_L_in))
144 {
145 const unsigned int invert_mask = (blendif >> 16) & (1 << DEVELOP_BLENDIF_L_in);
146 _blendif_lab_l(pixels, mask, stride, parameters + DEVELOP_BLENDIF_PARAMETER_ITEMS * DEVELOP_BLENDIF_L_in,
147 invert_mask);
148 }
149
150 if(blendif & (1 << DEVELOP_BLENDIF_A_in))
151 {
152 const unsigned int invert_mask = (blendif >> 16) & (1 << DEVELOP_BLENDIF_A_in);
153 _blendif_lab_a(pixels, mask, stride, parameters + DEVELOP_BLENDIF_PARAMETER_ITEMS * DEVELOP_BLENDIF_A_in,
154 invert_mask);
155 }
156
157 if(blendif & (1 << DEVELOP_BLENDIF_B_in))
158 {
159 const unsigned int invert_mask = (blendif >> 16) & (1 << DEVELOP_BLENDIF_B_in);
160 _blendif_lab_b(pixels, mask, stride, parameters + DEVELOP_BLENDIF_PARAMETER_ITEMS * DEVELOP_BLENDIF_B_in,
161 invert_mask);
162 }
163
164 if(blendif & ((1 << DEVELOP_BLENDIF_C_in) | (1 << DEVELOP_BLENDIF_h_in)))
165 {
166 const unsigned int invert_mask[2] DT_ALIGNED_PIXEL = {
167 (blendif >> 16) & (1 << DEVELOP_BLENDIF_C_in),
168 (blendif >> 16) & (1 << DEVELOP_BLENDIF_h_in),
169 };
170 _blendif_lch(pixels, mask, stride, parameters + DEVELOP_BLENDIF_PARAMETER_ITEMS * DEVELOP_BLENDIF_C_in,
171 invert_mask);
172 }
173}
174
175void dt_develop_blendif_lab_make_mask(const struct dt_dev_pixelpipe_iop_t *piece, const float *const restrict a,
176 const float *const restrict b, float *const restrict mask)
177{
178 const dt_iop_roi_t *const roi_in = &piece->roi_in;
179 const dt_iop_roi_t *const roi_out = &piece->roi_out;
180 const dt_develop_blend_params_t *const d = (const dt_develop_blend_params_t *const)piece->blendop_data;
181
182 if(piece->dsc_in.channels != DT_BLENDIF_LAB_CH) return;
183
184 const int xoffs = roi_out->x - roi_in->x;
185 const int yoffs = roi_out->y - roi_in->y;
186 const int iwidth = roi_in->width;
187 const int owidth = roi_out->width;
188 const int oheight = roi_out->height;
189
190 const unsigned int any_channel_active = d->blendif & DEVELOP_BLENDIF_Lab_MASK;
191 const unsigned int mask_inclusive = d->mask_combine & DEVELOP_COMBINE_INCL;
192 const unsigned int mask_inversed = d->mask_combine & DEVELOP_COMBINE_INV;
193
194 // invert the individual channels if the combine mode is inclusive
195 const unsigned int blendif = d->blendif ^ (mask_inclusive ? DEVELOP_BLENDIF_Lab_MASK << 16 : 0);
196
197 // a channel cancels the mask if the whole span is selected and the channel is inverted
198 const unsigned int canceling_channel = (blendif >> 16) & ~blendif & DEVELOP_BLENDIF_Lab_MASK;
199
200 const size_t buffsize = (size_t)owidth * oheight;
201
202 // get the clipped opacity value 0 - 1
203 const float global_opacity = clamp_simd(d->opacity / 100.0f);
204
205 if(!(d->mask_mode & DEVELOP_MASK_PARAMETRIC) || (!canceling_channel && !any_channel_active))
206 {
207 // mask is not conditional, invert the mask if required
208 if(mask_inversed)
209 {
211 for(size_t x = 0; x < buffsize; x++) mask[x] = global_opacity * (1.0f - mask[x]);
212 }
213 else
214 {
215 dt_iop_image_mul_const(mask,global_opacity,owidth,oheight,1); //mask[k] *= global_opacity;
216 }
217 }
218 else if(canceling_channel || !any_channel_active)
219 {
220 // one of the conditional channel selects nothing
221 // this means that the conditional opacity of all pixels is the same
222 // and depends on whether the mask combination is inclusive and whether the mask is inverted
223 if((mask_inversed == 0) ^ (mask_inclusive == 0))
224 {
225 dt_iop_image_fill(mask,global_opacity,owidth,oheight,1); //mask[k] = global_opacity;
226 }
227 else
228 {
229 dt_iop_image_fill(mask,0.0f,owidth,oheight,1); //mask[k] = 0.0f;
230 }
231 }
232 else
233 {
234 // we need to process all conditional channels
235
236 // parameters, for every channel the 4 limits + pre-computed increasing slope and decreasing slope
239
240 // allocate space for a temporary mask buffer to split the computation of every channel
241 float *const restrict temp_mask = dt_pixelpipe_cache_alloc_align_float_cache(buffsize, 0);
242 if(IS_NULL_PTR(temp_mask))
243 {
244 return;
245 }
247 {
248 // initialize the parametric mask
249 __OMP_FOR_SIMD__(aligned(temp_mask:64))
250 for(size_t x = 0; x < buffsize; x++) temp_mask[x] = 1.0f;
251
252 // combine channels
254 for(size_t y = 0; y < oheight; y++)
255 {
256 const size_t start = ((y + yoffs) * iwidth + xoffs) * DT_BLENDIF_LAB_CH;
257 _blendif_combine_channels(a + start, temp_mask + (y * owidth), owidth, blendif, parameters);
258 }
260 for(size_t y = 0; y < oheight; y++)
261 {
262 const size_t start = (y * owidth) * DT_BLENDIF_LAB_CH;
263 _blendif_combine_channels(b + start, temp_mask + (y * owidth), owidth, blendif >> DEVELOP_BLENDIF_L_out,
265 }
266
267 // apply global opacity
268 if(mask_inclusive)
269 {
270 if(mask_inversed)
271 {
272 __OMP_FOR_SIMD__(aligned(mask, temp_mask:64))
273 for(size_t x = 0; x < buffsize; x++) mask[x] = global_opacity * (1.0f - mask[x]) * temp_mask[x];
274 }
275 else
276 {
277 __OMP_FOR_SIMD__(aligned(mask, temp_mask:64))
278 for(size_t x = 0; x < buffsize; x++) mask[x] = global_opacity * (1.0f - (1.0f - mask[x]) * temp_mask[x]);
279 }
280 }
281 else
282 {
283 if(mask_inversed)
284 {
285 __OMP_FOR_SIMD__(aligned(mask, temp_mask:64))
286 for(size_t x = 0; x < buffsize; x++) mask[x] = global_opacity * (1.0f - mask[x] * temp_mask[x]);
287 }
288 else
289 {
290 __OMP_FOR_SIMD__(aligned(mask, temp_mask:64))
291 for(size_t x = 0; x < buffsize; x++) mask[x] = global_opacity * mask[x] * temp_mask[x];
292 }
293 }
294 }
295
297 }
298}
299
300
301__OMP_DECLARE_SIMD__(aligned(i, o: 16))
302static inline void _blend_Lab_scale(const float *i, float *o)
303{
304 const dt_aligned_pixel_t scale = { 1/100.0f, 1/128.0f, 1/128.0f, 1.0f };
306 o[c] = i[c] * scale[c];
307}
308
309__OMP_DECLARE_SIMD__(aligned(i, o: 16))
310static inline void _blend_Lab_rescale(const float *i, float *o)
311{
312 const dt_aligned_pixel_t scale = { 100.0f, 128.0f, 128.0f, 1.0f };
314 o[c] = i[c] * scale[c];
315}
316
317
318/* normal blend with clamping */
319__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
320static void _blend_normal_bounded(const float *const restrict a, const float *const restrict b,
321 float *const restrict out, const float *const restrict mask, const size_t stride,
323{
324 for(size_t i = 0; i < stride; i++)
325 {
326 size_t j = i * DT_BLENDIF_LAB_CH;
327 const float local_opacity = mask[i];
328 dt_aligned_pixel_t ta, tb;
329
330 _blend_Lab_scale(a + j, ta);
331 _blend_Lab_scale(b + j, tb);
332
334 tb[x] = _CLAMP(ta[x] * (1.0f - local_opacity) + tb[x] * local_opacity, min[x], max[x]);
335
336 _blend_Lab_rescale(tb, out + j);
337 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
338 }
339}
340
341/* normal blend without any clamping */
342__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
343static void _blend_normal_unbounded(const float *const restrict a, const float *const restrict b,
344 float *const restrict out,
345 const float *const restrict mask, const size_t stride,
347{
348 for(size_t i = 0; i < stride; i++)
349 {
350 size_t j = i * DT_BLENDIF_LAB_CH;
351 const float local_opacity = mask[i];
352 dt_aligned_pixel_t ta, tb;
353
354 _blend_Lab_scale(a + j, ta);
355 _blend_Lab_scale(b + j, tb);
356
358 tb[x] = ta[x] * (1.0f - local_opacity) + tb[x] * local_opacity;
359
360 _blend_Lab_rescale(tb, out + j);
361 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
362 }
363}
364
365/* lighten */
366__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
367static void _blend_lighten(const float *const restrict a, const float *const restrict b,
368 float *const restrict out, const float *const restrict mask, const size_t stride,
370{
371 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
372 {
373 const float local_opacity = mask[i];
374 dt_aligned_pixel_t ta, tb;
375
376 _blend_Lab_scale(a + j, ta);
377 _blend_Lab_scale(b + j, tb);
378
379 tb[0] = _CLAMP(ta[0] * (1.0f - local_opacity) + (ta[0] > tb[0] ? ta[0] : tb[0]) * local_opacity,
380 min[0], max[0]);
381 tb[1] = _CLAMP(ta[1] * (1.0f - fabsf(tb[0] - ta[0])) + 0.5f * (ta[1] + tb[1]) * fabsf(tb[0] - ta[0]),
382 min[1], max[1]);
383 tb[2] = _CLAMP(ta[2] * (1.0f - fabsf(tb[0] - ta[0])) + 0.5f * (ta[2] + tb[2]) * fabsf(tb[0] - ta[0]),
384 min[2], max[2]);
385
386 _blend_Lab_rescale(tb, out + j);
387 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
388 }
389}
390
391/* darken */
392__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
393static void _blend_darken(const float *const restrict a, const float *const restrict b,
394 float *const restrict out, const float *const restrict mask, const size_t stride,
396{
397 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
398 {
399 const float local_opacity = mask[i];
400 dt_aligned_pixel_t ta, tb;
401
402 _blend_Lab_scale(a + j, ta);
403 _blend_Lab_scale(b + j, tb);
404
405 tb[0] = _CLAMP(ta[0] * (1.0f - local_opacity) + (ta[0] < tb[0] ? ta[0] : tb[0]) * local_opacity,
406 min[0], max[0]);
407 tb[1] = _CLAMP(ta[1] * (1.0f - fabsf(tb[0] - ta[0])) + 0.5f * (ta[1] + tb[1]) * fabsf(tb[0] - ta[0]),
408 min[1], max[1]);
409 tb[2] = _CLAMP(ta[2] * (1.0f - fabsf(tb[0] - ta[0])) + 0.5f * (ta[2] + tb[2]) * fabsf(tb[0] - ta[0]),
410 min[2], max[2]);
411
412 _blend_Lab_rescale(tb, out + j);
413 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
414 }
415}
416
417/* multiply */
418__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
419static void _blend_multiply(const float *const restrict a, const float *const restrict b,
420 float *const restrict out, const float *const restrict mask, const size_t stride,
422{
423 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
424 {
425 const float local_opacity = mask[i];
426 dt_aligned_pixel_t ta, tb;
427
428 _blend_Lab_scale(a + j, ta);
429 _blend_Lab_scale(b + j, tb);
430
431 tb[0] = _CLAMP(ta[0] * (1.0f - local_opacity) + (ta[0] * tb[0]) * local_opacity, min[0], max[0]);
432
433 const float f = fmaxf(ta[0], 0.01f);
434 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity) + (ta[1] + tb[1]) * tb[0] / f * local_opacity, min[1], max[1]);
435 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity) + (ta[2] + tb[2]) * tb[0] / f * local_opacity, min[2], max[2]);
436
437 _blend_Lab_rescale(tb, out + j);
438 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
439 }
440}
441
442/* average */
443__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
444static void _blend_average(const float *const restrict a, const float *const restrict b,
445 float *const restrict out, const float *const restrict mask, const size_t stride,
447{
448 for(size_t i = 0; i < stride; i++)
449 {
450 size_t j = i * DT_BLENDIF_LAB_CH;
451 const float local_opacity = mask[i];
452 dt_aligned_pixel_t ta, tb;
453
454 _blend_Lab_scale(a + j, ta);
455 _blend_Lab_scale(b + j, tb);
456
458 tb[x] = _CLAMP(ta[x] * (1.0f - local_opacity) + (ta[x] + tb[x]) / 2.0f * local_opacity, min[x], max[x]);
459
460 _blend_Lab_rescale(tb, out + j);
461 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
462 }
463}
464
465/* add */
466__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
467static void _blend_add(const float *const restrict a, const float *const restrict b,
468 float *const restrict out, const float *const restrict mask, const size_t stride,
470{
471 for(size_t i = 0; i < stride; i++)
472 {
473 size_t j = i * DT_BLENDIF_LAB_CH;
474 const float local_opacity = mask[i];
475 dt_aligned_pixel_t ta, tb;
476
477 _blend_Lab_scale(a + j, ta);
478 _blend_Lab_scale(b + j, tb);
479
481 tb[x] = _CLAMP(ta[x] * (1.0f - local_opacity) + (ta[x] + tb[x]) * local_opacity, min[x], max[x]);
482
483 _blend_Lab_rescale(tb, out + j);
484 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
485 }
486}
487
488/* subtract */
489__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
490static void _blend_subtract(const float *const restrict a, const float *const restrict b,
491 float *const restrict out, const float *const restrict mask, const size_t stride,
493{
494 for(size_t i = 0; i < stride; i++)
495 {
496 size_t j = i * DT_BLENDIF_LAB_CH;
497 float local_opacity = mask[i];
498 dt_aligned_pixel_t ta, tb;
499
500 _blend_Lab_scale(a + j, ta);
501 _blend_Lab_scale(b + j, tb);
502
504 tb[x] = _CLAMP(ta[x] * (1.0f - local_opacity) + ((tb[x] + ta[x]) - (fabsf(min[x] + max[x]))) * local_opacity,
505 min[x], max[x]);
506
507 _blend_Lab_rescale(tb, out + j);
508 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
509 }
510}
511
512/* difference (deprecated) */
513__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
514static void _blend_difference(const float *const restrict a, const float *const restrict b,
515 float *const restrict out, const float *const restrict mask, const size_t stride,
517{
518 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
519 {
520 const float local_opacity = mask[i];
521 dt_aligned_pixel_t ta, tb;
522
523 _blend_Lab_scale(a + j, ta);
524 _blend_Lab_scale(b + j, tb);
525
526 const float lmin = 0.0f;
527 for(size_t x = 0; x < 3; x++)
528 {
529 float lmax = max[x] + fabsf(min[x]);
530 float la = _CLAMP(ta[x] + fabsf(min[x]), lmin, lmax);
531 float lb = _CLAMP(tb[x] + fabsf(min[x]), lmin, lmax);
532 tb[x] = _CLAMP(la * (1.0f - local_opacity) + fabsf(la - lb) * local_opacity, lmin, lmax) - fabsf(min[x]);
533 }
534
535 _blend_Lab_rescale(tb, out + j);
536 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
537 }
538}
539
540/* difference 2 (new) */
541__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
542static void _blend_difference2(const float *const restrict a, const float *const restrict b,
543 float *const restrict out, const float *const restrict mask, const size_t stride,
545{
546 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
547 {
548 const float local_opacity = mask[i];
549 dt_aligned_pixel_t ta, tb;
550
551 _blend_Lab_scale(a + j, ta);
552 _blend_Lab_scale(b + j, tb);
553
555 tb[x] = fabsf(ta[x] - tb[x]) / fabsf(max[x] - min[x]);
556 tb[0] = fmaxf(tb[0], fmaxf(tb[1], tb[2]));
557
558 tb[0] = _CLAMP(ta[0] * (1.0f - local_opacity) + tb[0] * local_opacity, min[0], max[0]);
559 tb[1] = 0.0f;
560 tb[2] = 0.0f;
561
562 _blend_Lab_rescale(tb, out + j);
563 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
564 }
565}
566
567/* screen */
568__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
569static void _blend_screen(const float *const restrict a, const float *const restrict b,
570 float *const restrict out, const float *const restrict mask, const size_t stride,
572{
573 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
574 {
575 const float local_opacity = mask[i];
576 dt_aligned_pixel_t ta, tb;
577
578 _blend_Lab_scale(a + j, ta);
579 _blend_Lab_scale(b + j, tb);
580
581 const float lmin = 0.0f;
582 const float lmax = max[0] + fabsf(min[0]);
583 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
584 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
585
586 tb[0] = _CLAMP(la * (1.0f - local_opacity) + ((lmax - (lmax - la) * (lmax - lb))) * local_opacity, lmin, lmax)
587 - fabsf(min[0]);
588
589 const float f = fmaxf(ta[0], 0.01f);
590 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity) + 0.5f * (ta[1] + tb[1]) * tb[0] / f * local_opacity,
591 min[1], max[1]);
592 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity) + 0.5f * (ta[2] + tb[2]) * tb[0] / f * local_opacity,
593 min[2], max[2]);
594
595 _blend_Lab_rescale(tb, out + j);
596 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
597 }
598}
599
600/* overlay */
601__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
602static void _blend_overlay(const float *const restrict a, const float *const restrict b,
603 float *const restrict out, const float *const restrict mask, const size_t stride,
605{
606 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
607 {
608 const float local_opacity = mask[i];
609 const float local_opacity2 = local_opacity * local_opacity;
610 dt_aligned_pixel_t ta, tb;
611
612 _blend_Lab_scale(&a[j], ta);
613 _blend_Lab_scale(&b[j], tb);
614
615 const float lmin = 0.0f;
616 const float lmax = max[0] + fabsf(min[0]);
617 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
618 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
619 const float halfmax = lmax / 2.0f;
620 const float doublemax = lmax * 2.0f;
621
622 tb[0] = _CLAMP(la * (1.0f - local_opacity2)
623 + (la > halfmax ? lmax - (lmax - doublemax * (la - halfmax)) * (lmax - lb)
624 : (doublemax * la) * lb)
625 * local_opacity2, lmin, lmax)
626 - fabsf(min[0]);
627
628 const float f = fmaxf(ta[0], 0.01f);
629 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity2) + (ta[1] + tb[1]) * tb[0] / f * local_opacity2, min[1], max[1]);
630 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity2) + (ta[2] + tb[2]) * tb[0] / f * local_opacity2, min[2], max[2]);
631
632 _blend_Lab_rescale(tb, out + j);
633 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
634 }
635}
636
637/* softlight */
638__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
639static void _blend_softlight(const float *const restrict a, const float *const restrict b,
640 float *const restrict out, const float *const restrict mask, const size_t stride,
642{
643 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
644 {
645 const float local_opacity = mask[i];
646 const float local_opacity2 = local_opacity * local_opacity;
647 dt_aligned_pixel_t ta, tb;
648
649 _blend_Lab_scale(a + j, ta);
650 _blend_Lab_scale(b + j, tb);
651
652 const float lmin = 0.0f;
653 const float lmax = max[0] + fabsf(min[0]);
654 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
655 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
656 const float halfmax = lmax / 2.0f;
657
658 tb[0] = _CLAMP(la * (1.0f - local_opacity2)
659 + (lb > halfmax ? lmax - (lmax - la) * (lmax - (lb - halfmax))
660 : la * (lb + halfmax))
661 * local_opacity2, lmin, lmax)
662 - fabsf(min[0]);
663
664 const float f = fmaxf(ta[0], 0.01f);
665 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity2) + (ta[1] + tb[1]) * tb[0] / f * local_opacity2, min[1], max[1]);
666 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity2) + (ta[2] + tb[2]) * tb[0] / f * local_opacity2, min[2], max[2]);
667
668 _blend_Lab_rescale(tb, out + j);
669 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
670 }
671}
672
673/* hardlight */
674__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
675static void _blend_hardlight(const float *const restrict a, const float *const restrict b,
676 float *const restrict out, const float *const restrict mask, const size_t stride,
678{
679 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
680 {
681 const float local_opacity = mask[i];
682 const float local_opacity2 = local_opacity * local_opacity;
683 dt_aligned_pixel_t ta, tb;
684
685 _blend_Lab_scale(a + j, ta);
686 _blend_Lab_scale(b + j, tb);
687
688 const float lmin = 0.0f;
689 const float lmax = max[0] + fabsf(min[0]);
690 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
691 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
692 const float halfmax = lmax / 2.0f;
693 const float doublemax = lmax * 2.0f;
694
695 tb[0] = _CLAMP(la * (1.0f - local_opacity2)
696 + (lb > halfmax ? lmax - (lmax - doublemax * (la - halfmax)) * (lmax - lb)
697 : doublemax * la * lb)
698 * local_opacity2, lmin, lmax)
699 - fabsf(min[0]);
700
701 const float f = fmaxf(ta[0], 0.01f);
702 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity2) + (ta[1] + tb[1]) * tb[0] / f * local_opacity2, min[1], max[1]);
703 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity2) + (ta[2] + tb[2]) * tb[0] / f * local_opacity2, min[2], max[2]);
704
705 _blend_Lab_rescale(tb, out + j);
706 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
707 }
708}
709
710/* vividlight */
711__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
712static void _blend_vividlight(const float *const restrict a, const float *const restrict b,
713 float *const restrict out, const float *const restrict mask, const size_t stride,
715{
716 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
717 {
718 const float local_opacity = mask[i];
719 const float local_opacity2 = local_opacity * local_opacity;
720 dt_aligned_pixel_t ta, tb;
721
722 _blend_Lab_scale(a + j, ta);
723 _blend_Lab_scale(b + j, tb);
724
725 const float lmin = 0.0f;
726 const float lmax = max[0] + fabsf(min[0]);
727 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
728 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
729 const float halfmax = lmax / 2.0f;
730 const float doublemax = lmax * 2.0f;
731
732 tb[0] = _CLAMP(la * (1.0f - local_opacity2)
733 + (lb > halfmax ? (lb >= lmax ? lmax : la / (doublemax * (lmax - lb)))
734 : (lb <= lmin ? lmin : lmax - (lmax - la) / (doublemax * lb)))
735 * local_opacity2, lmin, lmax)
736 - fabsf(min[0]);
737
738 const float f = fmaxf(ta[0], 0.01f);
739 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity2) + (ta[1] + tb[1]) * tb[0] / f * local_opacity2, min[1], max[1]);
740 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity2) + (ta[2] + tb[2]) * tb[0] / f * local_opacity2, min[2], max[2]);
741
742 _blend_Lab_rescale(tb, out + j);
743 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
744 }
745}
746
747/* linearlight */
748__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
749static void _blend_linearlight(const float *const restrict a, const float *const restrict b,
750 float *const restrict out, const float *const restrict mask, const size_t stride,
752{
753 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
754 {
755 const float local_opacity = mask[i];
756 const float local_opacity2 = local_opacity * local_opacity;
757 dt_aligned_pixel_t ta, tb;
758
759 _blend_Lab_scale(a + j, ta);
760 _blend_Lab_scale(b + j, tb);
761
762 const float lmin = 0.0f;
763 const float lmax = max[0] + fabsf(min[0]);
764 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
765 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
766 const float doublemax = lmax * 2.0f;
767
768 tb[0] = _CLAMP(la * (1.0f - local_opacity2) + (la + doublemax * lb - lmax) * local_opacity2, lmin, lmax)
769 - fabsf(min[0]);
770
771 const float f = fmaxf(ta[0], 0.01f);
772 tb[1] = _CLAMP(ta[1] * (1.0f - local_opacity2) + (ta[1] + tb[1]) * tb[0] / f * local_opacity2, min[1], max[1]);
773 tb[2] = _CLAMP(ta[2] * (1.0f - local_opacity2) + (ta[2] + tb[2]) * tb[0] / f * local_opacity2, min[2], max[2]);
774
775 _blend_Lab_rescale(tb, out + j);
776 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
777 }
778}
779
780/* pinlight */
781__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
782static void _blend_pinlight(const float *const restrict a, const float *const restrict b,
783 float *const restrict out, const float *const restrict mask, const size_t stride,
785{
786 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
787 {
788 const float local_opacity = mask[i];
789 const float local_opacity2 = local_opacity * local_opacity;
790 dt_aligned_pixel_t ta, tb;
791
792 _blend_Lab_scale(a + j, ta);
793 _blend_Lab_scale(b + j, tb);
794
795 const float lmin = 0.0f;
796 const float lmax = max[0] + fabsf(min[0]);
797 const float la = _CLAMP(ta[0] + fabsf(min[0]), lmin, lmax);
798 const float lb = _CLAMP(tb[0] + fabsf(min[0]), lmin, lmax);
799 const float halfmax = lmax / 2.0f;
800 const float doublemax = lmax * 2.0f;
801
802 tb[0] = _CLAMP(la * (1.0f - local_opacity2)
803 + (lb > halfmax ? fmaxf(la, doublemax * (lb - halfmax))
804 : fminf(la, doublemax * lb))
805 * local_opacity2, lmin, lmax)
806 - fabsf(min[0]);
807
808 tb[1] = _CLAMP(ta[1], min[1], max[1]);
809 tb[2] = _CLAMP(ta[2], min[2], max[2]);
810
811 _blend_Lab_rescale(tb, out + j);
812 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
813 }
814}
815
816/* lightness blend */
817__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
818static void _blend_lightness(const float *const restrict a, const float *const restrict b,
819 float *const restrict out, const float *const restrict mask, const size_t stride,
821{
822 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
823 {
824 const float local_opacity = mask[i];
825 dt_aligned_pixel_t ta, tb;
826
827 _blend_Lab_scale(a + j, ta);
828 _blend_Lab_scale(b + j, tb);
829
830 // no need to transfer to LCH as L is the same as in Lab, and C and H
831 // remain unchanged
832 tb[0] = _CLAMP(ta[0] * (1.0f - local_opacity) + tb[0] * local_opacity, min[0], max[0]);
833 tb[1] = _CLAMP(ta[1], min[1], max[1]);
834 tb[2] = _CLAMP(ta[2], min[2], max[2]);
835
836 _blend_Lab_rescale(tb, out + j);
837 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
838 }
839}
840
841/* chroma blend */
842__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
843static void _blend_chromaticity(const float *const restrict a, const float *const restrict b,
844 float *const restrict out, const float *const restrict mask, const size_t stride,
846{
847 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
848 {
849 const float local_opacity = mask[i];
850 dt_aligned_pixel_t ta, tb;
851 dt_aligned_pixel_t tta, ttb;
852
853 _blend_Lab_scale(a + j, ta);
854 _CLAMP_XYZ(ta, min, max);
855 dt_Lab_2_LCH(ta, tta);
856
857 _blend_Lab_scale(b + j, tb);
858 _CLAMP_XYZ(tb, min, max);
859 dt_Lab_2_LCH(tb, ttb);
860
861 ttb[0] = tta[0];
862 ttb[1] = (tta[1] * (1.0f - local_opacity)) + ttb[1] * local_opacity;
863 ttb[2] = tta[2];
864
865 dt_LCH_2_Lab(ttb, tb);
866 _CLAMP_XYZ(tb, min, max);
867 _blend_Lab_rescale(tb, out + j);
868 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
869 }
870}
871
872/* hue blend */
873__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
874static void _blend_hue(const float *const restrict a, const float *const restrict b,
875 float *const restrict out, const float *const restrict mask, const size_t stride,
877{
878 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
879 {
880 const float local_opacity = mask[i];
881 dt_aligned_pixel_t ta, tb;
882 dt_aligned_pixel_t tta, ttb;
883
884 _blend_Lab_scale(a + j, ta);
885 _CLAMP_XYZ(ta, min, max);
886 dt_Lab_2_LCH(ta, tta);
887
888 _blend_Lab_scale(b + j, tb);
889 _CLAMP_XYZ(tb, min, max);
890 dt_Lab_2_LCH(tb, ttb);
891
892 ttb[0] = tta[0];
893 ttb[1] = tta[1];
894 /* blend hue along shortest distance on color circle */
895 const float d = fabsf(tta[2] - ttb[2]);
896 const float s = d > 0.5f ? -local_opacity * (1.0f - d) / d : local_opacity;
897 ttb[2] = fmodf((tta[2] * (1.0f - s)) + ttb[2] * s + 1.0f, 1.0f);
898
899 dt_LCH_2_Lab(ttb, tb);
900 _CLAMP_XYZ(tb, min, max);
901 _blend_Lab_rescale(tb, out + j);
902 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
903 }
904}
905
906/* color blend; blend hue and chroma, but not lightness */
907__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
908static void _blend_color(const float *const restrict a, const float *const restrict b,
909 float *const restrict out, const float *const restrict mask, const size_t stride,
911{
912 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
913 {
914 const float local_opacity = mask[i];
915 dt_aligned_pixel_t ta, tb;
916 dt_aligned_pixel_t tta, ttb;
917
918 _blend_Lab_scale(a + j, ta);
919 _CLAMP_XYZ(ta, min, max);
920 dt_Lab_2_LCH(ta, tta);
921
922 _blend_Lab_scale(b + j, tb);
923 _CLAMP_XYZ(tb, min, max);
924 dt_Lab_2_LCH(tb, ttb);
925
926 ttb[0] = tta[0];
927 ttb[1] = (tta[1] * (1.0f - local_opacity)) + ttb[1] * local_opacity;
928
929 /* blend hue along shortest distance on color circle */
930 const float d = fabsf(tta[2] - ttb[2]);
931 const float s = d > 0.5f ? -local_opacity * (1.0f - d) / d : local_opacity;
932 ttb[2] = fmodf((tta[2] * (1.0f - s)) + ttb[2] * s + 1.0f, 1.0f);
933
934 dt_LCH_2_Lab(ttb, tb);
935 _CLAMP_XYZ(tb, min, max);
936 _blend_Lab_rescale(tb, out + j);
937 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
938 }
939}
940
941/* color adjustment; blend hue and chroma; take lightness from module output */
942__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
943static void _blend_coloradjust(const float *const restrict a, const float *const restrict b,
944 float *const restrict out, const float *const restrict mask, const size_t stride,
946{
947 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
948 {
949 const float local_opacity = mask[i];
950 dt_aligned_pixel_t ta, tb;
951 dt_aligned_pixel_t tta, ttb;
952
953 _blend_Lab_scale(a + j, ta);
954 _CLAMP_XYZ(ta, min, max);
955 dt_Lab_2_LCH(ta, tta);
956
957 _blend_Lab_scale(b + j, tb);
958 _CLAMP_XYZ(tb, min, max);
959 dt_Lab_2_LCH(tb, ttb);
960
961 // ttb[0] (output lightness) unchanged
962 ttb[1] = (tta[1] * (1.0f - local_opacity)) + ttb[1] * local_opacity;
963
964 /* blend hue along shortest distance on color circle */
965 const float d = fabsf(tta[2] - ttb[2]);
966 const float s = d > 0.5f ? -local_opacity * (1.0f - d) / d : local_opacity;
967 ttb[2] = fmodf((tta[2] * (1.0f - s)) + ttb[2] * s + 1.0f, 1.0f);
968
969 dt_LCH_2_Lab(ttb, tb);
970 _CLAMP_XYZ(tb, min, max);
971 _blend_Lab_rescale(tb, out + j);
972 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
973 }
974}
975
976/* blend only lightness in Lab color space without any clamping */
977__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
978static void _blend_Lab_lightness(const float *const restrict a, const float *const restrict b,
979 float *const restrict out, const float *const restrict mask, const size_t stride,
981{
982 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
983 {
984 const float local_opacity = mask[i];
985 dt_aligned_pixel_t ta, tb;
986
987 _blend_Lab_scale(a + j, ta);
988 _blend_Lab_scale(b + j, tb);
989
990 tb[0] = ta[0] * (1.0f - local_opacity) + tb[0] * local_opacity;
991 tb[1] = ta[1];
992 tb[2] = ta[2];
993
994 _blend_Lab_rescale(tb, out + j);
995 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
996 }
997}
998
999/* blend only a-channel in Lab color space without any clamping */
1000__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
1001static void _blend_Lab_a(const float *const restrict a, const float *const restrict b,
1002 float *const restrict out, const float *const restrict mask, const size_t stride,
1004{
1005 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1006 {
1007 const float local_opacity = mask[i];
1008 dt_aligned_pixel_t ta, tb;
1009
1010 _blend_Lab_scale(a + j, ta);
1011 _blend_Lab_scale(b + j, tb);
1012
1013 tb[0] = ta[0];
1014 tb[1] = ta[1] * (1.0f - local_opacity) + tb[1] * local_opacity;
1015 tb[2] = ta[2];
1016
1017 _blend_Lab_rescale(tb, out + j);
1018 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
1019 }
1020}
1021
1022/* blend only b-channel in Lab color space without any clamping */
1023__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
1024static void _blend_Lab_b(const float *const restrict a, const float *const restrict b,
1025 float *const restrict out, const float *const restrict mask, const size_t stride,
1027{
1028 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1029 {
1030 const float local_opacity = mask[i];
1031 dt_aligned_pixel_t ta, tb;
1032
1033 _blend_Lab_scale(a + j, ta);
1034 _blend_Lab_scale(b + j, tb);
1035
1036 tb[0] = ta[0];
1037 tb[1] = ta[1];
1038 tb[2] = ta[2] * (1.0f - local_opacity) + tb[2] * local_opacity;
1039
1040 _blend_Lab_rescale(tb, out + j);
1041 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
1042 }
1043}
1044
1045
1046/* blend only color in Lab color space without any clamping */
1047__OMP_DECLARE_SIMD__(aligned(a, b, out, min, max: 16) uniform(stride, min, max))
1048static void _blend_Lab_color(const float *const restrict a, const float *const restrict b,
1049 float *const restrict out, const float *const restrict mask, const size_t stride,
1051{
1052 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1053 {
1054 float local_opacity = mask[i];
1055 dt_aligned_pixel_t ta, tb;
1056
1057 _blend_Lab_scale(a + j, ta);
1058 _blend_Lab_scale(b + j, tb);
1059
1060 tb[0] = ta[0];
1061 tb[1] = ta[1] * (1.0f - local_opacity) + tb[1] * local_opacity;
1062 tb[2] = ta[2] * (1.0f - local_opacity) + tb[2] * local_opacity;
1063
1064 _blend_Lab_rescale(tb, out + j);
1065 out[j + DT_BLENDIF_LAB_BCH] = local_opacity;
1066 }
1067}
1068
1069
1070static _blend_row_func *_choose_blend_func(const unsigned int blend_mode)
1071{
1072 _blend_row_func *blend = NULL;
1073
1074 /* select the blend operator */
1075 switch(blend_mode & DEVELOP_BLEND_MODE_MASK)
1076 {
1078 blend = _blend_lighten;
1079 break;
1081 blend = _blend_darken;
1082 break;
1084 blend = _blend_multiply;
1085 break;
1087 blend = _blend_average;
1088 break;
1089 case DEVELOP_BLEND_ADD:
1090 blend = _blend_add;
1091 break;
1093 blend = _blend_subtract;
1094 break;
1096 blend = _blend_difference;
1097 break;
1099 blend = _blend_difference2;
1100 break;
1102 blend = _blend_screen;
1103 break;
1105 blend = _blend_overlay;
1106 break;
1108 blend = _blend_softlight;
1109 break;
1111 blend = _blend_hardlight;
1112 break;
1114 blend = _blend_vividlight;
1115 break;
1117 blend = _blend_linearlight;
1118 break;
1120 blend = _blend_pinlight;
1121 break;
1123 blend = _blend_lightness;
1124 break;
1126 blend = _blend_chromaticity;
1127 break;
1128 case DEVELOP_BLEND_HUE:
1129 blend = _blend_hue;
1130 break;
1132 blend = _blend_color;
1133 break;
1135 blend = _blend_normal_bounded;
1136 break;
1138 blend = _blend_coloradjust;
1139 break;
1142 blend = _blend_Lab_lightness;
1143 break;
1145 blend = _blend_Lab_a;
1146 break;
1148 blend = _blend_Lab_b;
1149 break;
1151 blend = _blend_Lab_color;
1152 break;
1153
1154 /* fallback to normal blend */
1156 default:
1157 blend = _blend_normal_unbounded;
1158 break;
1159 }
1160
1161 return blend;
1162}
1163
1164
1165__OMP_DECLARE_SIMD__(aligned(out:16))
1166static inline void _display_channel_value(dt_aligned_pixel_t out, const float value, const float mask)
1167{
1168 out[0] = value;
1169 out[1] = value;
1170 out[2] = value;
1171 out[3] = mask;
1172}
1173
1174__OMP_DECLARE_SIMD__(aligned(a, b:16) uniform(channel, stride))
1175static void _display_channel(const float *const restrict a, float *const restrict b,
1176 const float *const restrict mask, const size_t stride, const int channel,
1177 const float *const restrict boost_factors)
1178{
1179 switch(channel)
1180 {
1182 {
1183 const float factor = 1.0f / (100.0f * exp2f(boost_factors[DEVELOP_BLENDIF_L_in]));
1184 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1185 {
1186 const float c = clamp_simd(a[j + 0] * factor);
1187 _display_channel_value(b + j, c, mask[i]);
1188 }
1189 break;
1190 }
1192 {
1193 const float factor = 1.0f / (100.0f * exp2f(boost_factors[DEVELOP_BLENDIF_L_out]));
1194 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1195 {
1196 const float c = clamp_simd(b[j + 0] * factor);
1197 _display_channel_value(b + j, c, mask[i]);
1198 }
1199 break;
1200 }
1202 {
1203 const float factor = 1.0f / (256.0f * exp2f(boost_factors[DEVELOP_BLENDIF_A_in]));
1204 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1205 {
1206 const float c = clamp_simd(a[j + 1] * factor + 0.5f);
1207 _display_channel_value(b + j, c, mask[i]);
1208 }
1209 break;
1210 }
1212 {
1213 const float factor = 1.0f / (256.0f * exp2f(boost_factors[DEVELOP_BLENDIF_A_out]));
1214 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1215 {
1216 const float c = clamp_simd(b[j + 1] * factor + 0.5f);
1217 _display_channel_value(b + j, c, mask[i]);
1218 }
1219 break;
1220 }
1222 {
1223 const float factor = 1.0f / (256.0f * exp2f(boost_factors[DEVELOP_BLENDIF_B_in]));
1224 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1225 {
1226 const float c = clamp_simd(a[j + 2] * factor + 0.5f);
1227 _display_channel_value(b + j, c, mask[i]);
1228 }
1229 break;
1230 }
1232 {
1233 const float factor = 1.0f / (256.0f * exp2f(boost_factors[DEVELOP_BLENDIF_B_out]));
1234 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1235 {
1236 const float c = clamp_simd(b[j + 2] * factor + 0.5f);
1237 _display_channel_value(b + j, c, mask[i]);
1238 }
1239 break;
1240 }
1242 {
1243 const float factor = 1.0f / (128.0f * sqrtf(2.0f) * exp2f(boost_factors[DEVELOP_BLENDIF_C_in]));
1244 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1245 {
1247 dt_Lab_2_LCH(a + j, LCH);
1248 const float c = clamp_simd(LCH[1] * factor);
1249 _display_channel_value(b + j, c, mask[i]);
1250 }
1251 break;
1252 }
1254 {
1255 const float factor = 1.0f / (128.0f * sqrtf(2.0f) * exp2f(boost_factors[DEVELOP_BLENDIF_C_out]));
1256 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1257 {
1259 dt_Lab_2_LCH(b + j, LCH);
1260 const float c = clamp_simd(LCH[1] * factor);
1261 _display_channel_value(b + j, c, mask[i]);
1262 }
1263 break;
1264 }
1266 // no boost factor for hues
1267 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1268 {
1270 dt_Lab_2_LCH(a + j, LCH);
1271 const float c = clamp_simd(LCH[2]);
1272 _display_channel_value(b + j, c, mask[i]);
1273 }
1274 break;
1276 // no boost factor for hues
1277 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1278 {
1280 dt_Lab_2_LCH(b + j, LCH);
1281 const float c = clamp_simd(LCH[2]);
1282 _display_channel_value(b + j, c, mask[i]);
1283 }
1284 break;
1285 default:
1286 for(size_t i = 0, j = 0; i < stride; i++, j += DT_BLENDIF_LAB_CH)
1287 {
1288 _display_channel_value(b + j, 0.0f, mask[i]);
1289 }
1290 break;
1291 }
1292}
1293
1294
1295__OMP_DECLARE_SIMD__(aligned(a, b:16) uniform(stride))
1296static inline void _copy_mask(const float *const restrict a, float *const restrict b, const size_t stride)
1297{
1298 __OMP_SIMD__(aligned(a, b: 16))
1299 for(size_t x = DT_BLENDIF_LAB_BCH; x < stride; x += DT_BLENDIF_LAB_CH) b[x] = a[x];
1300}
1301
1303 const struct dt_dev_pixelpipe_iop_t *piece,
1304 const float *const a, float *const b,
1305 const float *const restrict mask,
1306 const dt_dev_pixelpipe_display_mask_t request_mask_display)
1307{
1308 const dt_iop_roi_t *const roi_in = &piece->roi_in;
1309 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1310 const dt_develop_blend_params_t *const d = (const dt_develop_blend_params_t *const)piece->blendop_data;
1311
1312 if(piece->dsc_in.channels != DT_BLENDIF_LAB_CH) return;
1313
1314 const int xoffs = roi_out->x - roi_in->x;
1315 const int yoffs = roi_out->y - roi_in->y;
1316 const int iwidth = roi_in->width;
1317 const int owidth = roi_out->width;
1318 const int oheight = roi_out->height;
1319
1320 // only non-zero if mask_display was set by an _earlier_ module
1321 const dt_dev_pixelpipe_display_mask_t mask_display = pipe->mask_display;
1322
1323 // process the blending operator
1324 if(request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_ANY)
1325 {
1326 const float *const restrict boost_factors = d->blendif_boost_factors;
1327 const dt_dev_pixelpipe_display_mask_t channel = request_mask_display & DT_DEV_PIXELPIPE_DISPLAY_ANY;
1330 for(size_t y = 0; y < oheight; y++)
1331 {
1332 const size_t a_start = ((y + yoffs) * iwidth + xoffs) * DT_BLENDIF_LAB_CH;
1333 const size_t b_start = y * owidth * DT_BLENDIF_LAB_CH;
1334 const size_t m_start = y * owidth;
1335 _display_channel(a + a_start, b + b_start, mask + m_start, owidth, channel, boost_factors);
1336 }
1337
1338 // the generated output of the channel masks is expressed in RGB but this blending needs to output pixels in
1339 // the Lab color space. A conversion needs thus to be performed. As the pipe is using the work profile to
1340 // convert between Lab and the gamma module (which works in RGB), we need to use use that profile for the
1341 // conversion.
1342 const size_t buffsize = (size_t)owidth * oheight * DT_BLENDIF_LAB_CH;
1343 if(!IS_NULL_PTR(profile))
1344 {
1346 for(size_t j = 0; j < buffsize; j += DT_BLENDIF_LAB_CH)
1347 {
1348 dt_aligned_pixel_t pixel;
1349 for_each_channel(c,aligned(b))
1350 pixel[c] = b[j+c];
1351 const float yellow_mask = b[j+3]; // preserve alpha for code which does in-place conversion
1352 dt_ioppr_rgb_matrix_to_lab(pixel, b + j, profile->matrix_in_transposed, profile->lut_in,
1353 profile->unbounded_coeffs_in, profile->lutsize, profile->nonlinearlut);
1354 b[j+3] = yellow_mask;
1355 }
1356 }
1357 else
1358 {
1359 __OMP_FOR_SIMD__(aligned(b:64))
1360 for(size_t j = 0; j < buffsize; j += DT_BLENDIF_LAB_CH)
1361 {
1363 const float yellow_mask = b[j+3]; // preserve alpha for code which does in-place conversion
1364 dt_Rec709_to_XYZ_D50(b + j, XYZ);
1365 dt_XYZ_to_Lab(XYZ, b + j);
1366 b[j+3] = yellow_mask;
1367 }
1368 }
1369 }
1370 else
1371 {
1372 _blend_row_func *const blend = _choose_blend_func(d->blend_mode);
1373 // minimum and maximum values after scaling !!!
1374 const dt_aligned_pixel_t min = { 0.0f, -1.0f, -1.0f, 0.0f };
1375 const dt_aligned_pixel_t max = { 1.0f, 1.0f, 1.0f, 1.0f };
1376
1377 float *tmp_buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)owidth * oheight * DT_BLENDIF_LAB_CH, 0);
1378 if (!IS_NULL_PTR(tmp_buffer))
1379 {
1380 dt_iop_image_copy(tmp_buffer, b, (size_t)owidth * oheight * DT_BLENDIF_LAB_CH);
1381 if((d->blend_mode & DEVELOP_BLEND_REVERSE) == DEVELOP_BLEND_REVERSE)
1382 {
1384 for(size_t y = 0; y < oheight; y++)
1385 {
1386 const size_t a_start = ((y + yoffs) * iwidth + xoffs) * DT_BLENDIF_LAB_CH;
1387 const size_t b_start = y * owidth * DT_BLENDIF_LAB_CH;
1388 const size_t m_start = y * owidth;
1389 blend(tmp_buffer + b_start, a + a_start, b + b_start, mask + m_start, owidth, min, max);
1390 }
1391 }
1392 else
1393 {
1395 for(size_t y = 0; y < oheight; y++)
1396 {
1397 const size_t a_start = ((y + yoffs) * iwidth + xoffs) * DT_BLENDIF_LAB_CH;
1398 const size_t b_start = y * owidth * DT_BLENDIF_LAB_CH;
1399 const size_t m_start = y * owidth;
1400 blend(a + a_start, tmp_buffer + b_start, b + b_start, mask + m_start, owidth, min, max);
1401 }
1402 }
1404 }
1405 }
1406
1407 if(mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK)
1408 {
1409 const size_t stride = owidth * DT_BLENDIF_LAB_CH;
1411 for(size_t y = 0; y < oheight; y++)
1412 {
1413 const size_t a_start = ((y + yoffs) * iwidth + xoffs) * DT_BLENDIF_LAB_CH;
1414 const size_t b_start = y * stride;
1415 _copy_mask(a + a_start, b + b_start, stride);
1416 }
1417 }
1418}
1419
1420// tools/update_modelines.sh
1421// remove-trailing-space on;
1422// clang-format off
1423// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1424// vim: shiftwidth=2 expandtab tabstop=2 cindent
1425// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1426// clang-format on
void dt_develop_blendif_process_parameters(float *const restrict parameters, const dt_develop_blend_params_t *const params)
Definition blend.c:253
@ DEVELOP_COMBINE_INV
Definition blend.h:123
@ DEVELOP_COMBINE_INCL
Definition blend.h:125
@ DEVELOP_BLENDIF_C_out
Definition blend.h:164
@ DEVELOP_BLENDIF_A_in
Definition blend.h:144
@ DEVELOP_BLENDIF_C_in
Definition blend.h:161
@ DEVELOP_BLENDIF_L_out
Definition blend.h:147
@ DEVELOP_BLENDIF_h_in
Definition blend.h:162
@ DEVELOP_BLENDIF_Lab_MASK
Definition blend.h:190
@ DEVELOP_BLENDIF_B_in
Definition blend.h:145
@ DEVELOP_BLENDIF_A_out
Definition blend.h:148
@ DEVELOP_BLENDIF_B_out
Definition blend.h:149
@ DEVELOP_BLENDIF_L_in
Definition blend.h:143
#define DEVELOP_BLENDIF_PARAMETER_ITEMS
Definition blend.h:353
@ DEVELOP_BLEND_LIGHTEN
Definition blend.h:65
@ DEVELOP_BLEND_COLOR
Definition blend.h:82
@ DEVELOP_BLEND_CHROMATICITY
Definition blend.h:80
@ DEVELOP_BLEND_DIFFERENCE
Definition blend.h:71
@ DEVELOP_BLEND_LIGHTNESS
Definition blend.h:79
@ DEVELOP_BLEND_BOUNDED
Definition blend.h:88
@ DEVELOP_BLEND_SUBTRACT
Definition blend.h:70
@ DEVELOP_BLEND_MODE_MASK
Definition blend.h:107
@ DEVELOP_BLEND_NORMAL2
Definition blend.h:87
@ DEVELOP_BLEND_HARDLIGHT
Definition blend.h:75
@ DEVELOP_BLEND_HUE
Definition blend.h:81
@ DEVELOP_BLEND_OVERLAY
Definition blend.h:73
@ DEVELOP_BLEND_LAB_A
Definition blend.h:94
@ DEVELOP_BLEND_LAB_COLOR
Definition blend.h:90
@ DEVELOP_BLEND_REVERSE
Definition blend.h:106
@ DEVELOP_BLEND_AVERAGE
Definition blend.h:68
@ DEVELOP_BLEND_MULTIPLY
Definition blend.h:67
@ DEVELOP_BLEND_SCREEN
Definition blend.h:72
@ DEVELOP_BLEND_PINLIGHT
Definition blend.h:78
@ DEVELOP_BLEND_LAB_L
Definition blend.h:93
@ DEVELOP_BLEND_LINEARLIGHT
Definition blend.h:77
@ DEVELOP_BLEND_ADD
Definition blend.h:69
@ DEVELOP_BLEND_VIVIDLIGHT
Definition blend.h:76
@ DEVELOP_BLEND_SOFTLIGHT
Definition blend.h:74
@ DEVELOP_BLEND_COLORADJUST
Definition blend.h:85
@ DEVELOP_BLEND_DARKEN
Definition blend.h:66
@ DEVELOP_BLEND_LAB_B
Definition blend.h:95
@ DEVELOP_BLEND_LAB_LIGHTNESS
Definition blend.h:89
@ DEVELOP_BLEND_DIFFERENCE2
Definition blend.h:86
@ DEVELOP_MASK_PARAMETRIC
Definition blend.h:115
#define DT_BLENDIF_LAB_BCH
Definition blendif_lab.c:35
static _blend_row_func * _choose_blend_func(const unsigned int blend_mode)
static float _blendif_compute_factor(const float value, const unsigned int invert_mask, const float *const restrict parameters)
Definition blendif_lab.c:56
#define DT_BLENDIF_LAB_CH
Definition blendif_lab.c:34
void dt_develop_blendif_lab_blend(const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const float *const a, float *const b, const float *const restrict mask, const dt_dev_pixelpipe_display_mask_t request_mask_display)
void() _blend_row_func(const float *const restrict a, const float *const restrict b, float *const restrict out, const float *const restrict mask, const size_t stride, const dt_aligned_pixel_t min, const dt_aligned_pixel_t max)
Definition blendif_lab.c:38
static void _CLAMP_XYZ(dt_aligned_pixel_t XYZ, const dt_aligned_pixel_t min, const dt_aligned_pixel_t max)
Definition blendif_lab.c:50
static float _CLAMP(const float x, const float min, const float max)
Definition blendif_lab.c:44
void dt_develop_blendif_lab_make_mask(const struct dt_dev_pixelpipe_iop_t *piece, const float *const restrict a, const float *const restrict b, float *const restrict mask)
static void _display_channel_value(dt_aligned_pixel_t out, const float value, const float mask)
static void _blend_Lab_rescale(const float *i, float *o)
static void _blend_Lab_scale(const float *i, float *o)
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const float x
const float f
static dt_aligned_pixel_t LCH
static const float const float const float min
static dt_aligned_pixel_t XYZ
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
dt_Rec709_to_XYZ_D50(rgb, XYZ)
dt_XYZ_to_Lab(XYZ, Lab)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
dt_dev_pixelpipe_display_mask_t
Definition develop.h:121
@ DT_DEV_PIXELPIPE_DISPLAY_a
Definition develop.h:127
@ DT_DEV_PIXELPIPE_DISPLAY_OUTPUT
Definition develop.h:125
@ DT_DEV_PIXELPIPE_DISPLAY_L
Definition develop.h:126
@ DT_DEV_PIXELPIPE_DISPLAY_ANY
Definition develop.h:143
@ DT_DEV_PIXELPIPE_DISPLAY_LCH_h
Definition develop.h:134
@ DT_DEV_PIXELPIPE_DISPLAY_LCH_C
Definition develop.h:133
@ DT_DEV_PIXELPIPE_DISPLAY_b
Definition develop.h:128
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
__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_fill(float *const buf, const float fill_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:218
#define DEVELOP_BLENDIF_SIZE
Definition lightroom.c:236
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define DT_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_FOR__(...)
Definition openmp.h:98
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL__(...)
Definition openmp.h:94
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_FOR_SIMD__(...)
Definition openmp.h:97
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
static float clamp_simd(const float x)
const float factor
Definition pdf.h:91
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
dt_iop_buffer_dsc_t dsc_in
unsigned int channels
Definition format.h:83
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50