Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: "blend" step of "raster pipeline"
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : * Copyright (c) 2009, Frank Warmerdam
10 :
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "gdalalg_raster_blend.h"
15 :
16 : #include "cpl_conv.h"
17 : #include "gdal_priv.h"
18 : #include "gdal_utils.h"
19 :
20 : #include <algorithm>
21 : #include <array>
22 : #include <cassert>
23 : #include <limits>
24 : #include <utility>
25 :
26 : #if defined(__x86_64) || defined(_M_X64)
27 : #define HAVE_SSE2
28 : #include <immintrin.h>
29 : #endif
30 : #ifdef HAVE_SSE2
31 : #include "gdalsse_priv.h"
32 : #endif
33 :
34 : //! @cond Doxygen_Suppress
35 :
36 : #ifndef _
37 : #define _(x) (x)
38 : #endif
39 :
40 : /************************************************************************/
41 : /* CompositionModes */
42 : /************************************************************************/
43 614 : std::map<CompositionMode, std::string> CompositionModes()
44 : {
45 : return {
46 1228 : {CompositionMode::SRC_OVER, "src-over"},
47 0 : {CompositionMode::HSV_VALUE, "hsv-value"},
48 0 : {CompositionMode::MULTIPLY, "multiply"},
49 0 : {CompositionMode::SCREEN, "screen"},
50 0 : {CompositionMode::OVERLAY, "overlay"},
51 0 : {CompositionMode::HARD_LIGHT, "hard-light"},
52 0 : {CompositionMode::DARKEN, "darken"},
53 0 : {CompositionMode::LIGHTEN, "lighten"},
54 0 : {CompositionMode::COLOR_BURN, "color-burn"},
55 0 : {CompositionMode::COLOR_DODGE, "color-dodge"},
56 6754 : };
57 : }
58 :
59 : /************************************************************************/
60 : /* CompositionModeToString() */
61 : /************************************************************************/
62 :
63 229 : std::string CompositionModeToString(CompositionMode mode)
64 : {
65 458 : const auto &modes = CompositionModes();
66 229 : const auto &iter = modes.find(mode);
67 229 : if (iter != modes.end())
68 : {
69 229 : return iter->second;
70 : }
71 0 : CPLError(CE_Failure, CPLE_IllegalArg,
72 : "Invalid composition mode value: %d, returning 'src-over'",
73 : static_cast<int>(mode));
74 0 : return "src-over";
75 : }
76 :
77 : /************************************************************************/
78 : /* CompositionModesIdentifiers() */
79 : /************************************************************************/
80 :
81 228 : std::vector<std::string> CompositionModesIdentifiers()
82 : {
83 456 : const auto &modes = CompositionModes();
84 228 : std::vector<std::string> identifiers;
85 2508 : for (const auto &pair : modes)
86 : {
87 2280 : identifiers.push_back(pair.second);
88 : }
89 456 : return identifiers;
90 : }
91 :
92 : /************************************************************************/
93 : /* CompositionModeFromString() */
94 : /************************************************************************/
95 :
96 157 : CompositionMode CompositionModeFromString(const std::string &str)
97 : {
98 314 : const auto &modes = CompositionModes();
99 : auto iter =
100 : std::find_if(modes.begin(), modes.end(),
101 965 : [&str](const auto &pair) { return pair.second == str; });
102 157 : if (iter != modes.end())
103 : {
104 157 : return iter->first;
105 : }
106 0 : CPLError(CE_Failure, CPLE_IllegalArg,
107 : "Invalid composition identifier: %s, returning SRC_OVER",
108 : str.c_str());
109 0 : return CompositionMode::SRC_OVER;
110 : }
111 :
112 : /************************************************************************/
113 : /* MinBandCountForCompositionMode() */
114 : /************************************************************************/
115 :
116 : //! Returns the minimum number of bands required for the given composition mode
117 334 : int MinBandCountForCompositionMode(CompositionMode mode)
118 : {
119 334 : switch (mode)
120 : {
121 36 : case CompositionMode::HSV_VALUE:
122 36 : return 3;
123 298 : case CompositionMode::SRC_OVER:
124 : case CompositionMode::MULTIPLY:
125 : case CompositionMode::SCREEN:
126 : case CompositionMode::OVERLAY:
127 : case CompositionMode::HARD_LIGHT:
128 : case CompositionMode::DARKEN:
129 : case CompositionMode::LIGHTEN:
130 : case CompositionMode::COLOR_BURN:
131 : case CompositionMode::COLOR_DODGE:
132 298 : return 1;
133 : }
134 : // unreachable...
135 0 : return 1;
136 : }
137 :
138 : /************************************************************************/
139 : /* MaxBandCountForCompositionMode() */
140 : /************************************************************************/
141 :
142 : /**
143 : * Returns the maximum number of bands allowed for the given composition mode
144 : */
145 334 : int MaxBandCountForCompositionMode(CompositionMode mode)
146 : {
147 334 : switch (mode)
148 : {
149 334 : case CompositionMode::SRC_OVER:
150 : case CompositionMode::HSV_VALUE:
151 : case CompositionMode::MULTIPLY:
152 : case CompositionMode::SCREEN:
153 : case CompositionMode::OVERLAY:
154 : case CompositionMode::HARD_LIGHT:
155 : case CompositionMode::DARKEN:
156 : case CompositionMode::LIGHTEN:
157 : case CompositionMode::COLOR_BURN:
158 : case CompositionMode::COLOR_DODGE:
159 334 : return 4;
160 : }
161 : // unreachable...
162 0 : return 4;
163 : }
164 :
165 : /************************************************************************/
166 : /* BandCountIsCompatibleWithCompositionMode() */
167 : /************************************************************************/
168 :
169 : //! Checks whether the number of bands is compatible with the given composition mode
170 333 : bool BandCountIsCompatibleWithCompositionMode(int bandCount,
171 : CompositionMode mode)
172 : {
173 333 : const int minBands = MinBandCountForCompositionMode(mode);
174 333 : const int maxBands = MaxBandCountForCompositionMode(mode);
175 333 : return minBands <= bandCount && bandCount <= maxBands;
176 : }
177 :
178 : /************************************************************************/
179 : /* MulScale255() */
180 : /************************************************************************/
181 :
182 : /** Multiply 2 bytes considering them as ratios with 255 = 100%, and return their product unscaled to [0, 255], by ceiling */
183 10964 : inline GByte MulScale255(GByte a, GByte b)
184 : {
185 10964 : return static_cast<GByte>((a * b + 255) / 256);
186 : }
187 :
188 : /************************************************************************/
189 : /* ProcessAlphaChannels */
190 : /************************************************************************/
191 :
192 824 : static inline void ProcessAlphaChannels(size_t i,
193 : const GByte *CPL_RESTRICT pabyA,
194 : const GByte *CPL_RESTRICT pabyOverlayA,
195 : int nOpacity, bool bSwappedOpacity,
196 : GByte &outA, GByte &outOverlaA,
197 : GByte &outFinalAlpha)
198 : {
199 : // Apply opacity depending on whether overlay and base were swapped
200 824 : const GByte byOpacity = static_cast<GByte>(nOpacity);
201 824 : if (!bSwappedOpacity)
202 : {
203 756 : outOverlaA =
204 756 : pabyOverlayA ? MulScale255(pabyOverlayA[i], byOpacity) : byOpacity;
205 :
206 756 : outA = pabyA ? pabyA[i] : 255;
207 : }
208 : else
209 : {
210 68 : outOverlaA = pabyOverlayA ? pabyOverlayA[i] : 255;
211 68 : if (outOverlaA != 255)
212 : {
213 0 : outOverlaA = pabyOverlayA ? pabyOverlayA[i] : 255;
214 : }
215 68 : outA = pabyA ? MulScale255(pabyA[i], byOpacity) : byOpacity;
216 : }
217 :
218 : // Da' = Sa + Da - Sa.Da
219 824 : outFinalAlpha =
220 824 : static_cast<GByte>(outOverlaA + outA - MulScale255(outOverlaA, outA));
221 824 : }
222 :
223 : /************************************************************************/
224 : /* DivScale255() */
225 : /************************************************************************/
226 :
227 : /** Divide 2 bytes considering them as ratios with 255 = 100%, and return their quotient unscaled to [0, 255], by flooring
228 : * \warning Caution: this function does not check that the result actually fits on a byte, and just casts the computed value to byte.
229 : */
230 2456 : inline GByte DivScale255(GByte a, GByte b)
231 : {
232 2456 : if (a == 0)
233 : {
234 272 : return 0;
235 : }
236 2184 : else if (b == 0)
237 : {
238 0 : return 255;
239 : }
240 : else
241 : {
242 2184 : const int nRes = (a * 255) / b;
243 : #ifdef DEBUG
244 2184 : CPLAssert(nRes <= 255);
245 : #endif
246 2184 : return static_cast<GByte>(nRes);
247 : }
248 : }
249 :
250 : /************************************************************************/
251 : /* PremultiplyChannels */
252 : /************************************************************************/
253 :
254 : //! Premultiply RGB channels by alpha (A)
255 1648 : static inline void PremultiplyChannels(size_t i, const GByte *pabyR,
256 : const GByte *pabyG, const GByte *pabyB,
257 : GByte &outR, GByte &outG, GByte &outB,
258 : const GByte &A)
259 :
260 : {
261 1648 : if (A == 255)
262 : {
263 960 : outR = pabyR ? pabyR[i] : 255;
264 960 : outG = pabyG ? pabyG[i] : outR; // in case only R is present
265 960 : outB = pabyB ? pabyB[i] : outR; // in case only R is present
266 : }
267 : else
268 : {
269 688 : outR = pabyR ? MulScale255(pabyR[i], A) : A;
270 688 : outG = pabyG ? MulScale255(pabyG[i], A)
271 : : outR; // in case only R is present
272 688 : outB = pabyB ? MulScale255(pabyB[i], A)
273 : : outR; // in case only R is present
274 : }
275 1648 : }
276 :
277 : /************************************************************************/
278 : /* GDALRasterBlendAlgorithm::GDALRasterBlendAlgorithm() */
279 : /************************************************************************/
280 :
281 228 : GDALRasterBlendAlgorithm::GDALRasterBlendAlgorithm(bool standaloneStep)
282 : : GDALRasterPipelineStepAlgorithm(
283 : NAME, DESCRIPTION, HELP_URL,
284 0 : ConstructorOptions()
285 228 : .SetStandaloneStep(standaloneStep)
286 228 : .SetAddDefaultArguments(false)
287 456 : .SetInputDatasetHelpMsg(_("Input raster dataset"))
288 456 : .SetInputDatasetAlias("color-input")
289 456 : .SetInputDatasetMetaVar("COLOR-INPUT")
290 684 : .SetOutputDatasetHelpMsg(_("Output raster dataset")))
291 : {
292 228 : const auto AddOverlayDatasetArg = [this]()
293 : {
294 : auto &arg = AddArg("overlay", 0, _("Overlay dataset"),
295 456 : &m_overlayDataset, GDAL_OF_RASTER)
296 228 : .SetPositional()
297 228 : .SetRequired();
298 :
299 228 : SetAutoCompleteFunctionForFilename(arg, GDAL_OF_RASTER);
300 228 : };
301 :
302 228 : if (standaloneStep)
303 : {
304 186 : AddRasterInputArgs(false, false);
305 186 : AddOverlayDatasetArg();
306 186 : AddProgressArg();
307 186 : AddRasterOutputArgs(false);
308 : }
309 : else
310 : {
311 42 : AddRasterHiddenInputDatasetArg();
312 42 : AddOverlayDatasetArg();
313 : }
314 :
315 : const std::vector<std::string> compositionModeChoices{
316 228 : CompositionModesIdentifiers()};
317 456 : AddArg("operator", 0, _("Composition operator"), &m_operatorIdentifier)
318 228 : .SetChoices(compositionModeChoices)
319 456 : .SetDefault(CompositionModeToString(CompositionMode::SRC_OVER))
320 : .AddAction(
321 157 : [this]()
322 385 : { m_operator = CompositionModeFromString(m_operatorIdentifier); });
323 :
324 : AddArg("opacity", 0,
325 : _("Opacity percentage to apply to the overlay dataset (0=fully "
326 : "transparent, 100=full use of overlay opacity)"),
327 456 : &m_opacity)
328 228 : .SetDefault(m_opacity)
329 228 : .SetMinValueIncluded(0)
330 228 : .SetMaxValueIncluded(OPACITY_INPUT_RANGE);
331 :
332 410 : AddValidationAction([this]() { return ValidateGlobal(); });
333 228 : }
334 :
335 : namespace
336 : {
337 :
338 : /************************************************************************/
339 : /* BlendDataset */
340 : /************************************************************************/
341 :
342 : class BlendDataset final : public GDALDataset
343 : {
344 : public:
345 : BlendDataset(GDALDataset &oColorDS, GDALDataset &oOverlayDS,
346 : const CompositionMode eOperator, int nOpacity255Scale,
347 : bool bSwappedOpacity);
348 : ~BlendDataset() override;
349 :
350 2 : CPLErr GetGeoTransform(GDALGeoTransform >) const override
351 : {
352 2 : return m_oColorDS.GetGeoTransform(gt);
353 : }
354 :
355 2 : const OGRSpatialReference *GetSpatialRef() const override
356 : {
357 2 : return m_oColorDS.GetSpatialRef();
358 : }
359 :
360 : bool AcquireSourcePixels(int nXOff, int nYOff, int nXSize, int nYSize,
361 : int nBufXSize, int nBufYSize,
362 : GDALRasterIOExtraArg *psExtraArg);
363 :
364 : protected:
365 : CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
366 : int nYSize, void *pData, int nBufXSize, int nBufYSize,
367 : GDALDataType eBufType, int nBandCount,
368 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
369 : GSpacing nLineSpace, GSpacing nBandSpace,
370 : GDALRasterIOExtraArg *psExtraArg) override;
371 :
372 : private:
373 : friend class BlendBand;
374 : GDALDataset &m_oColorDS;
375 : GDALDataset &m_oOverlayDS;
376 : const CompositionMode m_operator;
377 : const int m_opacity255Scale;
378 : std::vector<std::unique_ptr<BlendDataset>> m_apoOverviews{};
379 : int m_nCachedXOff = 0;
380 : int m_nCachedYOff = 0;
381 : int m_nCachedXSize = 0;
382 : int m_nCachedYSize = 0;
383 : int m_nCachedBufXSize = 0;
384 : int m_nCachedBufYSize = 0;
385 : GDALRasterIOExtraArg m_sCachedExtraArg{};
386 : std::vector<GByte> m_abyBuffer{};
387 : bool m_ioError = false;
388 : bool m_bSwappedOpacity = false;
389 : };
390 :
391 : /************************************************************************/
392 : /* rgb_to_hs() */
393 : /************************************************************************/
394 :
395 : // rgb comes in as [r,g,b] with values in the range [0,255]. The returned
396 : // values will be with hue and saturation in the range [0,1].
397 :
398 : // Derived from hsv_merge.py
399 :
400 4982560 : static void rgb_to_hs(int r, int g, int b, float *h, float *s)
401 : {
402 : int minc, maxc;
403 4982560 : if (r <= g)
404 : {
405 2531110 : if (r <= b)
406 : {
407 1700760 : minc = r;
408 1700760 : maxc = std::max(g, b);
409 : }
410 : else /* b < r */
411 : {
412 830353 : minc = b;
413 830353 : maxc = g;
414 : }
415 : }
416 : else /* g < r */
417 : {
418 2451460 : if (g <= b)
419 : {
420 1659840 : minc = g;
421 1659840 : maxc = std::max(r, b);
422 : }
423 : else /* b < g */
424 : {
425 791616 : minc = b;
426 791616 : maxc = r;
427 : }
428 : }
429 4982560 : const int maxc_minus_minc = maxc - minc;
430 4982560 : if (s)
431 4982560 : *s = maxc_minus_minc / static_cast<float>(std::max(1, maxc));
432 4982560 : if (h)
433 : {
434 4982560 : const float maxc_minus_minc_times_6 =
435 4982560 : maxc_minus_minc == 0 ? 1.0f : 6.0f * maxc_minus_minc;
436 4982560 : if (maxc == b)
437 1700100 : *h = 4.0f / 6.0f + (r - g) / maxc_minus_minc_times_6;
438 3282460 : else if (maxc == g)
439 1660930 : *h = 2.0f / 6.0f + (b - r) / maxc_minus_minc_times_6;
440 : else
441 : {
442 1621540 : const float tmp = (g - b) / maxc_minus_minc_times_6;
443 1621540 : *h = tmp < 0.0f ? tmp + 1.0f : tmp;
444 : }
445 : }
446 4982560 : }
447 :
448 : /************************************************************************/
449 : /* choose_among() */
450 : /************************************************************************/
451 :
452 : template <typename T>
453 5509780 : static inline T choose_among(int idx, T a0, T a1, T a2, T a3, T a4, T a5)
454 : {
455 5509780 : switch (idx)
456 : {
457 917280 : case 0:
458 917280 : return a0;
459 918255 : case 1:
460 918255 : return a1;
461 919239 : case 2:
462 919239 : return a2;
463 918978 : case 3:
464 918978 : return a3;
465 918751 : case 4:
466 918751 : return a4;
467 917280 : default:
468 917280 : break;
469 : }
470 917280 : return a5;
471 : }
472 :
473 : /************************************************************************/
474 : /* hsv_to_rgb() */
475 : /************************************************************************/
476 :
477 : // hsv comes in as [h,s,v] with hue and saturation in the range [0,1],
478 : // but value in the range [0,255].
479 :
480 : // Derived from hsv_merge.py
481 :
482 4982560 : static void hsv_to_rgb(float h, float s, GByte v, GByte *r, GByte *g, GByte *b)
483 : {
484 4982560 : const int i = static_cast<int>(6.0f * h);
485 4982560 : const float f = 6.0f * h - i;
486 4982560 : const GByte p = static_cast<GByte>(v * (1.0f - s) + 0.5f);
487 4982560 : const GByte q = static_cast<GByte>(v * (1.0f - s * f) + 0.5f);
488 4982560 : const GByte t = static_cast<GByte>(v * (1.0f - s * (1.0f - f)) + 0.5f);
489 :
490 4982560 : if (r)
491 1836600 : *r = choose_among(i, v, q, p, p, t, v);
492 4982560 : if (g)
493 1836590 : *g = choose_among(i, t, v, v, q, p, p);
494 4982560 : if (b)
495 1836590 : *b = choose_among(i, p, p, t, v, v, q);
496 4982560 : }
497 :
498 : /************************************************************************/
499 : /* XMM_RGB_to_HS() */
500 : /************************************************************************/
501 :
502 : #ifdef HAVE_SSE2
503 : static inline void
504 538808 : XMM_RGB_to_HS(const GByte *CPL_RESTRICT pInR, const GByte *CPL_RESTRICT pInG,
505 : const GByte *CPL_RESTRICT pInB, const XMMReg4Float &zero,
506 : const XMMReg4Float &one, const XMMReg4Float &six,
507 : const XMMReg4Float &two_over_six,
508 : const XMMReg4Float &four_over_six, XMMReg4Float &h,
509 : XMMReg4Float &s)
510 : {
511 538808 : const auto r = XMMReg4Float::Load4Val(pInR);
512 538808 : const auto g = XMMReg4Float::Load4Val(pInG);
513 538808 : const auto b = XMMReg4Float::Load4Val(pInB);
514 538808 : const auto minc = XMMReg4Float::Min(XMMReg4Float::Min(r, g), b);
515 538808 : const auto maxc = XMMReg4Float::Max(XMMReg4Float::Max(r, g), b);
516 538808 : const auto max_minus_min = maxc - minc;
517 538808 : s = max_minus_min / XMMReg4Float::Max(one, maxc);
518 : const auto inv_max_minus_min_times_6_0 =
519 538808 : XMMReg4Float::Ternary(XMMReg4Float::Equals(max_minus_min, zero), one,
520 538808 : six * max_minus_min)
521 538808 : .inverse();
522 538808 : const auto tmp = (g - b) * inv_max_minus_min_times_6_0;
523 538808 : h = XMMReg4Float::Ternary(
524 538808 : XMMReg4Float::Equals(maxc, b),
525 538808 : four_over_six + (r - g) * inv_max_minus_min_times_6_0,
526 538808 : XMMReg4Float::Ternary(
527 538808 : XMMReg4Float::Equals(maxc, g),
528 538808 : two_over_six + (b - r) * inv_max_minus_min_times_6_0,
529 538808 : XMMReg4Float::Ternary(XMMReg4Float::Lesser(tmp, zero), tmp + one,
530 538808 : tmp)));
531 538808 : }
532 : #endif
533 :
534 : /************************************************************************/
535 : /* patch_value_line() */
536 : /************************************************************************/
537 :
538 : static
539 : #ifdef __GNUC__
540 : __attribute__((__noinline__))
541 : #endif
542 : void
543 695 : patch_value_line(int nCount, const GByte *CPL_RESTRICT pInR,
544 : const GByte *CPL_RESTRICT pInG,
545 : const GByte *CPL_RESTRICT pInB,
546 : const GByte *CPL_RESTRICT pInGray,
547 : GByte *CPL_RESTRICT pOutR, GByte *CPL_RESTRICT pOutG,
548 : GByte *CPL_RESTRICT pOutB)
549 : {
550 695 : int i = 0;
551 : #ifdef HAVE_SSE2
552 695 : const auto zero = XMMReg4Float::Zero();
553 695 : const auto one = XMMReg4Float::Set1(1.0f);
554 695 : const auto six = XMMReg4Float::Set1(6.0f);
555 695 : const auto two_over_six = XMMReg4Float::Set1(2.0f / 6.0f);
556 695 : const auto four_over_six = two_over_six + two_over_six;
557 :
558 695 : constexpr int ELTS = 8;
559 270099 : for (; i + (ELTS - 1) < nCount; i += ELTS)
560 : {
561 269404 : XMMReg4Float h0, s0;
562 269404 : XMM_RGB_to_HS(pInR + i, pInG + i, pInB + i, zero, one, six,
563 : two_over_six, four_over_six, h0, s0);
564 269404 : XMMReg4Float h1, s1;
565 269404 : XMM_RGB_to_HS(pInR + i + ELTS / 2, pInG + i + ELTS / 2,
566 269404 : pInB + i + ELTS / 2, zero, one, six, two_over_six,
567 : four_over_six, h1, s1);
568 :
569 269404 : XMMReg4Float v0, v1;
570 269404 : XMMReg4Float::Load8Val(pInGray + i, v0, v1);
571 :
572 269404 : const auto half = XMMReg4Float::Set1(0.5f);
573 269404 : const auto six_h0 = six * h0;
574 269404 : const auto idx0 = six_h0.truncate_to_int();
575 269404 : const auto f0 = six_h0 - idx0.cast_to_float();
576 269404 : const auto p0 = (v0 * (one - s0) + half).truncate_to_int();
577 269404 : const auto q0 = (v0 * (one - s0 * f0) + half).truncate_to_int();
578 269404 : const auto t0 = (v0 * (one - s0 * (one - f0)) + half).truncate_to_int();
579 :
580 269404 : const auto six_h1 = six * h1;
581 269404 : const auto idx1 = six_h1.truncate_to_int();
582 269404 : const auto f1 = six_h1 - idx1.cast_to_float();
583 269404 : const auto p1 = (v1 * (one - s1) + half).truncate_to_int();
584 269404 : const auto q1 = (v1 * (one - s1 * f1) + half).truncate_to_int();
585 269404 : const auto t1 = (v1 * (one - s1 * (one - f1)) + half).truncate_to_int();
586 :
587 269404 : const auto idx = XMMReg8Byte::Pack(idx0, idx1);
588 : const auto v =
589 269404 : XMMReg8Byte::Pack(v0.truncate_to_int(), v1.truncate_to_int());
590 269404 : const auto p = XMMReg8Byte::Pack(p0, p1);
591 269404 : const auto q = XMMReg8Byte::Pack(q0, q1);
592 269404 : const auto t = XMMReg8Byte::Pack(t0, t1);
593 :
594 269404 : const auto equalsTo0 = XMMReg8Byte::Equals(idx, XMMReg8Byte::Zero());
595 269404 : const auto one8Byte = XMMReg8Byte::Set1(1);
596 269404 : const auto equalsTo1 = XMMReg8Byte::Equals(idx, one8Byte);
597 269404 : const auto two8Byte = one8Byte + one8Byte;
598 269404 : const auto equalsTo2 = XMMReg8Byte::Equals(idx, two8Byte);
599 269404 : const auto four8Byte = two8Byte + two8Byte;
600 269404 : const auto equalsTo4 = XMMReg8Byte::Equals(idx, four8Byte);
601 269404 : const auto equalsTo3 = XMMReg8Byte::Equals(idx, four8Byte - one8Byte);
602 : // clang-format off
603 269404 : if (pOutR)
604 : {
605 : const auto out_r =
606 : XMMReg8Byte::Ternary(equalsTo0, v,
607 134702 : XMMReg8Byte::Ternary(equalsTo1, q,
608 134702 : XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo2, equalsTo3), p,
609 269404 : XMMReg8Byte::Ternary(equalsTo4, t, v))));
610 134702 : out_r.Store8Val(pOutR + i);
611 : }
612 269404 : if (pOutG)
613 : {
614 : const auto out_g =
615 : XMMReg8Byte::Ternary(equalsTo0, t,
616 118318 : XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo1, equalsTo2), v,
617 236636 : XMMReg8Byte::Ternary(equalsTo3, q, p)));
618 118318 : out_g.Store8Val(pOutG + i);
619 : }
620 269404 : if (pOutB)
621 : {
622 : const auto out_b =
623 118318 : XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo0, equalsTo1), p,
624 118318 : XMMReg8Byte::Ternary(equalsTo2, t,
625 118318 : XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo3, equalsTo4),
626 118318 : v, q)));
627 118318 : out_b.Store8Val(pOutB + i);
628 : }
629 : // clang-format on
630 : }
631 : #endif
632 :
633 2524 : for (; i < nCount; ++i)
634 : {
635 : float h, s;
636 1829 : rgb_to_hs(pInR[i], pInG[i], pInB[i], &h, &s);
637 5001 : hsv_to_rgb(h, s, pInGray[i], pOutR ? pOutR + i : nullptr,
638 3172 : pOutG ? pOutG + i : nullptr, pOutB ? pOutB + i : nullptr);
639 : }
640 695 : }
641 :
642 : /************************************************************************/
643 : /* BlendBand */
644 : /************************************************************************/
645 :
646 : class BlendBand final : public GDALRasterBand
647 : {
648 : public:
649 542 : BlendBand(BlendDataset &oBlendDataset, int nBandIn)
650 542 : : m_oBlendDataset(oBlendDataset)
651 : {
652 542 : nBand = nBandIn;
653 542 : nRasterXSize = oBlendDataset.GetRasterXSize();
654 542 : nRasterYSize = oBlendDataset.GetRasterYSize();
655 542 : oBlendDataset.m_oColorDS.GetRasterBand(1)->GetBlockSize(&nBlockXSize,
656 : &nBlockYSize);
657 542 : eDataType = GDT_UInt8;
658 542 : }
659 :
660 10 : GDALColorInterp GetColorInterpretation() override
661 : {
662 10 : if (m_oBlendDataset.GetRasterCount() <= 2 && nBand == 1)
663 0 : return GCI_GrayIndex;
664 10 : else if (m_oBlendDataset.GetRasterCount() == 2 || nBand == 4)
665 0 : return GCI_AlphaBand;
666 : else
667 10 : return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
668 : }
669 :
670 18 : int GetOverviewCount() override
671 : {
672 18 : return static_cast<int>(m_oBlendDataset.m_apoOverviews.size());
673 : }
674 :
675 14 : GDALRasterBand *GetOverview(int idx) override
676 : {
677 13 : return idx >= 0 && idx < GetOverviewCount()
678 27 : ? m_oBlendDataset.m_apoOverviews[idx]->GetRasterBand(nBand)
679 14 : : nullptr;
680 : }
681 :
682 : protected:
683 71 : CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override
684 : {
685 71 : int nReqXSize = 0;
686 71 : int nReqYSize = 0;
687 71 : GetActualBlockSize(nBlockXOff, nBlockYOff, &nReqXSize, &nReqYSize);
688 142 : return RasterIO(GF_Read, nBlockXOff * nBlockXSize,
689 71 : nBlockYOff * nBlockYSize, nReqXSize, nReqYSize, pData,
690 71 : nReqXSize, nReqYSize, GDT_UInt8, 1, nBlockXSize,
691 142 : nullptr);
692 : }
693 :
694 : CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
695 : int nYSize, void *pData, int nBufXSize, int nBufYSize,
696 : GDALDataType eBufType, GSpacing nPixelSpace,
697 : GSpacing nLineSpace,
698 : GDALRasterIOExtraArg *psExtraArg) override;
699 :
700 : private:
701 : BlendDataset &m_oBlendDataset;
702 : };
703 :
704 : /************************************************************************/
705 : /* BlendDataset::BlendDataset() */
706 : /************************************************************************/
707 :
708 163 : BlendDataset::BlendDataset(GDALDataset &oColorDS, GDALDataset &oOverlayDS,
709 : const CompositionMode eOperator,
710 163 : int nOpacity255Scale, bool bSwappedOpacity)
711 : : m_oColorDS(oColorDS), m_oOverlayDS(oOverlayDS), m_operator(eOperator),
712 163 : m_opacity255Scale(nOpacity255Scale), m_bSwappedOpacity(bSwappedOpacity)
713 : {
714 163 : m_oColorDS.Reference();
715 163 : m_oOverlayDS.Reference();
716 :
717 163 : CPLAssert(oColorDS.GetRasterXSize() == oOverlayDS.GetRasterXSize());
718 163 : CPLAssert(oColorDS.GetRasterYSize() == oOverlayDS.GetRasterYSize());
719 163 : nRasterXSize = oColorDS.GetRasterXSize();
720 163 : nRasterYSize = oColorDS.GetRasterYSize();
721 163 : const int nOvrCount = oOverlayDS.GetRasterBand(1)->GetOverviewCount();
722 163 : bool bCanCreateOvr = true;
723 705 : for (int iBand = 1; iBand <= oColorDS.GetRasterCount(); ++iBand)
724 : {
725 542 : SetBand(iBand, std::make_unique<BlendBand>(*this, iBand));
726 542 : bCanCreateOvr =
727 542 : bCanCreateOvr &&
728 542 : (iBand > oColorDS.GetRasterCount() ||
729 1626 : oColorDS.GetRasterBand(iBand)->GetOverviewCount() == nOvrCount) &&
730 542 : (iBand > oOverlayDS.GetRasterCount() ||
731 449 : oOverlayDS.GetRasterBand(iBand)->GetOverviewCount() == nOvrCount);
732 : const int nColorBandxIdx =
733 542 : iBand <= oColorDS.GetRasterCount() ? iBand : 1;
734 : const int nOverlayBandIdx =
735 542 : iBand <= oOverlayDS.GetRasterCount() ? iBand : 1;
736 562 : for (int iOvr = 0; iOvr < nOvrCount && bCanCreateOvr; ++iOvr)
737 : {
738 : const auto poColorOvrBand =
739 20 : oColorDS.GetRasterBand(nColorBandxIdx)->GetOverview(iOvr);
740 : const auto poGSOvrBand =
741 20 : oOverlayDS.GetRasterBand(nOverlayBandIdx)->GetOverview(iOvr);
742 20 : bCanCreateOvr =
743 20 : poColorOvrBand->GetDataset() != &oColorDS &&
744 20 : poColorOvrBand->GetDataset() == oColorDS.GetRasterBand(1)
745 20 : ->GetOverview(iOvr)
746 20 : ->GetDataset() &&
747 20 : poGSOvrBand->GetDataset() != &oOverlayDS &&
748 20 : poGSOvrBand->GetDataset() == oOverlayDS.GetRasterBand(1)
749 20 : ->GetOverview(iOvr)
750 20 : ->GetDataset() &&
751 60 : poColorOvrBand->GetXSize() == poGSOvrBand->GetXSize() &&
752 20 : poColorOvrBand->GetYSize() == poGSOvrBand->GetYSize();
753 : }
754 : }
755 :
756 163 : SetDescription(CPLSPrintf("Blend %s width %s", m_oColorDS.GetDescription(),
757 163 : m_oOverlayDS.GetDescription()));
758 163 : if (nBands > 1)
759 : {
760 150 : SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
761 : }
762 :
763 163 : if (bCanCreateOvr)
764 : {
765 168 : for (int iOvr = 0; iOvr < nOvrCount; ++iOvr)
766 : {
767 5 : m_apoOverviews.push_back(std::make_unique<BlendDataset>(
768 5 : *(oColorDS.GetRasterBand(1)->GetOverview(iOvr)->GetDataset()),
769 5 : *(oOverlayDS.GetRasterBand(1)->GetOverview(iOvr)->GetDataset()),
770 5 : m_operator, nOpacity255Scale, bSwappedOpacity));
771 : }
772 : }
773 163 : }
774 :
775 : /************************************************************************/
776 : /* ~BlendDataset::BlendDataset() */
777 : /************************************************************************/
778 :
779 326 : BlendDataset::~BlendDataset()
780 : {
781 163 : m_oColorDS.ReleaseRef();
782 163 : m_oOverlayDS.ReleaseRef();
783 326 : }
784 :
785 : /************************************************************************/
786 : /* BlendDataset::AcquireSourcePixels() */
787 : /************************************************************************/
788 :
789 1181 : bool BlendDataset::AcquireSourcePixels(int nXOff, int nYOff, int nXSize,
790 : int nYSize, int nBufXSize, int nBufYSize,
791 : GDALRasterIOExtraArg *psExtraArg)
792 : {
793 1181 : if (nXOff == m_nCachedXOff && nYOff == m_nCachedYOff &&
794 464 : nXSize == m_nCachedXSize && nYSize == m_nCachedYSize &&
795 305 : nBufXSize == m_nCachedBufXSize && nBufYSize == m_nCachedBufYSize &&
796 305 : psExtraArg->eResampleAlg == m_sCachedExtraArg.eResampleAlg &&
797 305 : psExtraArg->bFloatingPointWindowValidity ==
798 305 : m_sCachedExtraArg.bFloatingPointWindowValidity &&
799 305 : (!psExtraArg->bFloatingPointWindowValidity ||
800 0 : (psExtraArg->dfXOff == m_sCachedExtraArg.dfXOff &&
801 0 : psExtraArg->dfYOff == m_sCachedExtraArg.dfYOff &&
802 0 : psExtraArg->dfXSize == m_sCachedExtraArg.dfXSize &&
803 0 : psExtraArg->dfYSize == m_sCachedExtraArg.dfYSize)))
804 : {
805 305 : return !m_abyBuffer.empty();
806 : }
807 :
808 876 : const int nColorCount = m_oColorDS.GetRasterCount();
809 876 : const int nOverlayCount = m_oOverlayDS.GetRasterCount();
810 876 : const int nCompsInBuffer = nColorCount + nOverlayCount;
811 876 : assert(nCompsInBuffer > 0);
812 :
813 1752 : if (static_cast<size_t>(nBufXSize) >
814 876 : std::numeric_limits<size_t>::max() / nBufYSize / nCompsInBuffer)
815 : {
816 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
817 : "Out of memory allocating temporary buffer");
818 0 : m_abyBuffer.clear();
819 0 : m_ioError = true;
820 0 : return false;
821 : }
822 :
823 876 : const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
824 : try
825 : {
826 876 : if (m_abyBuffer.size() < nPixelCount * nCompsInBuffer)
827 158 : m_abyBuffer.resize(nPixelCount * nCompsInBuffer);
828 : }
829 4 : catch (const std::exception &)
830 : {
831 4 : CPLError(CE_Failure, CPLE_OutOfMemory,
832 : "Out of memory allocating temporary buffer");
833 4 : m_abyBuffer.clear();
834 4 : m_ioError = true;
835 4 : return false;
836 : }
837 :
838 : const bool bOK =
839 872 : (m_oColorDS.RasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize,
840 872 : m_abyBuffer.data(), nBufXSize, nBufYSize,
841 : GDT_UInt8, nColorCount, nullptr, 1, nBufXSize,
842 : static_cast<GSpacing>(nPixelCount),
843 1742 : psExtraArg) == CE_None &&
844 870 : m_oOverlayDS.RasterIO(
845 : GF_Read, nXOff, nYOff, nXSize, nYSize,
846 870 : m_abyBuffer.data() + nPixelCount * nColorCount, nBufXSize,
847 : nBufYSize, GDT_UInt8, nOverlayCount, nullptr, 1, nBufXSize,
848 872 : static_cast<GSpacing>(nPixelCount), psExtraArg) == CE_None);
849 872 : if (bOK)
850 : {
851 870 : m_nCachedXOff = nXOff;
852 870 : m_nCachedYOff = nYOff;
853 870 : m_nCachedXSize = nXSize;
854 870 : m_nCachedYSize = nYSize;
855 870 : m_nCachedBufXSize = nBufXSize;
856 870 : m_nCachedBufYSize = nBufYSize;
857 870 : m_sCachedExtraArg = *psExtraArg;
858 : }
859 : else
860 : {
861 2 : m_abyBuffer.clear();
862 2 : m_ioError = true;
863 : }
864 872 : return bOK;
865 : }
866 :
867 : /************************************************************************/
868 : /* gTabInvDstA */
869 : /************************************************************************/
870 :
871 : constexpr int SHIFT_DIV_DSTA = 8;
872 :
873 : // Table of (255 * 256 + k/2) / k values for k in [0,255]
874 : constexpr auto gTabInvDstA = []()
875 : {
876 : std::array<uint16_t, 256> arr{};
877 :
878 : arr[0] = 0;
879 : for (int k = 1; k <= 255; ++k)
880 : {
881 : arr[k] = static_cast<uint16_t>(((255 << SHIFT_DIV_DSTA) + (k / 2)) / k);
882 : }
883 :
884 : return arr;
885 : }();
886 :
887 : /************************************************************************/
888 : /* BlendMultiply_Generic */
889 : /************************************************************************/
890 :
891 328 : static void BlendMultiply_Generic(
892 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
893 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
894 : const GByte *CPL_RESTRICT pabyOverlayR,
895 : const GByte *CPL_RESTRICT pabyOverlayG,
896 : const GByte *CPL_RESTRICT pabyOverlayB,
897 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
898 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
899 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
900 : {
901 :
902 : // Generic formulas from Mapserver
903 : // Dca' = Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
904 : // Da' = Sa + Da - Sa.Da
905 :
906 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
907 : // TODO: optimize mathematically to avoid redundant computations
908 :
909 328 : GSpacing nDstOffset = 0;
910 :
911 656 : for (; i < N; ++i)
912 : {
913 :
914 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
915 : GByte nR, nG, nB, nA;
916 : GByte nFinalAlpha;
917 328 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
918 : nA, nOverlayA, nFinalAlpha);
919 328 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
920 328 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
921 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
922 :
923 : // Result
924 840 : auto processComponent = [&nFinalAlpha](GByte C, GByte A, GByte OverlayC,
925 840 : GByte OverlayA) -> GByte
926 : {
927 3360 : return DivScale255((MulScale255(C, OverlayC) +
928 1680 : MulScale255(C, 255 - OverlayA) +
929 840 : MulScale255(OverlayC, 255 - A)),
930 1680 : nFinalAlpha);
931 328 : };
932 :
933 328 : pabyDst[nDstOffset] = processComponent(nR, nA, nOverlayR, nOverlayA);
934 :
935 : // Grayscale with alpha
936 328 : if (nOutputBands == 2)
937 : {
938 48 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
939 : }
940 : else
941 : {
942 : // RBG and RGBA
943 280 : if (nOutputBands >= 3)
944 : {
945 512 : pabyDst[nDstOffset + nBandSpace] =
946 256 : processComponent(nG, nA, nOverlayG, nOverlayA);
947 512 : pabyDst[nDstOffset + 2 * nBandSpace] =
948 256 : processComponent(nB, nA, nOverlayB, nOverlayA);
949 : }
950 :
951 : // RGBA
952 280 : if (nOutputBands == 4)
953 : {
954 256 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
955 : }
956 : }
957 328 : nDstOffset += nPixelSpace;
958 : }
959 328 : }
960 :
961 : /************************************************************************/
962 : /* BlendScreen_Generic */
963 : /************************************************************************/
964 :
965 80 : static void BlendScreen_Generic(
966 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
967 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
968 : const GByte *CPL_RESTRICT pabyOverlayR,
969 : const GByte *CPL_RESTRICT pabyOverlayG,
970 : const GByte *CPL_RESTRICT pabyOverlayB,
971 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
972 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
973 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
974 : {
975 :
976 : // Generic formulas from Mapserver
977 : // Dca' = Sca + Dca - Sca.Dca
978 : // Da' = Sa + Da - Sa.Da
979 :
980 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
981 : // TODO: optimize mathematically to avoid redundant computations
982 :
983 80 : GSpacing nDstOffset = 0;
984 :
985 160 : for (; i < N; ++i)
986 : {
987 :
988 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
989 : GByte nR, nG, nB, nA;
990 : GByte nFinalAlpha;
991 80 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
992 : nA, nOverlayA, nFinalAlpha);
993 80 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
994 80 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
995 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
996 :
997 : // Result
998 :
999 240 : auto processComponent = [&nFinalAlpha](GByte C, GByte OverlayC) -> GByte
1000 : {
1001 240 : return DivScale255(C + OverlayC - MulScale255(C, OverlayC),
1002 480 : nFinalAlpha);
1003 80 : };
1004 :
1005 80 : pabyDst[nDstOffset] = processComponent(nR, nOverlayR);
1006 :
1007 : // Grayscale with alpha
1008 80 : if (nOutputBands == 2)
1009 : {
1010 0 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
1011 : }
1012 : else
1013 : {
1014 : // RBG and RGBA
1015 80 : if (nOutputBands >= 3)
1016 : {
1017 160 : pabyDst[nDstOffset + nBandSpace] =
1018 80 : processComponent(nG, nOverlayG);
1019 160 : pabyDst[nDstOffset + 2 * nBandSpace] =
1020 80 : processComponent(nB, nOverlayB);
1021 : }
1022 :
1023 : // RGBA
1024 80 : if (nOutputBands == 4)
1025 : {
1026 80 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
1027 : }
1028 : }
1029 80 : nDstOffset += nPixelSpace;
1030 : }
1031 80 : }
1032 :
1033 : /************************************************************************/
1034 : /* BlendOverlay_Generic */
1035 : /************************************************************************/
1036 :
1037 72 : static void BlendOverlay_Generic(
1038 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
1039 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
1040 : const GByte *CPL_RESTRICT pabyOverlayR,
1041 : const GByte *CPL_RESTRICT pabyOverlayG,
1042 : const GByte *CPL_RESTRICT pabyOverlayB,
1043 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
1044 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
1045 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
1046 : {
1047 :
1048 : // Generic formulas from Mapserver
1049 : // Where "D" is destination, "S" is source (overlay)
1050 : // if 2.Dca < Da
1051 : // Dca' = 2.Sca.Dca + Sca.(1 - Da) + Dca.(1 - Sa)
1052 : // otherwise
1053 : // Dca' = Sa.Da - 2.(Da - Dca).(Sa - Sca) + Sca.(1 - Da) + Dca.(1 - Sa)
1054 : //
1055 : // Da' = Sa + Da - Sa.Da
1056 :
1057 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
1058 : // TODO: optimize mathematically to avoid redundant computations
1059 :
1060 72 : GSpacing nDstOffset = 0;
1061 :
1062 144 : for (; i < N; ++i)
1063 : {
1064 :
1065 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
1066 : GByte nR, nG, nB, nA;
1067 : GByte nFinalAlpha;
1068 72 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
1069 : nA, nOverlayA, nFinalAlpha);
1070 72 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
1071 72 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
1072 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
1073 :
1074 : // Sa.Da
1075 72 : const GByte nAlphaMul{MulScale255(nOverlayA, nA)};
1076 :
1077 112 : auto processComponent_lessThan = [&nFinalAlpha](GByte C, GByte A,
1078 : GByte OverlayC,
1079 112 : GByte OverlayA) -> GByte
1080 : {
1081 560 : return DivScale255(2 * MulScale255(C, OverlayC) +
1082 224 : MulScale255(C, 255 - OverlayA) +
1083 112 : MulScale255(OverlayC, 255 - A),
1084 224 : nFinalAlpha);
1085 72 : };
1086 :
1087 : auto processComponent_greaterEqual =
1088 104 : [&nFinalAlpha, &nAlphaMul](GByte C, GByte A, GByte OverlayC,
1089 104 : GByte OverlayA) -> GByte
1090 : {
1091 416 : return DivScale255(nAlphaMul -
1092 208 : 2 * MulScale255(A - C, OverlayA - OverlayC) +
1093 208 : MulScale255(C, 255 - OverlayA) +
1094 104 : MulScale255(OverlayC, 255 - A),
1095 208 : nFinalAlpha);
1096 72 : };
1097 :
1098 72 : if (2 * nR < nA)
1099 : {
1100 64 : pabyDst[nDstOffset] =
1101 32 : processComponent_lessThan(nR, nA, nOverlayR, nOverlayA);
1102 : }
1103 : else
1104 : {
1105 80 : pabyDst[nDstOffset] =
1106 40 : processComponent_greaterEqual(nR, nA, nOverlayR, nOverlayA);
1107 : }
1108 :
1109 : // Grayscale with alpha
1110 72 : if (nOutputBands == 2)
1111 : {
1112 0 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
1113 : }
1114 : else
1115 : {
1116 : // RBG and RGBA
1117 72 : if (nOutputBands >= 3)
1118 : {
1119 :
1120 72 : if (2 * nG < nA)
1121 : {
1122 80 : pabyDst[nDstOffset + nBandSpace] =
1123 40 : processComponent_lessThan(nG, nA, nOverlayG, nOverlayA);
1124 : }
1125 : else
1126 : {
1127 64 : pabyDst[nDstOffset + nBandSpace] =
1128 32 : processComponent_greaterEqual(nG, nA, nOverlayG,
1129 : nOverlayA);
1130 : }
1131 :
1132 72 : if (2 * nB < nA)
1133 : {
1134 80 : pabyDst[nDstOffset + 2 * nBandSpace] =
1135 40 : processComponent_lessThan(nB, nA, nOverlayB, nOverlayA);
1136 : }
1137 : else
1138 : {
1139 64 : pabyDst[nDstOffset + 2 * nBandSpace] =
1140 32 : processComponent_greaterEqual(nB, nA, nOverlayB,
1141 : nOverlayA);
1142 : }
1143 : }
1144 :
1145 : // RGBA
1146 72 : if (nOutputBands == 4)
1147 : {
1148 72 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
1149 : }
1150 : }
1151 72 : nDstOffset += nPixelSpace;
1152 : }
1153 72 : }
1154 :
1155 : /************************************************************************/
1156 : /* BlendHardLight_Generic */
1157 : /************************************************************************/
1158 :
1159 16 : static void BlendHardLight_Generic(
1160 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
1161 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
1162 : const GByte *CPL_RESTRICT pabyOverlayR,
1163 : const GByte *CPL_RESTRICT pabyOverlayG,
1164 : const GByte *CPL_RESTRICT pabyOverlayB,
1165 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
1166 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
1167 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
1168 : {
1169 : // Hard Light is Overlay with roles of source and overlay swapped
1170 16 : BlendOverlay_Generic(pabyOverlayR, pabyOverlayG, pabyOverlayB, pabyOverlayA,
1171 : pabyR, pabyG, pabyB, pabyA, pabyDst, nPixelSpace,
1172 : nBandSpace, i, N, nOpacity, nOutputBands,
1173 16 : !bSwappedOpacity);
1174 16 : }
1175 :
1176 : /************************************************************************/
1177 : /* BlendDarken_Generic */
1178 : /************************************************************************/
1179 :
1180 88 : static void BlendDarken_Generic(
1181 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
1182 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
1183 : const GByte *CPL_RESTRICT pabyOverlayR,
1184 : const GByte *CPL_RESTRICT pabyOverlayG,
1185 : const GByte *CPL_RESTRICT pabyOverlayB,
1186 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
1187 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
1188 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
1189 : {
1190 : // Generic formulas from Mapserver
1191 : // Dca' = min(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
1192 : // Da' = Sa + Da - Sa.Da
1193 :
1194 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
1195 : // TODO: optimize mathematically to avoid redundant computations
1196 :
1197 88 : GSpacing nDstOffset = 0;
1198 :
1199 176 : for (; i < N; ++i)
1200 : {
1201 :
1202 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
1203 : GByte nR, nG, nB, nA;
1204 : GByte nFinalAlpha;
1205 88 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
1206 : nA, nOverlayA, nFinalAlpha);
1207 88 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
1208 88 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
1209 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
1210 :
1211 : // Result
1212 :
1213 216 : auto processComponent = [&nFinalAlpha](GByte C, GByte A, GByte OverlayC,
1214 216 : GByte OverlayA) -> GByte
1215 : {
1216 648 : return DivScale255(
1217 432 : std::min(MulScale255(OverlayC, A), MulScale255(C, OverlayA)) +
1218 432 : MulScale255(C, 255 - OverlayA) +
1219 216 : MulScale255(OverlayC, 255 - A),
1220 648 : nFinalAlpha);
1221 88 : };
1222 :
1223 88 : pabyDst[nDstOffset] = processComponent(nR, nA, nOverlayR, nOverlayA);
1224 :
1225 : // Grayscale with alpha
1226 88 : if (nOutputBands == 2)
1227 : {
1228 24 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
1229 : }
1230 : else
1231 : {
1232 : // RBG and RGBA
1233 64 : if (nOutputBands >= 3)
1234 : {
1235 128 : pabyDst[nDstOffset + nBandSpace] =
1236 64 : processComponent(nG, nA, nOverlayG, nOverlayA);
1237 128 : pabyDst[nDstOffset + 2 * nBandSpace] =
1238 64 : processComponent(nB, nA, nOverlayB, nOverlayA);
1239 : }
1240 :
1241 : // RGBA
1242 64 : if (nOutputBands == 4)
1243 : {
1244 64 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
1245 : }
1246 : }
1247 88 : nDstOffset += nPixelSpace;
1248 : }
1249 88 : }
1250 :
1251 : /************************************************************************/
1252 : /* BlendLighten_Generic */
1253 : /************************************************************************/
1254 :
1255 136 : static void BlendLighten_Generic(
1256 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
1257 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
1258 : const GByte *CPL_RESTRICT pabyOverlayR,
1259 : const GByte *CPL_RESTRICT pabyOverlayG,
1260 : const GByte *CPL_RESTRICT pabyOverlayB,
1261 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
1262 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
1263 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
1264 : {
1265 :
1266 : // Generic formulas from Mapserver
1267 : // Dca' = max(Sca.Da, Dca.Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
1268 : // Da' = Sa + Da - Sa.Da
1269 :
1270 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
1271 : // TODO: optimize mathematically to avoid redundant computations
1272 :
1273 136 : GSpacing nDstOffset = 0;
1274 :
1275 272 : for (; i < N; ++i)
1276 : {
1277 :
1278 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
1279 : GByte nR, nG, nB, nA;
1280 : GByte nFinalAlpha;
1281 136 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
1282 : nA, nOverlayA, nFinalAlpha);
1283 136 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
1284 136 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
1285 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
1286 :
1287 : // Result
1288 :
1289 360 : auto processComponent = [&nFinalAlpha](GByte C, GByte A, GByte OverlayC,
1290 360 : GByte OverlayA) -> GByte
1291 : {
1292 1080 : return DivScale255(
1293 720 : std::max(MulScale255(OverlayC, A), MulScale255(C, OverlayA)) +
1294 720 : MulScale255(C, 255 - OverlayA) +
1295 360 : MulScale255(OverlayC, 255 - A),
1296 1080 : nFinalAlpha);
1297 136 : };
1298 :
1299 136 : pabyDst[nDstOffset] = processComponent(nR, nA, nOverlayR, nOverlayA);
1300 :
1301 : // Grayscale with alpha
1302 136 : if (nOutputBands == 2)
1303 : {
1304 24 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
1305 : }
1306 : else
1307 : {
1308 : // RBG and RGBA
1309 112 : if (nOutputBands >= 3)
1310 : {
1311 224 : pabyDst[nDstOffset + nBandSpace] =
1312 112 : processComponent(nG, nA, nOverlayG, nOverlayA);
1313 224 : pabyDst[nDstOffset + 2 * nBandSpace] =
1314 112 : processComponent(nB, nA, nOverlayB, nOverlayA);
1315 : }
1316 :
1317 : // RGBA
1318 112 : if (nOutputBands == 4)
1319 : {
1320 112 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
1321 : }
1322 : }
1323 136 : nDstOffset += nPixelSpace;
1324 : }
1325 136 : }
1326 :
1327 : /************************************************************************/
1328 : /* BlendColorDodge_Generic */
1329 : /************************************************************************/
1330 :
1331 56 : static void BlendColorDodge_Generic(
1332 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
1333 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
1334 : const GByte *CPL_RESTRICT pabyOverlayR,
1335 : const GByte *CPL_RESTRICT pabyOverlayG,
1336 : const GByte *CPL_RESTRICT pabyOverlayB,
1337 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
1338 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
1339 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
1340 : {
1341 :
1342 : // Generic formulas from Mapserver
1343 : // if Sca.Da + Dca.Sa >= Sa.Da
1344 : // Dca' = Sa.Da + Sca.(1 - Da) + Dca.(1 - Sa)
1345 : // otherwise
1346 : // Dca' = Dca.Sa/(1-Sca/Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
1347 : //
1348 : // Da' = Sa + Da - Sa.Da
1349 :
1350 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
1351 : // TODO: optimize mathematically to avoid redundant computations
1352 :
1353 56 : GSpacing nDstOffset = 0;
1354 :
1355 112 : for (; i < N; ++i)
1356 : {
1357 :
1358 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
1359 : GByte nR, nG, nB, nA;
1360 : GByte nFinalAlpha;
1361 56 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
1362 : nA, nOverlayA, nFinalAlpha);
1363 56 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
1364 56 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
1365 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
1366 :
1367 : // Result
1368 :
1369 56 : const GByte alphaMul255{MulScale255(nOverlayA, nA)};
1370 :
1371 : // Dca' = Sa.Da + Sca.(1 - Da) + Dca.(1 - Sa)
1372 : auto processComponent_greaterEqual =
1373 104 : [&nFinalAlpha, &alphaMul255](GByte C, GByte A, GByte OverlayC,
1374 104 : GByte OverlayA) -> GByte
1375 : {
1376 104 : return DivScale255(alphaMul255 + MulScale255(C, 255 - OverlayA) +
1377 104 : MulScale255(OverlayC, 255 - A),
1378 208 : nFinalAlpha);
1379 56 : };
1380 :
1381 : // Dca.Sa/(1-Sca/Sa) + Sca.(1 - Da) + Dca.(1 - Sa)
1382 64 : auto processComponent_lessThan = [&nFinalAlpha](GByte C, GByte A,
1383 : GByte OverlayC,
1384 64 : GByte OverlayA) -> GByte
1385 : {
1386 192 : return DivScale255(
1387 64 : DivScale255(MulScale255(C, OverlayA),
1388 64 : 255 - DivScale255(OverlayC, OverlayA)) +
1389 128 : MulScale255(C, 255 - OverlayA) +
1390 64 : MulScale255(OverlayC, 255 - A),
1391 128 : nFinalAlpha);
1392 56 : };
1393 :
1394 168 : auto branchingCondition = [&alphaMul255](GByte C, GByte A,
1395 : GByte OverlayC,
1396 168 : GByte OverlayA) -> bool {
1397 168 : return MulScale255(OverlayC, A) + MulScale255(C, OverlayA) >=
1398 168 : alphaMul255;
1399 56 : };
1400 :
1401 56 : if (branchingCondition(nR, nA, nOverlayR, nOverlayA))
1402 : {
1403 64 : pabyDst[nDstOffset] =
1404 32 : processComponent_greaterEqual(nR, nA, nOverlayR, nOverlayA);
1405 : }
1406 : else
1407 : {
1408 48 : pabyDst[nDstOffset] =
1409 24 : processComponent_lessThan(nR, nA, nOverlayR, nOverlayA);
1410 : }
1411 :
1412 : // Grayscale with alpha
1413 56 : if (nOutputBands == 2)
1414 : {
1415 0 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
1416 : }
1417 : else
1418 : {
1419 : // RBG and RGBA
1420 56 : if (nOutputBands >= 3)
1421 : {
1422 56 : if (branchingCondition(nG, nA, nOverlayG, nOverlayA))
1423 : {
1424 64 : pabyDst[nDstOffset + nBandSpace] =
1425 32 : processComponent_greaterEqual(nG, nA, nOverlayG,
1426 : nOverlayA);
1427 : }
1428 : else
1429 : {
1430 48 : pabyDst[nDstOffset + nBandSpace] =
1431 24 : processComponent_lessThan(nG, nA, nOverlayG, nOverlayA);
1432 : }
1433 :
1434 56 : if (branchingCondition(nB, nA, nOverlayB, nOverlayA))
1435 : {
1436 80 : pabyDst[nDstOffset + 2 * nBandSpace] =
1437 40 : processComponent_greaterEqual(nB, nA, nOverlayB,
1438 : nOverlayA);
1439 : }
1440 : else
1441 : {
1442 32 : pabyDst[nDstOffset + 2 * nBandSpace] =
1443 16 : processComponent_lessThan(nB, nA, nOverlayB, nOverlayA);
1444 : }
1445 : }
1446 :
1447 : // RGBA
1448 56 : if (nOutputBands == 4)
1449 : {
1450 56 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
1451 : }
1452 : }
1453 56 : nDstOffset += nPixelSpace;
1454 : }
1455 56 : }
1456 :
1457 : /************************************************************************/
1458 : /* BlendColorBurn_Generic */
1459 : /************************************************************************/
1460 :
1461 64 : static void BlendColorBurn_Generic(
1462 : const GByte *CPL_RESTRICT pabyR, const GByte *CPL_RESTRICT pabyG,
1463 : const GByte *CPL_RESTRICT pabyB, const GByte *CPL_RESTRICT pabyA,
1464 : const GByte *CPL_RESTRICT pabyOverlayR,
1465 : const GByte *CPL_RESTRICT pabyOverlayG,
1466 : const GByte *CPL_RESTRICT pabyOverlayB,
1467 : const GByte *CPL_RESTRICT pabyOverlayA, GByte *CPL_RESTRICT pabyDst,
1468 : GSpacing nPixelSpace, GSpacing nBandSpace, size_t i, size_t N,
1469 : GByte nOpacity, int nOutputBands, bool bSwappedOpacity)
1470 : {
1471 :
1472 : // if Sca.Da + Dca.Sa <= Sa.Da
1473 : // Dca' = Sca.(1 - Da) + Dca.(1 - Sa)
1474 : // otherwise
1475 : // Dca' = Sa.(Sca.Da + Dca.Sa - Sa.Da)/Sca + Sca.(1 - Da) + Dca.(1 - Sa)
1476 : // Da' = Sa + Da - Sa.Da
1477 :
1478 : // TODO: optimize for the various cases (with/without alpha, grayscale/RGB)
1479 : // TODO: optimize mathematically to avoid redundant computations
1480 :
1481 64 : GSpacing nDstOffset = 0;
1482 :
1483 128 : for (; i < N; ++i)
1484 : {
1485 :
1486 : GByte nOverlayR, nOverlayG, nOverlayB, nOverlayA;
1487 : GByte nR, nG, nB, nA;
1488 : GByte nFinalAlpha;
1489 64 : ProcessAlphaChannels(i, pabyA, pabyOverlayA, nOpacity, bSwappedOpacity,
1490 : nA, nOverlayA, nFinalAlpha);
1491 64 : PremultiplyChannels(i, pabyR, pabyG, pabyB, nR, nG, nB, nA);
1492 64 : PremultiplyChannels(i, pabyOverlayR, pabyOverlayG, pabyOverlayB,
1493 : nOverlayR, nOverlayG, nOverlayB, nOverlayA);
1494 :
1495 : // Result
1496 :
1497 64 : const GByte alphaMul255{MulScale255(nOverlayA, nA)};
1498 :
1499 : auto processComponent_lessEqual =
1500 144 : [&nFinalAlpha](GByte C, GByte A, GByte OverlayC,
1501 144 : GByte OverlayA) -> GByte
1502 : {
1503 144 : return DivScale255(MulScale255(C, 255 - OverlayA) +
1504 144 : MulScale255(OverlayC, 255 - A),
1505 288 : nFinalAlpha);
1506 64 : };
1507 :
1508 : // The simplified formula below was tested and verified with the floating point equivalent [0..1] normalized version
1509 : auto processComponent_greater =
1510 48 : [&nFinalAlpha, &alphaMul255](GByte C, GByte A, GByte OverlayC,
1511 48 : GByte OverlayA) -> GByte
1512 : {
1513 48 : const GByte C_unpremultiplied = DivScale255(C, A);
1514 : const GByte Overlay_C_unpremultiplied =
1515 48 : DivScale255(OverlayC, OverlayA);
1516 144 : return DivScale255(
1517 48 : MulScale255(alphaMul255, (C_unpremultiplied +
1518 96 : Overlay_C_unpremultiplied - 255)) +
1519 96 : MulScale255(C, 255 - OverlayA) +
1520 48 : MulScale255(OverlayC, 255 - A),
1521 96 : nFinalAlpha);
1522 64 : };
1523 :
1524 192 : auto branchingCondition = [&alphaMul255](GByte C, GByte A,
1525 : GByte OverlayC,
1526 192 : GByte OverlayA) -> bool {
1527 192 : return MulScale255(OverlayC, A) + MulScale255(C, OverlayA) <=
1528 192 : alphaMul255;
1529 64 : };
1530 :
1531 64 : if (branchingCondition(nR, nA, nOverlayR, nOverlayA))
1532 : {
1533 96 : pabyDst[nDstOffset] =
1534 48 : processComponent_lessEqual(nR, nA, nOverlayR, nOverlayA);
1535 : }
1536 : else
1537 : {
1538 32 : pabyDst[nDstOffset] =
1539 16 : processComponent_greater(nR, nA, nOverlayR, nOverlayA);
1540 : }
1541 :
1542 : // Grayscale with alpha
1543 64 : if (nOutputBands == 2)
1544 : {
1545 0 : pabyDst[nDstOffset + nBandSpace] = nFinalAlpha;
1546 : }
1547 : else
1548 : {
1549 : // RBG and RGBA
1550 64 : if (nOutputBands >= 3)
1551 : {
1552 64 : if (branchingCondition(nG, nA, nOverlayG, nOverlayA))
1553 : {
1554 96 : pabyDst[nDstOffset + nBandSpace] =
1555 48 : processComponent_lessEqual(nG, nA, nOverlayG,
1556 : nOverlayA);
1557 : }
1558 : else
1559 : {
1560 32 : pabyDst[nDstOffset + nBandSpace] =
1561 16 : processComponent_greater(nG, nA, nOverlayG, nOverlayA);
1562 : }
1563 :
1564 64 : if (branchingCondition(nB, nA, nOverlayB, nOverlayA))
1565 : {
1566 96 : pabyDst[nDstOffset + 2 * nBandSpace] =
1567 48 : processComponent_lessEqual(nB, nA, nOverlayB,
1568 : nOverlayA);
1569 : }
1570 : else
1571 : {
1572 32 : pabyDst[nDstOffset + 2 * nBandSpace] =
1573 16 : processComponent_greater(nB, nA, nOverlayB, nOverlayA);
1574 : }
1575 : }
1576 :
1577 : // RGBA
1578 64 : if (nOutputBands == 4)
1579 : {
1580 64 : pabyDst[nDstOffset + 3 * nBandSpace] = nFinalAlpha;
1581 : }
1582 : }
1583 64 : nDstOffset += nPixelSpace;
1584 : }
1585 64 : }
1586 :
1587 : /************************************************************************/
1588 : /* BlendSrcOverRGBA_SSE2() */
1589 : /************************************************************************/
1590 :
1591 : #ifdef HAVE_SSE2
1592 : #if defined(__GNUC__)
1593 : __attribute__((noinline))
1594 : #endif
1595 : static int
1596 1 : BlendSrcOverRGBA_SSE2(const GByte *CPL_RESTRICT pabyR,
1597 : const GByte *CPL_RESTRICT pabyG,
1598 : const GByte *CPL_RESTRICT pabyB,
1599 : const GByte *CPL_RESTRICT pabyA,
1600 : const GByte *CPL_RESTRICT pabyOverlayR,
1601 : const GByte *CPL_RESTRICT pabyOverlayG,
1602 : const GByte *CPL_RESTRICT pabyOverlayB,
1603 : const GByte *CPL_RESTRICT pabyOverlayA,
1604 : GByte *CPL_RESTRICT pabyDst, GSpacing nBandSpace, int N,
1605 : GByte nOpacity)
1606 : {
1607 : // See scalar code after call to BlendSrcOverRGBA_SSE2() below for the
1608 : // non-obfuscated formulas...
1609 :
1610 0 : const auto load_and_unpack = [](const void *p)
1611 : {
1612 0 : const auto zero = _mm_setzero_si128();
1613 0 : auto overlayA = _mm_loadu_si128(reinterpret_cast<const __m128i *>(p));
1614 0 : return std::make_pair(_mm_unpacklo_epi8(overlayA, zero),
1615 0 : _mm_unpackhi_epi8(overlayA, zero));
1616 : };
1617 :
1618 0 : const auto pack_and_store = [](void *p, __m128i lo, __m128i hi) {
1619 0 : _mm_storeu_si128(reinterpret_cast<__m128i *>(p),
1620 : _mm_packus_epi16(lo, hi));
1621 0 : };
1622 :
1623 0 : const auto mul16bit_8bit_result = [](__m128i a, __m128i b)
1624 : {
1625 0 : const auto r255 = _mm_set1_epi16(255);
1626 0 : return _mm_srli_epi16(_mm_add_epi16(_mm_mullo_epi16(a, b), r255), 8);
1627 : };
1628 :
1629 2 : const auto opacity = _mm_set1_epi16(nOpacity);
1630 1 : const auto r255 = _mm_set1_epi16(255);
1631 : const int16_t *tabInvDstASigned =
1632 1 : reinterpret_cast<const int16_t *>(gTabInvDstA.data());
1633 1 : constexpr int REG_WIDTH = static_cast<int>(sizeof(opacity));
1634 :
1635 1 : int i = 0;
1636 1 : for (; i <= N - REG_WIDTH; i += REG_WIDTH)
1637 : {
1638 0 : auto [overlayA_lo, overlayA_hi] = load_and_unpack(pabyOverlayA + i);
1639 0 : auto [srcA_lo, srcA_hi] = load_and_unpack(pabyA + i);
1640 0 : overlayA_lo = mul16bit_8bit_result(overlayA_lo, opacity);
1641 0 : overlayA_hi = mul16bit_8bit_result(overlayA_hi, opacity);
1642 : auto srcAMul255MinusOverlayA_lo =
1643 0 : mul16bit_8bit_result(srcA_lo, _mm_sub_epi16(r255, overlayA_lo));
1644 : auto srcAMul255MinusOverlayA_hi =
1645 0 : mul16bit_8bit_result(srcA_hi, _mm_sub_epi16(r255, overlayA_hi));
1646 0 : auto dstA_lo = _mm_add_epi16(overlayA_lo, srcAMul255MinusOverlayA_lo);
1647 0 : auto dstA_hi = _mm_add_epi16(overlayA_hi, srcAMul255MinusOverlayA_hi);
1648 :
1649 : // This would be the equivalent of a "_mm_i16gather_epi16" operation
1650 : // which does not exist...
1651 : // invDstA_{i} = [tabInvDstASigned[dstA_{i}] for i in range(8)]
1652 0 : auto invDstA_lo = _mm_undefined_si128();
1653 0 : auto invDstA_hi = _mm_undefined_si128();
1654 : #define SET_INVDSTA(k) \
1655 : do \
1656 : { \
1657 : const int idxLo = _mm_extract_epi16(dstA_lo, k); \
1658 : const int idxHi = _mm_extract_epi16(dstA_hi, k); \
1659 : invDstA_lo = _mm_insert_epi16(invDstA_lo, tabInvDstASigned[idxLo], k); \
1660 : invDstA_hi = _mm_insert_epi16(invDstA_hi, tabInvDstASigned[idxHi], k); \
1661 : } while (0)
1662 :
1663 0 : SET_INVDSTA(0);
1664 0 : SET_INVDSTA(1);
1665 0 : SET_INVDSTA(2);
1666 0 : SET_INVDSTA(3);
1667 0 : SET_INVDSTA(4);
1668 0 : SET_INVDSTA(5);
1669 0 : SET_INVDSTA(6);
1670 0 : SET_INVDSTA(7);
1671 :
1672 0 : pack_and_store(pabyDst + i + 3 * nBandSpace, dstA_lo, dstA_hi);
1673 :
1674 : #define PROCESS_COMPONENT(pabySrc, pabyOverlay, iBand) \
1675 : do \
1676 : { \
1677 : auto [src_lo, src_hi] = load_and_unpack((pabySrc) + i); \
1678 : auto [overlay_lo, overlay_hi] = load_and_unpack((pabyOverlay) + i); \
1679 : auto dst_lo = _mm_srli_epi16( \
1680 : _mm_add_epi16( \
1681 : _mm_add_epi16( \
1682 : _mm_mullo_epi16(overlay_lo, overlayA_lo), \
1683 : _mm_mullo_epi16(src_lo, srcAMul255MinusOverlayA_lo)), \
1684 : r255), \
1685 : 8); \
1686 : auto dst_hi = _mm_srli_epi16( \
1687 : _mm_add_epi16( \
1688 : _mm_add_epi16( \
1689 : _mm_mullo_epi16(overlay_hi, overlayA_hi), \
1690 : _mm_mullo_epi16(src_hi, srcAMul255MinusOverlayA_hi)), \
1691 : r255), \
1692 : 8); \
1693 : dst_lo = mul16bit_8bit_result(dst_lo, invDstA_lo); \
1694 : dst_hi = mul16bit_8bit_result(dst_hi, invDstA_hi); \
1695 : pack_and_store(pabyDst + i + (iBand)*nBandSpace, dst_lo, dst_hi); \
1696 : } while (0)
1697 :
1698 0 : PROCESS_COMPONENT(pabyR, pabyOverlayR, 0);
1699 0 : PROCESS_COMPONENT(pabyG, pabyOverlayG, 1);
1700 0 : PROCESS_COMPONENT(pabyB, pabyOverlayB, 2);
1701 : }
1702 1 : return i;
1703 : }
1704 : #endif
1705 :
1706 : template <bool bPixelSpaceIsOne>
1707 : #if defined(__GNUC__)
1708 : __attribute__((noinline))
1709 : #endif
1710 : static void
1711 1 : BlendSrcOverRGBA_Generic(const GByte *CPL_RESTRICT pabyR,
1712 : const GByte *CPL_RESTRICT pabyG,
1713 : const GByte *CPL_RESTRICT pabyB,
1714 : const GByte *CPL_RESTRICT pabyA,
1715 : const GByte *CPL_RESTRICT pabyOverlayR,
1716 : const GByte *CPL_RESTRICT pabyOverlayG,
1717 : const GByte *CPL_RESTRICT pabyOverlayB,
1718 : const GByte *CPL_RESTRICT pabyOverlayA,
1719 : GByte *CPL_RESTRICT pabyDst,
1720 : [[maybe_unused]] GSpacing nPixelSpace,
1721 : GSpacing nBandSpace, int i, int N, GByte nOpacity)
1722 : {
1723 : #if !(defined(HAVE_SSE2) && defined(__GNUC__))
1724 : if constexpr (!bPixelSpaceIsOne)
1725 : {
1726 : assert(nPixelSpace != 1);
1727 : }
1728 : #endif
1729 1 : [[maybe_unused]] GSpacing nDstOffset = 0;
1730 : #if defined(HAVE_SSE2) && defined(__clang__) && !defined(__INTEL_CLANG_COMPILER)
1731 : // No need to vectorize for SSE2 and clang
1732 : #pragma clang loop vectorize(disable)
1733 : #endif
1734 2 : for (; i < N; ++i)
1735 : {
1736 1 : const GByte nOverlayR = pabyOverlayR[i];
1737 1 : const GByte nOverlayG = pabyOverlayG[i];
1738 1 : const GByte nOverlayB = pabyOverlayB[i];
1739 1 : const GByte nOverlayA =
1740 1 : static_cast<GByte>((pabyOverlayA[i] * nOpacity + 255) / 256);
1741 1 : const GByte nR = pabyR[i];
1742 1 : const GByte nG = pabyG[i];
1743 1 : const GByte nB = pabyB[i];
1744 1 : const GByte nA = pabyA[i];
1745 1 : const GByte nSrcAMul255MinusOverlayA =
1746 1 : static_cast<GByte>((nA * (255 - nOverlayA) + 255) / 256);
1747 1 : const GByte nDstA =
1748 : static_cast<GByte>(nOverlayA + nSrcAMul255MinusOverlayA);
1749 1 : GByte nDstR = static_cast<GByte>(
1750 1 : (nOverlayR * nOverlayA + nR * nSrcAMul255MinusOverlayA + 255) /
1751 : 256);
1752 1 : GByte nDstG = static_cast<GByte>(
1753 1 : (nOverlayG * nOverlayA + nG * nSrcAMul255MinusOverlayA + 255) /
1754 : 256);
1755 1 : GByte nDstB = static_cast<GByte>(
1756 1 : (nOverlayB * nOverlayA + nB * nSrcAMul255MinusOverlayA + 255) /
1757 : 256);
1758 : // nInvDstA = (255 << SHIFT_DIV_DSTA) / nDstA;
1759 1 : const uint16_t nInvDstA = gTabInvDstA[nDstA];
1760 1 : constexpr unsigned ROUND_OFFSET_DIV_DSTA = ((1 << SHIFT_DIV_DSTA) - 1);
1761 1 : nDstR = static_cast<GByte>((nDstR * nInvDstA + ROUND_OFFSET_DIV_DSTA) >>
1762 : SHIFT_DIV_DSTA);
1763 1 : nDstG = static_cast<GByte>((nDstG * nInvDstA + ROUND_OFFSET_DIV_DSTA) >>
1764 : SHIFT_DIV_DSTA);
1765 1 : nDstB = static_cast<GByte>((nDstB * nInvDstA + ROUND_OFFSET_DIV_DSTA) >>
1766 : SHIFT_DIV_DSTA);
1767 : if constexpr (bPixelSpaceIsOne)
1768 : {
1769 : pabyDst[i] = nDstR;
1770 : pabyDst[i + nBandSpace] = nDstG;
1771 : pabyDst[i + 2 * nBandSpace] = nDstB;
1772 : pabyDst[i + 3 * nBandSpace] = nDstA;
1773 : }
1774 : else
1775 : {
1776 1 : pabyDst[nDstOffset] = nDstR;
1777 1 : pabyDst[nDstOffset + nBandSpace] = nDstG;
1778 1 : pabyDst[nDstOffset + 2 * nBandSpace] = nDstB;
1779 1 : pabyDst[nDstOffset + 3 * nBandSpace] = nDstA;
1780 1 : nDstOffset += nPixelSpace;
1781 : }
1782 : }
1783 1 : }
1784 :
1785 : /************************************************************************/
1786 : /* BlendDataset::IRasterIO() */
1787 : /************************************************************************/
1788 :
1789 718 : CPLErr BlendDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1790 : int nXSize, int nYSize, void *pData,
1791 : int nBufXSize, int nBufYSize,
1792 : GDALDataType eBufType, int nBandCount,
1793 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1794 : GSpacing nLineSpace, GSpacing nBandSpace,
1795 : GDALRasterIOExtraArg *psExtraArg)
1796 : {
1797 : // Try to pass the request to the most appropriate overview dataset.
1798 718 : if (nBufXSize < nXSize && nBufYSize < nYSize)
1799 : {
1800 2 : int bTried = FALSE;
1801 2 : const CPLErr eErr = TryOverviewRasterIO(
1802 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1803 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
1804 : nBandSpace, psExtraArg, &bTried);
1805 2 : if (bTried)
1806 2 : return eErr;
1807 : }
1808 :
1809 716 : GByte *const CPL_RESTRICT pabyDst = static_cast<GByte *>(pData);
1810 716 : const int nColorCount = m_oColorDS.GetRasterCount();
1811 716 : const int nOverlayCount = m_oOverlayDS.GetRasterCount();
1812 :
1813 : /************************************************************************/
1814 : /* HSV_VALUE */
1815 : /*************************************************************************/
1816 286 : if (nOverlayCount == 1 && m_opacity255Scale == 255 &&
1817 279 : m_operator == CompositionMode::HSV_VALUE && eRWFlag == GF_Read &&
1818 204 : eBufType == GDT_UInt8 && nBandCount == nBands &&
1819 1206 : IsAllBands(nBands, panBandMap) &&
1820 204 : AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
1821 : psExtraArg))
1822 : {
1823 198 : const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
1824 198 : const GByte *pabyR = m_abyBuffer.data();
1825 198 : const GByte *pabyG = m_abyBuffer.data() + nPixelCount;
1826 198 : const GByte *pabyB = m_abyBuffer.data() + nPixelCount * 2;
1827 198 : const GByte *pabyValue = m_abyBuffer.data() + nPixelCount * nColorCount;
1828 198 : size_t nSrcIdx = 0;
1829 517 : for (int j = 0; j < nBufYSize; ++j)
1830 : {
1831 319 : auto nDstOffset = j * nLineSpace;
1832 319 : if (nPixelSpace == 1 && nLineSpace >= nPixelSpace * nBufXSize &&
1833 317 : nBandSpace >= nLineSpace * nBufYSize)
1834 : {
1835 317 : patch_value_line(nBufXSize, pabyR + nSrcIdx, pabyG + nSrcIdx,
1836 : pabyB + nSrcIdx, pabyValue + nSrcIdx,
1837 317 : pabyDst + nDstOffset,
1838 317 : pabyDst + nDstOffset + nBandSpace,
1839 317 : pabyDst + nDstOffset + 2 * nBandSpace);
1840 317 : nSrcIdx += nBufXSize;
1841 : }
1842 : else
1843 : {
1844 262146 : for (int i = 0; i < nBufXSize;
1845 262144 : ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
1846 : {
1847 : float h, s;
1848 262144 : rgb_to_hs(pabyR[nSrcIdx], pabyG[nSrcIdx], pabyB[nSrcIdx],
1849 : &h, &s);
1850 262144 : hsv_to_rgb(h, s, pabyValue[nSrcIdx],
1851 262144 : &pabyDst[nDstOffset + 0 * nBandSpace],
1852 262144 : &pabyDst[nDstOffset + 1 * nBandSpace],
1853 262144 : &pabyDst[nDstOffset + 2 * nBandSpace]);
1854 : }
1855 : }
1856 : }
1857 198 : if (nColorCount == 4)
1858 : {
1859 394 : for (int j = 0; j < nBufYSize; ++j)
1860 : {
1861 198 : auto nDstOffset = 3 * nBandSpace + j * nLineSpace;
1862 198 : const GByte *pabyA = m_abyBuffer.data() + nPixelCount * 3;
1863 198 : GDALCopyWords64(pabyA, GDT_UInt8, 1, pabyDst + nDstOffset,
1864 : GDT_UInt8, static_cast<int>(nPixelSpace),
1865 : nBufXSize);
1866 : }
1867 : }
1868 :
1869 198 : return CE_None;
1870 : }
1871 :
1872 : /************************************************************************/
1873 : /* SRC_OVER */
1874 : /************************************************************************/
1875 315 : else if (nOverlayCount == 4 && nColorCount == 4 &&
1876 314 : m_operator == CompositionMode::SRC_OVER && eRWFlag == GF_Read &&
1877 1 : eBufType == GDT_UInt8 && nBandCount == nBands &&
1878 834 : IsAllBands(nBands, panBandMap) &&
1879 1 : AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize, nBufXSize,
1880 : nBufYSize, psExtraArg))
1881 : {
1882 1 : const GByte nOpacity = static_cast<GByte>(m_opacity255Scale);
1883 1 : const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
1884 1 : const GByte *CPL_RESTRICT pabyR = m_abyBuffer.data();
1885 1 : const GByte *CPL_RESTRICT pabyG = m_abyBuffer.data() + nPixelCount;
1886 1 : const GByte *CPL_RESTRICT pabyB = m_abyBuffer.data() + nPixelCount * 2;
1887 1 : const GByte *CPL_RESTRICT pabyA = m_abyBuffer.data() + nPixelCount * 3;
1888 : const GByte *CPL_RESTRICT pabyOverlayR =
1889 1 : m_abyBuffer.data() + nPixelCount * nColorCount;
1890 : const GByte *CPL_RESTRICT pabyOverlayG =
1891 1 : m_abyBuffer.data() + nPixelCount * (nColorCount + 1);
1892 : const GByte *CPL_RESTRICT pabyOverlayB =
1893 1 : m_abyBuffer.data() + nPixelCount * (nColorCount + 2);
1894 : const GByte *CPL_RESTRICT pabyOverlayA =
1895 1 : m_abyBuffer.data() + nPixelCount * (nColorCount + 3);
1896 1 : size_t nSrcIdx = 0;
1897 2 : for (int j = 0; j < nBufYSize; ++j)
1898 : {
1899 1 : auto nDstOffset = j * nLineSpace;
1900 1 : int i = 0;
1901 : #ifdef HAVE_SSE2
1902 1 : if (nPixelSpace == 1)
1903 : {
1904 2 : i = BlendSrcOverRGBA_SSE2(
1905 : pabyR + nSrcIdx, pabyG + nSrcIdx, pabyB + nSrcIdx,
1906 : pabyA + nSrcIdx, pabyOverlayR + nSrcIdx,
1907 : pabyOverlayG + nSrcIdx, pabyOverlayB + nSrcIdx,
1908 1 : pabyOverlayA + nSrcIdx, pabyDst + nDstOffset, nBandSpace,
1909 : nBufXSize, nOpacity);
1910 1 : nSrcIdx += i;
1911 1 : nDstOffset += i;
1912 : }
1913 : #endif
1914 : #if !(defined(HAVE_SSE2) && defined(__GNUC__))
1915 : if (nPixelSpace == 1)
1916 : {
1917 : // Note: clang 20 does a rather good job at autovectorizing
1918 : // for SSE2, but BlendSrcOverRGBA_SSE2() performs better.
1919 : BlendSrcOverRGBA_Generic</* bPixelSpaceIsOne = */ true>(
1920 : pabyR + nSrcIdx, pabyG + nSrcIdx, pabyB + nSrcIdx,
1921 : pabyA + nSrcIdx, pabyOverlayR + nSrcIdx,
1922 : pabyOverlayG + nSrcIdx, pabyOverlayB + nSrcIdx,
1923 : pabyOverlayA + nSrcIdx, pabyDst + nDstOffset,
1924 : /*nPixelSpace = */ 1, nBandSpace, i, nBufXSize, nOpacity);
1925 : }
1926 : else
1927 : #endif
1928 : {
1929 1 : BlendSrcOverRGBA_Generic</* bPixelSpaceIsOne = */ false>(
1930 : pabyR + nSrcIdx, pabyG + nSrcIdx, pabyB + nSrcIdx,
1931 : pabyA + nSrcIdx, pabyOverlayR + nSrcIdx,
1932 : pabyOverlayG + nSrcIdx, pabyOverlayB + nSrcIdx,
1933 1 : pabyOverlayA + nSrcIdx, pabyDst + nDstOffset, nPixelSpace,
1934 : nBandSpace, i, nBufXSize, nOpacity);
1935 : }
1936 1 : nSrcIdx += nBufXSize - i;
1937 : }
1938 1 : return CE_None;
1939 : }
1940 :
1941 : /************************************************************************/
1942 : /* OTHER OPERATORS */
1943 : /************************************************************************/
1944 :
1945 1335 : else if ((m_operator == CompositionMode::MULTIPLY ||
1946 301 : m_operator == CompositionMode::OVERLAY ||
1947 273 : m_operator == CompositionMode::SCREEN ||
1948 233 : m_operator == CompositionMode::HARD_LIGHT ||
1949 225 : m_operator == CompositionMode::DARKEN ||
1950 169 : m_operator == CompositionMode::LIGHTEN ||
1951 89 : m_operator == CompositionMode::COLOR_BURN ||
1952 517 : m_operator == CompositionMode::COLOR_DODGE) &&
1953 488 : eRWFlag == GF_Read && eBufType == GDT_UInt8 &&
1954 1522 : nBandCount == nBands && IsAllBands(nBands, panBandMap) &&
1955 488 : AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize, nBufXSize,
1956 : nBufYSize, psExtraArg))
1957 : {
1958 : // We should have optimized paths for 1, 2, 3 and 4 bands on input and overlay
1959 : // permutations but let's keep it simple for now.
1960 488 : const GByte nOpacity = static_cast<GByte>(m_opacity255Scale);
1961 488 : const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
1962 488 : const GByte *CPL_RESTRICT pabyR = m_abyBuffer.data();
1963 488 : GByte *CPL_RESTRICT pabyG = nullptr;
1964 488 : GByte *CPL_RESTRICT pabyB = nullptr;
1965 488 : GByte *CPL_RESTRICT pabyA = nullptr;
1966 488 : switch (nColorCount)
1967 : {
1968 96 : case 2:
1969 96 : pabyA = m_abyBuffer.data() + nPixelCount;
1970 96 : break;
1971 0 : case 3:
1972 0 : pabyG = m_abyBuffer.data() + nPixelCount;
1973 0 : pabyB = m_abyBuffer.data() + nPixelCount * 2;
1974 0 : break;
1975 368 : case 4:
1976 368 : pabyG = m_abyBuffer.data() + nPixelCount;
1977 368 : pabyB = m_abyBuffer.data() + nPixelCount * 2;
1978 368 : pabyA = m_abyBuffer.data() + nPixelCount * 3;
1979 368 : break;
1980 : }
1981 :
1982 : const GByte *CPL_RESTRICT pabyOverlayR =
1983 488 : m_abyBuffer.data() + nPixelCount * nColorCount;
1984 488 : GByte *CPL_RESTRICT pabyOverlayG = nullptr;
1985 488 : GByte *CPL_RESTRICT pabyOverlayB = nullptr;
1986 488 : GByte *CPL_RESTRICT pabyOverlayA = nullptr;
1987 488 : switch (nOverlayCount)
1988 : {
1989 104 : case 2:
1990 104 : pabyOverlayA =
1991 104 : m_abyBuffer.data() + nPixelCount * (nColorCount + 1);
1992 104 : break;
1993 0 : case 3:
1994 0 : pabyOverlayG =
1995 0 : m_abyBuffer.data() + nPixelCount * (nColorCount + 1);
1996 0 : pabyOverlayB =
1997 0 : m_abyBuffer.data() + nPixelCount * (nColorCount + 2);
1998 0 : break;
1999 312 : case 4:
2000 312 : pabyOverlayG =
2001 312 : m_abyBuffer.data() + nPixelCount * (nColorCount + 1);
2002 312 : pabyOverlayB =
2003 312 : m_abyBuffer.data() + nPixelCount * (nColorCount + 2);
2004 312 : pabyOverlayA =
2005 312 : m_abyBuffer.data() + nPixelCount * (nColorCount + 3);
2006 312 : break;
2007 : }
2008 :
2009 488 : size_t nSrcIdx = 0;
2010 976 : for (int j = 0; j < nBufYSize; ++j)
2011 : {
2012 488 : auto nDstOffset = j * nLineSpace;
2013 488 : int i = 0;
2014 :
2015 488 : const GByte *CPL_RESTRICT pabyOverlayG_current =
2016 488 : pabyOverlayG ? pabyOverlayG + nSrcIdx : nullptr;
2017 488 : const GByte *CPL_RESTRICT pabyOverlayB_current =
2018 488 : pabyOverlayB ? pabyOverlayB + nSrcIdx : nullptr;
2019 488 : const GByte *CPL_RESTRICT pabyOverlayA_current =
2020 488 : pabyOverlayA ? pabyOverlayA + nSrcIdx : nullptr;
2021 :
2022 488 : const GByte *CPL_RESTRICT pabyG_current =
2023 488 : pabyG ? pabyG + nSrcIdx : nullptr;
2024 488 : const GByte *CPL_RESTRICT pabyB_current =
2025 488 : pabyB ? pabyB + nSrcIdx : nullptr;
2026 488 : const GByte *CPL_RESTRICT pabyA_current =
2027 488 : pabyA ? pabyA + nSrcIdx : nullptr;
2028 :
2029 : // Determine the number of bands
2030 488 : const int nInputBands{1 + (pabyG ? 2 : 0) + (pabyA ? 1 : 0)};
2031 488 : const int nOverlayBands{1 + (pabyOverlayG ? 2 : 0) +
2032 488 : (pabyOverlayA ? 1 : 0)};
2033 488 : const int nOutputBands = std::max(nInputBands, nOverlayBands);
2034 :
2035 488 : if (m_operator == CompositionMode::SCREEN)
2036 40 : BlendScreen_Generic(
2037 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2038 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2039 : pabyOverlayB_current, pabyOverlayA_current,
2040 40 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2041 40 : nOpacity, nOutputBands, m_bSwappedOpacity);
2042 448 : else if (m_operator == CompositionMode::MULTIPLY)
2043 216 : BlendMultiply_Generic(
2044 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2045 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2046 : pabyOverlayB_current, pabyOverlayA_current,
2047 216 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2048 216 : nOpacity, nOutputBands, m_bSwappedOpacity);
2049 232 : else if (m_operator == CompositionMode::HARD_LIGHT)
2050 8 : BlendHardLight_Generic(
2051 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2052 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2053 : pabyOverlayB_current, pabyOverlayA_current,
2054 8 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2055 8 : nOpacity, nOutputBands, m_bSwappedOpacity);
2056 224 : else if (m_operator == CompositionMode::OVERLAY)
2057 28 : BlendOverlay_Generic(
2058 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2059 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2060 : pabyOverlayB_current, pabyOverlayA_current,
2061 28 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2062 28 : nOpacity, nOutputBands, m_bSwappedOpacity);
2063 196 : else if (m_operator == CompositionMode::DARKEN)
2064 56 : BlendDarken_Generic(
2065 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2066 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2067 : pabyOverlayB_current, pabyOverlayA_current,
2068 56 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2069 56 : nOpacity, nOutputBands, m_bSwappedOpacity);
2070 140 : else if (m_operator == CompositionMode::LIGHTEN)
2071 80 : BlendLighten_Generic(
2072 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2073 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2074 : pabyOverlayB_current, pabyOverlayA_current,
2075 80 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2076 80 : nOpacity, nOutputBands, m_bSwappedOpacity);
2077 60 : else if (m_operator == CompositionMode::COLOR_BURN)
2078 32 : BlendColorBurn_Generic(
2079 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2080 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2081 : pabyOverlayB_current, pabyOverlayA_current,
2082 32 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2083 32 : nOpacity, nOutputBands, m_bSwappedOpacity);
2084 28 : else if (m_operator == CompositionMode::COLOR_DODGE)
2085 28 : BlendColorDodge_Generic(
2086 : pabyR + nSrcIdx, pabyG_current, pabyB_current,
2087 : pabyA_current, pabyOverlayR + nSrcIdx, pabyOverlayG_current,
2088 : pabyOverlayB_current, pabyOverlayA_current,
2089 28 : pabyDst + nDstOffset, nPixelSpace, nBandSpace, i, nBufXSize,
2090 28 : nOpacity, nOutputBands, m_bSwappedOpacity);
2091 : else
2092 : {
2093 0 : CPLAssert(false);
2094 : }
2095 :
2096 488 : nSrcIdx += nBufXSize - i;
2097 : }
2098 488 : return CE_None;
2099 : }
2100 :
2101 : /************************************************************************/
2102 : /* ERRORS */
2103 : /************************************************************************/
2104 29 : else if (m_ioError)
2105 : {
2106 6 : return CE_Failure;
2107 : }
2108 : else
2109 : {
2110 23 : const CPLErr eErr = GDALDataset::IRasterIO(
2111 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2112 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
2113 : nBandSpace, psExtraArg);
2114 23 : m_ioError = eErr != CE_None;
2115 23 : return eErr;
2116 : }
2117 : }
2118 :
2119 : /************************************************************************/
2120 : /* SrcOverRGBOneComponent() */
2121 : /************************************************************************/
2122 :
2123 : // GCC and clang do a god job a auto vectorizing the below function
2124 : #if defined(__GNUC__) && !defined(__clang__)
2125 : __attribute__((optimize("tree-vectorize")))
2126 : #endif
2127 : static void
2128 12 : SrcOverRGB(const uint8_t *const __restrict pabyOverlay,
2129 : const uint8_t *const __restrict pabySrc,
2130 : uint8_t *const __restrict pabyDst, const size_t N,
2131 : const uint8_t nOpacity)
2132 : {
2133 915 : for (size_t i = 0; i < N; ++i)
2134 : {
2135 903 : const uint8_t nOverlay = pabyOverlay[i];
2136 903 : const uint8_t nSrc = pabySrc[i];
2137 903 : pabyDst[i] = static_cast<uint8_t>(
2138 903 : (nOverlay * nOpacity + nSrc * (255 - nOpacity) + 255) / 256);
2139 : }
2140 12 : }
2141 :
2142 : /************************************************************************/
2143 : /* BlendBand::IRasterIO() */
2144 : /************************************************************************/
2145 :
2146 566 : CPLErr BlendBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
2147 : int nXSize, int nYSize, void *pData, int nBufXSize,
2148 : int nBufYSize, GDALDataType eBufType,
2149 : GSpacing nPixelSpace, GSpacing nLineSpace,
2150 : GDALRasterIOExtraArg *psExtraArg)
2151 : {
2152 : // Try to pass the request to the most appropriate overview dataset.
2153 566 : if (nBufXSize < nXSize && nBufYSize < nYSize)
2154 : {
2155 1 : int bTried = FALSE;
2156 1 : const CPLErr eErr = TryOverviewRasterIO(
2157 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2158 : eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
2159 1 : if (bTried)
2160 1 : return eErr;
2161 : }
2162 :
2163 565 : const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
2164 565 : const int nColorCount = m_oBlendDataset.m_oColorDS.GetRasterCount();
2165 565 : const int nOverlayCount = m_oBlendDataset.m_oOverlayDS.GetRasterCount();
2166 565 : if (nBand == 4 && m_oBlendDataset.m_operator == CompositionMode::HSV_VALUE)
2167 : {
2168 9 : if (nColorCount == 3)
2169 : {
2170 0 : GByte ch = 255;
2171 0 : for (int iY = 0; iY < nBufYSize; ++iY)
2172 : {
2173 0 : GDALCopyWords64(&ch, GDT_UInt8, 0,
2174 0 : static_cast<GByte *>(pData) + iY * nLineSpace,
2175 : eBufType, static_cast<int>(nPixelSpace),
2176 : nBufXSize);
2177 : }
2178 0 : return CE_None;
2179 : }
2180 : else
2181 : {
2182 9 : CPLAssert(nColorCount == 4);
2183 9 : return m_oBlendDataset.m_oColorDS.GetRasterBand(4)->RasterIO(
2184 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
2185 9 : nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg);
2186 : }
2187 : }
2188 25 : else if (nOverlayCount == 3 && nColorCount == 3 &&
2189 18 : m_oBlendDataset.m_operator == CompositionMode::SRC_OVER &&
2190 593 : eRWFlag == GF_Read && eBufType == GDT_UInt8 &&
2191 12 : m_oBlendDataset.AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize,
2192 : nBufXSize, nBufYSize,
2193 : psExtraArg))
2194 : {
2195 12 : const int nOpacity = m_oBlendDataset.m_opacity255Scale;
2196 : const GByte *const CPL_RESTRICT pabySrc =
2197 12 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount * (nBand - 1);
2198 : const GByte *const CPL_RESTRICT pabyOverlay =
2199 12 : m_oBlendDataset.m_abyBuffer.data() +
2200 12 : nPixelCount * (nColorCount + nBand - 1);
2201 12 : GByte *const CPL_RESTRICT pabyDst = static_cast<GByte *>(pData);
2202 12 : size_t nSrcIdx = 0;
2203 24 : for (int j = 0; j < nBufYSize; ++j)
2204 : {
2205 12 : auto nDstOffset = j * nLineSpace;
2206 12 : if (nPixelSpace == 1)
2207 : {
2208 12 : SrcOverRGB(pabyOverlay + nSrcIdx, pabySrc + nSrcIdx,
2209 12 : pabyDst + nDstOffset, nBufXSize,
2210 : static_cast<uint8_t>(nOpacity));
2211 12 : nSrcIdx += nBufXSize;
2212 : }
2213 : else
2214 : {
2215 0 : for (int i = 0; i < nBufXSize;
2216 0 : ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
2217 : {
2218 0 : const int nOverlay = pabyOverlay[nSrcIdx];
2219 0 : const int nSrc = pabySrc[nSrcIdx];
2220 0 : pabyDst[nDstOffset] = static_cast<GByte>(
2221 0 : (nOverlay * nOpacity + nSrc * (255 - nOpacity) + 255) /
2222 : 256);
2223 : }
2224 : }
2225 : }
2226 12 : return CE_None;
2227 : }
2228 1020 : else if (eRWFlag == GF_Read && eBufType == GDT_UInt8 &&
2229 476 : m_oBlendDataset.AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize,
2230 : nBufXSize, nBufYSize,
2231 : psExtraArg))
2232 : {
2233 476 : GByte *pabyDst = static_cast<GByte *>(pData);
2234 :
2235 476 : if (m_oBlendDataset.m_operator == CompositionMode::MULTIPLY ||
2236 364 : m_oBlendDataset.m_operator == CompositionMode::SCREEN ||
2237 324 : m_oBlendDataset.m_operator == CompositionMode::HARD_LIGHT ||
2238 316 : m_oBlendDataset.m_operator == CompositionMode::OVERLAY ||
2239 288 : m_oBlendDataset.m_operator == CompositionMode::DARKEN ||
2240 256 : m_oBlendDataset.m_operator == CompositionMode::LIGHTEN ||
2241 200 : m_oBlendDataset.m_operator == CompositionMode::COLOR_BURN ||
2242 168 : m_oBlendDataset.m_operator == CompositionMode::COLOR_DODGE)
2243 : {
2244 336 : CPLAssert(nBand <= 4);
2245 336 : const GByte *pabyR = m_oBlendDataset.m_abyBuffer.data();
2246 : const GByte *pabyG =
2247 336 : nColorCount >= 3
2248 336 : ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount
2249 336 : : nullptr;
2250 : const GByte *pabyB =
2251 336 : nColorCount >= 3
2252 336 : ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 2
2253 336 : : nullptr;
2254 :
2255 336 : GByte *pabyA = nullptr;
2256 :
2257 336 : if (nColorCount == 2)
2258 : {
2259 0 : pabyA = m_oBlendDataset.m_abyBuffer.data() + nPixelCount;
2260 : }
2261 336 : else if (nColorCount == 4)
2262 : {
2263 336 : pabyA = m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 3;
2264 : }
2265 :
2266 : // Retrieve single band value as R
2267 : const GByte *pabyOverlayR =
2268 336 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount * nColorCount;
2269 :
2270 : const GByte *pabyOverlayG =
2271 336 : nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
2272 312 : nPixelCount * (nColorCount + 1)
2273 336 : : nullptr;
2274 : const GByte *pabyOverlayB =
2275 336 : nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
2276 312 : nPixelCount * (nColorCount + 2)
2277 336 : : nullptr;
2278 : const GByte *pabyOverlayA =
2279 336 : (nOverlayCount == 2 || nOverlayCount == 4)
2280 672 : ? m_oBlendDataset.m_abyBuffer.data() +
2281 312 : nPixelCount * (nColorCount + nOverlayCount - 1)
2282 336 : : nullptr;
2283 :
2284 : // Determine the number of bands
2285 336 : const int nInputBands{1 + (pabyG ? 2 : 0) + (pabyA ? 1 : 0)};
2286 336 : const int nOverlayBands{1 + (pabyOverlayG ? 2 : 0) +
2287 336 : (pabyOverlayA ? 1 : 0)};
2288 336 : const int nOutputBands = std::max(nInputBands, nOverlayBands);
2289 336 : const bool bSwappedOpacity{m_oBlendDataset.m_bSwappedOpacity};
2290 :
2291 336 : size_t nSrcIdx = 0;
2292 672 : for (int j = 0; j < nBufYSize; ++j)
2293 : {
2294 336 : auto nDstOffset = j * nLineSpace;
2295 672 : for (int i = 0; i < nBufXSize;
2296 336 : ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
2297 : {
2298 : // TODO: This need to be optimized for requesting a single band
2299 336 : std::vector<GByte> byteBuffer;
2300 336 : byteBuffer.resize(std::max(nColorCount, nOverlayCount));
2301 336 : if (m_oBlendDataset.m_operator == CompositionMode::SCREEN)
2302 40 : BlendScreen_Generic(
2303 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2304 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2305 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2306 : static_cast<GByte>(
2307 40 : m_oBlendDataset.m_opacity255Scale),
2308 : nOutputBands, bSwappedOpacity);
2309 296 : else if (m_oBlendDataset.m_operator ==
2310 : CompositionMode::HARD_LIGHT)
2311 8 : BlendHardLight_Generic(
2312 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2313 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2314 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2315 : static_cast<GByte>(
2316 8 : m_oBlendDataset.m_opacity255Scale),
2317 : nOutputBands, bSwappedOpacity);
2318 288 : else if (m_oBlendDataset.m_operator ==
2319 : CompositionMode::OVERLAY)
2320 28 : BlendOverlay_Generic(
2321 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2322 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2323 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2324 : static_cast<GByte>(
2325 28 : m_oBlendDataset.m_opacity255Scale),
2326 : nOutputBands, bSwappedOpacity);
2327 260 : else if (m_oBlendDataset.m_operator ==
2328 : CompositionMode::MULTIPLY)
2329 112 : BlendMultiply_Generic(
2330 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2331 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2332 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2333 : static_cast<GByte>(
2334 112 : m_oBlendDataset.m_opacity255Scale),
2335 : nOutputBands, bSwappedOpacity);
2336 148 : else if (m_oBlendDataset.m_operator ==
2337 : CompositionMode::DARKEN)
2338 32 : BlendDarken_Generic(
2339 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2340 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2341 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2342 : static_cast<GByte>(
2343 32 : m_oBlendDataset.m_opacity255Scale),
2344 : nOutputBands, bSwappedOpacity);
2345 116 : else if (m_oBlendDataset.m_operator ==
2346 : CompositionMode::LIGHTEN)
2347 56 : BlendLighten_Generic(
2348 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2349 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2350 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2351 : static_cast<GByte>(
2352 56 : m_oBlendDataset.m_opacity255Scale),
2353 : nOutputBands, bSwappedOpacity);
2354 60 : else if (m_oBlendDataset.m_operator ==
2355 : CompositionMode::COLOR_BURN)
2356 32 : BlendColorBurn_Generic(
2357 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2358 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2359 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2360 : static_cast<GByte>(
2361 32 : m_oBlendDataset.m_opacity255Scale),
2362 : nOutputBands, bSwappedOpacity);
2363 28 : else if (m_oBlendDataset.m_operator ==
2364 : CompositionMode::COLOR_DODGE)
2365 28 : BlendColorDodge_Generic(
2366 : pabyR, pabyG, pabyB, pabyA, pabyOverlayR,
2367 : pabyOverlayG, pabyOverlayB, pabyOverlayA,
2368 : byteBuffer.data(), nPixelSpace, 1, nSrcIdx, 1,
2369 : static_cast<GByte>(
2370 28 : m_oBlendDataset.m_opacity255Scale),
2371 : nOutputBands, bSwappedOpacity);
2372 : else
2373 : {
2374 0 : CPLAssert(false);
2375 : }
2376 :
2377 336 : pabyDst[nDstOffset] = byteBuffer[nBand - 1];
2378 : }
2379 336 : }
2380 : }
2381 140 : else if (m_oBlendDataset.m_operator == CompositionMode::SRC_OVER)
2382 : {
2383 3 : const auto RGBToGrayScale = [](int R, int G, int B)
2384 : {
2385 : // Equivalent to R * 0.299 + G * 0.587 + B * 0.114
2386 : // but using faster computation
2387 3 : return (R * 306 + G * 601 + B * 117) / 1024;
2388 : };
2389 :
2390 : const GByte *paby =
2391 83 : (nBand <= nColorCount) ? m_oBlendDataset.m_abyBuffer.data() +
2392 83 : nPixelCount * (nBand - 1)
2393 0 : : (nBand == 4 && nColorCount == 2)
2394 0 : ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount
2395 83 : : nullptr;
2396 : const GByte *pabyA =
2397 83 : (nColorCount == 4)
2398 106 : ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 3
2399 23 : : nColorCount == 2
2400 4 : ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount
2401 83 : : nullptr;
2402 : const GByte *pabyOverlay =
2403 83 : (nBand <= nOverlayCount)
2404 91 : ? m_oBlendDataset.m_abyBuffer.data() +
2405 75 : nPixelCount * (nColorCount + nBand - 1)
2406 8 : : (nBand <= 3) ? m_oBlendDataset.m_abyBuffer.data() +
2407 7 : nPixelCount * nColorCount
2408 83 : : nullptr;
2409 : const GByte *pabyOverlayA =
2410 80 : (nOverlayCount == 2 || nOverlayCount == 4)
2411 163 : ? m_oBlendDataset.m_abyBuffer.data() +
2412 62 : nPixelCount * (nColorCount + nOverlayCount - 1)
2413 83 : : nullptr;
2414 : const GByte *pabyOverlayR =
2415 66 : (nOverlayCount >= 3 && nColorCount < 3 && nBand <= 3)
2416 149 : ? m_oBlendDataset.m_abyBuffer.data() +
2417 3 : nPixelCount * nColorCount
2418 83 : : nullptr;
2419 : const GByte *pabyOverlayG =
2420 66 : (nOverlayCount >= 3 && nColorCount < 3 && nBand <= 3)
2421 149 : ? m_oBlendDataset.m_abyBuffer.data() +
2422 3 : nPixelCount * (nColorCount + 1)
2423 83 : : nullptr;
2424 : const GByte *pabyOverlayB =
2425 66 : (nOverlayCount >= 3 && nColorCount < 3 && nBand <= 3)
2426 149 : ? m_oBlendDataset.m_abyBuffer.data() +
2427 3 : nPixelCount * (nColorCount + 2)
2428 83 : : nullptr;
2429 :
2430 83 : size_t nSrcIdx = 0;
2431 714 : for (int j = 0; j < nBufYSize; ++j)
2432 : {
2433 631 : auto nDstOffset = j * nLineSpace;
2434 98753 : for (int i = 0; i < nBufXSize;
2435 98122 : ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
2436 : {
2437 : // Corrected to take into account m_opacity255Scale
2438 98122 : const int nOverlayA =
2439 98122 : pabyOverlayA ? ((pabyOverlayA[nSrcIdx] *
2440 97606 : m_oBlendDataset.m_opacity255Scale +
2441 : 255) /
2442 : 256)
2443 516 : : m_oBlendDataset.m_opacity255Scale;
2444 :
2445 98122 : const int nSrcA = pabyA ? pabyA[nSrcIdx] : 255;
2446 :
2447 98122 : const int nSrcAMul255MinusOverlayA =
2448 98122 : (nSrcA * (255 - nOverlayA) + 255) / 256;
2449 98122 : const int nDstA = nOverlayA + nSrcAMul255MinusOverlayA;
2450 98122 : if (nBand != 4)
2451 : {
2452 : const int nOverlay =
2453 3 : (pabyOverlayR && pabyOverlayG && pabyOverlayB)
2454 147439 : ? RGBToGrayScale(pabyOverlayR[nSrcIdx],
2455 3 : pabyOverlayG[nSrcIdx],
2456 3 : pabyOverlayB[nSrcIdx])
2457 73718 : : pabyOverlay ? pabyOverlay[nSrcIdx]
2458 73721 : : 255;
2459 :
2460 73721 : const int nSrc = paby ? paby[nSrcIdx] : 255;
2461 73721 : int nDst = (nOverlay * nOverlayA +
2462 73721 : nSrc * nSrcAMul255MinusOverlayA + 255) /
2463 : 256;
2464 73721 : if (nDstA != 0 && nDstA != 255)
2465 19672 : nDst = (nDst * 255 + (nDstA / 2)) / nDstA;
2466 73721 : pabyDst[nDstOffset] =
2467 73721 : static_cast<GByte>(std::min(nDst, 255));
2468 : }
2469 : else
2470 : {
2471 24401 : pabyDst[nDstOffset] = static_cast<GByte>(nDstA);
2472 : }
2473 : }
2474 : }
2475 : }
2476 57 : else if (nOverlayCount == 1 && m_oBlendDataset.m_opacity255Scale == 255)
2477 : {
2478 27 : const GByte *pabyR = m_oBlendDataset.m_abyBuffer.data();
2479 : const GByte *pabyG =
2480 27 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount;
2481 : const GByte *pabyB =
2482 27 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 2;
2483 27 : CPLAssert(m_oBlendDataset.m_operator == CompositionMode::HSV_VALUE);
2484 27 : size_t nSrcIdx = 0;
2485 : const GByte *pabyValue =
2486 27 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount * nColorCount;
2487 411 : for (int j = 0; j < nBufYSize; ++j)
2488 : {
2489 384 : auto nDstOffset = j * nLineSpace;
2490 384 : if (nPixelSpace == 1 && nLineSpace >= nPixelSpace * nBufXSize)
2491 : {
2492 884 : patch_value_line(
2493 : nBufXSize, pabyR + nSrcIdx, pabyG + nSrcIdx,
2494 : pabyB + nSrcIdx, pabyValue + nSrcIdx,
2495 378 : nBand == 1 ? pabyDst + nDstOffset : nullptr,
2496 378 : nBand == 2 ? pabyDst + nDstOffset : nullptr,
2497 378 : nBand == 3 ? pabyDst + nDstOffset : nullptr);
2498 378 : nSrcIdx += nBufXSize;
2499 : }
2500 : else
2501 : {
2502 786438 : for (int i = 0; i < nBufXSize;
2503 786432 : ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
2504 : {
2505 : float h, s;
2506 786432 : rgb_to_hs(pabyR[nSrcIdx], pabyG[nSrcIdx],
2507 786432 : pabyB[nSrcIdx], &h, &s);
2508 786432 : if (nBand == 1)
2509 : {
2510 262144 : hsv_to_rgb(h, s, pabyValue[nSrcIdx],
2511 262144 : &pabyDst[nDstOffset], nullptr, nullptr);
2512 : }
2513 524288 : else if (nBand == 2)
2514 : {
2515 262144 : hsv_to_rgb(h, s, pabyValue[nSrcIdx], nullptr,
2516 262144 : &pabyDst[nDstOffset], nullptr);
2517 : }
2518 : else
2519 : {
2520 262144 : CPLAssert(nBand == 3);
2521 262144 : hsv_to_rgb(h, s, pabyValue[nSrcIdx], nullptr,
2522 262144 : nullptr, &pabyDst[nDstOffset]);
2523 : }
2524 : }
2525 : }
2526 27 : }
2527 : }
2528 : else
2529 : {
2530 30 : CPLAssert(m_oBlendDataset.m_operator == CompositionMode::HSV_VALUE);
2531 30 : CPLAssert(nBand <= 3);
2532 30 : const GByte *pabyR = m_oBlendDataset.m_abyBuffer.data();
2533 : const GByte *pabyG =
2534 30 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount;
2535 : const GByte *pabyB =
2536 30 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 2;
2537 : const GByte *pabyValue =
2538 30 : m_oBlendDataset.m_abyBuffer.data() + nPixelCount * nColorCount;
2539 : const GByte *pabyOverlayR =
2540 30 : nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
2541 12 : nPixelCount * nColorCount
2542 30 : : nullptr;
2543 : const GByte *pabyOverlayG =
2544 30 : nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
2545 12 : nPixelCount * (nColorCount + 1)
2546 30 : : nullptr;
2547 : const GByte *pabyOverlayB =
2548 30 : nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
2549 12 : nPixelCount * (nColorCount + 2)
2550 30 : : nullptr;
2551 : const GByte *pabyOverlayA =
2552 24 : (nOverlayCount == 2 || nOverlayCount == 4)
2553 54 : ? m_oBlendDataset.m_abyBuffer.data() +
2554 12 : nPixelCount * (nColorCount + nOverlayCount - 1)
2555 30 : : nullptr;
2556 :
2557 30 : size_t nSrcIdx = 0;
2558 60 : for (int j = 0; j < nBufYSize; ++j)
2559 : {
2560 30 : auto nDstOffset = j * nLineSpace;
2561 3932190 : for (int i = 0; i < nBufXSize;
2562 3932160 : ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
2563 : {
2564 3932160 : const int nColorR = pabyR[nSrcIdx];
2565 3932160 : const int nColorG = pabyG[nSrcIdx];
2566 3932160 : const int nColorB = pabyB[nSrcIdx];
2567 : const int nOverlayV =
2568 1572860 : (pabyOverlayR && pabyOverlayG && pabyOverlayB)
2569 5505020 : ? std::max({pabyOverlayR[nSrcIdx],
2570 1572860 : pabyOverlayG[nSrcIdx],
2571 1572860 : pabyOverlayB[nSrcIdx]})
2572 2359300 : : pabyValue[nSrcIdx];
2573 3932160 : const int nOverlayA =
2574 3932160 : pabyOverlayA ? ((pabyOverlayA[nSrcIdx] *
2575 1572860 : m_oBlendDataset.m_opacity255Scale +
2576 : 255) /
2577 : 256)
2578 2359300 : : m_oBlendDataset.m_opacity255Scale;
2579 : const int nColorValue =
2580 3932160 : std::max({nColorR, nColorG, nColorB});
2581 :
2582 : float h, s;
2583 3932160 : rgb_to_hs(pabyR[nSrcIdx], pabyG[nSrcIdx], pabyB[nSrcIdx],
2584 : &h, &s);
2585 :
2586 3932160 : const GByte nTargetValue = static_cast<GByte>(
2587 3932160 : (nOverlayV * nOverlayA +
2588 3932160 : nColorValue * (255 - nOverlayA) + 255) /
2589 : 256);
2590 :
2591 3932160 : if (nBand == 1)
2592 : {
2593 1310720 : hsv_to_rgb(h, s, nTargetValue, &pabyDst[nDstOffset],
2594 : nullptr, nullptr);
2595 : }
2596 2621440 : else if (nBand == 2)
2597 : {
2598 1310720 : hsv_to_rgb(h, s, nTargetValue, nullptr,
2599 1310720 : &pabyDst[nDstOffset], nullptr);
2600 : }
2601 : else
2602 : {
2603 1310720 : CPLAssert(nBand == 3);
2604 1310720 : hsv_to_rgb(h, s, nTargetValue, nullptr, nullptr,
2605 1310720 : &pabyDst[nDstOffset]);
2606 : }
2607 : }
2608 : }
2609 : }
2610 :
2611 476 : return CE_None;
2612 : }
2613 68 : else if (m_oBlendDataset.m_ioError)
2614 : {
2615 0 : return CE_Failure;
2616 : }
2617 : else
2618 : {
2619 68 : const CPLErr eErr = GDALRasterBand::IRasterIO(
2620 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
2621 : eBufType, nPixelSpace, nLineSpace, psExtraArg);
2622 68 : m_oBlendDataset.m_ioError = eErr != CE_None;
2623 68 : return eErr;
2624 : }
2625 : }
2626 :
2627 : } // namespace
2628 :
2629 : /************************************************************************/
2630 : /* GDALRasterBlendAlgorithm::ValidateGlobal() */
2631 : /************************************************************************/
2632 :
2633 340 : bool GDALRasterBlendAlgorithm::ValidateGlobal()
2634 : {
2635 : auto poSrcDS =
2636 340 : m_inputDataset.empty() ? nullptr : m_inputDataset[0].GetDatasetRef();
2637 340 : auto poOverlayDS = m_overlayDataset.GetDatasetRef();
2638 340 : if (poSrcDS)
2639 : {
2640 674 : if (poSrcDS->GetRasterCount() == 0 || poSrcDS->GetRasterCount() > 4 ||
2641 337 : poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt8)
2642 : {
2643 1 : ReportError(CE_Failure, CPLE_NotSupported,
2644 : "Only 1-band, 2-band, 3-band or 4-band Byte dataset "
2645 : "supported as input");
2646 1 : return false;
2647 : }
2648 : }
2649 339 : if (poOverlayDS)
2650 : {
2651 339 : if (poOverlayDS->GetRasterCount() == 0 ||
2652 678 : poOverlayDS->GetRasterCount() > 4 ||
2653 339 : poOverlayDS->GetRasterBand(1)->GetRasterDataType() != GDT_UInt8)
2654 : {
2655 1 : ReportError(CE_Failure, CPLE_NotSupported,
2656 : "Only 1-band, 2-band, 3-band or 4-band Byte dataset "
2657 : "supported as overlay");
2658 1 : return false;
2659 : }
2660 : }
2661 :
2662 338 : if (poSrcDS && poOverlayDS)
2663 : {
2664 669 : if (poSrcDS->GetRasterXSize() != poOverlayDS->GetRasterXSize() ||
2665 334 : poSrcDS->GetRasterYSize() != poOverlayDS->GetRasterYSize())
2666 : {
2667 2 : ReportError(CE_Failure, CPLE_IllegalArg,
2668 : "Input dataset and overlay dataset must have "
2669 : "the same dimensions");
2670 2 : return false;
2671 : }
2672 :
2673 333 : if (!BandCountIsCompatibleWithCompositionMode(poSrcDS->GetRasterCount(),
2674 : m_operator))
2675 : {
2676 : const int minRequiredBands{
2677 1 : MinBandCountForCompositionMode(m_operator)};
2678 : const int maxRequiredBands{
2679 1 : MaxBandCountForCompositionMode(m_operator)};
2680 1 : if (minRequiredBands != maxRequiredBands)
2681 1 : ReportError(CE_Failure, CPLE_IllegalArg,
2682 : "Input dataset has %d band(s), but operator %s "
2683 : "requires between %d and %d bands",
2684 : poSrcDS->GetRasterCount(),
2685 2 : CompositionModeToString(m_operator).c_str(),
2686 : minRequiredBands, maxRequiredBands);
2687 : else
2688 0 : ReportError(CE_Failure, CPLE_IllegalArg,
2689 : "Input dataset has %d band(s), but operator %s "
2690 : "requires %d bands",
2691 : poSrcDS->GetRasterCount(),
2692 0 : CompositionModeToString(m_operator).c_str(),
2693 : minRequiredBands);
2694 1 : return false;
2695 : }
2696 : }
2697 :
2698 : // Check that for LIGHTEN and DARKEN, the source dataset and destination dataset
2699 : // have the same number of color bands (do not consider alpha)
2700 335 : if (poSrcDS && poOverlayDS &&
2701 332 : (m_operator == CompositionMode::LIGHTEN ||
2702 284 : m_operator == CompositionMode::DARKEN))
2703 : {
2704 : const int nSrcColorBands =
2705 140 : (poSrcDS->GetRasterCount() == 2 || poSrcDS->GetRasterCount() == 4)
2706 132 : ? poSrcDS->GetRasterCount() - 1
2707 8 : : poSrcDS->GetRasterCount();
2708 84 : const int nOverlayColorBands = (poOverlayDS->GetRasterCount() == 2 ||
2709 56 : poOverlayDS->GetRasterCount() == 4)
2710 132 : ? poOverlayDS->GetRasterCount() - 1
2711 8 : : poOverlayDS->GetRasterCount();
2712 84 : if (nSrcColorBands != nOverlayColorBands)
2713 : {
2714 16 : ReportError(
2715 : CE_Failure, CPLE_IllegalArg,
2716 : "For LIGHTEN and DARKEN operators, the source dataset "
2717 : "and overlay dataset must have the same number of "
2718 : "bands (without considering alpha). They have %d and %d "
2719 : "bands respectively",
2720 : nSrcColorBands, nOverlayColorBands);
2721 16 : return false;
2722 : }
2723 : }
2724 :
2725 319 : return true;
2726 : }
2727 :
2728 : /************************************************************************/
2729 : /* GDALRasterBlendAlgorithm::RunStep() */
2730 : /************************************************************************/
2731 :
2732 158 : bool GDALRasterBlendAlgorithm::RunStep(GDALPipelineStepRunContext &)
2733 : {
2734 158 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
2735 158 : CPLAssert(poSrcDS);
2736 :
2737 158 : auto poOverlayDS = m_overlayDataset.GetDatasetRef();
2738 158 : CPLAssert(poOverlayDS);
2739 :
2740 : // If any of the dataset single band has a color table implicitly convert it to RGBA by calling
2741 : // GDALTranslate with -expand RGBA
2742 : auto convertToRGBAifNeeded =
2743 316 : [](GDALDataset *&poDS,
2744 : std::unique_ptr<GDALDataset, GDALDatasetUniquePtrReleaser> &ds)
2745 : -> bool
2746 : {
2747 374 : if (poDS->GetRasterCount() == 1 &&
2748 58 : poDS->GetRasterBand(1)->GetColorTable() != nullptr)
2749 : {
2750 12 : CPLStringList aosOptions;
2751 6 : aosOptions.AddString("-of");
2752 6 : aosOptions.AddString("VRT");
2753 6 : aosOptions.AddString("-expand");
2754 6 : aosOptions.AddString("RGBA");
2755 : GDALTranslateOptions *translateOptions =
2756 6 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
2757 :
2758 6 : ds.reset(GDALDataset::FromHandle(GDALTranslate(
2759 : "", GDALDataset::ToHandle(poDS), translateOptions, nullptr)));
2760 :
2761 6 : GDALTranslateOptionsFree(translateOptions);
2762 :
2763 6 : if (ds != nullptr)
2764 : {
2765 6 : poDS = ds.get();
2766 6 : return true;
2767 : }
2768 : else
2769 : {
2770 0 : return false;
2771 : }
2772 : }
2773 310 : return true;
2774 : };
2775 :
2776 158 : if (!convertToRGBAifNeeded(poSrcDS, m_poTmpSrcDS))
2777 : {
2778 0 : ReportError(CE_Failure, CPLE_AppDefined,
2779 : "Conversion of source dataset color table to RGBA failed");
2780 0 : return false;
2781 : }
2782 :
2783 158 : if (!convertToRGBAifNeeded(poOverlayDS, m_poTmpOverlayDS))
2784 : {
2785 0 : ReportError(CE_Failure, CPLE_AppDefined,
2786 : "Conversion of overlay dataset color table to RGBA failed");
2787 0 : return false;
2788 : }
2789 :
2790 158 : if (!ValidateGlobal())
2791 0 : return false;
2792 :
2793 158 : const int nOpacity255Scale =
2794 158 : (m_opacity * 255 + OPACITY_INPUT_RANGE / 2) / OPACITY_INPUT_RANGE;
2795 :
2796 158 : bool bSwappedOpacity = false;
2797 : // Many algorithms are commutative regarding the two inputs but BlendDataset assume
2798 : // RGB(A) is in the source (and not in the overlay).
2799 420 : if ((m_operator == CompositionMode::MULTIPLY ||
2800 104 : m_operator == CompositionMode::SCREEN ||
2801 94 : m_operator == CompositionMode::HARD_LIGHT ||
2802 323 : m_operator == CompositionMode::OVERLAY) &&
2803 73 : (poSrcDS->GetRasterCount() < poOverlayDS->GetRasterCount()))
2804 : {
2805 10 : bSwappedOpacity = true;
2806 10 : std::swap(poSrcDS, poOverlayDS);
2807 : }
2808 :
2809 158 : m_outputDataset.Set(std::make_unique<BlendDataset>(
2810 158 : *poSrcDS, *poOverlayDS, m_operator, nOpacity255Scale, bSwappedOpacity));
2811 :
2812 158 : return true;
2813 : }
2814 :
2815 : GDALRasterBlendAlgorithmStandalone::~GDALRasterBlendAlgorithmStandalone() =
2816 : default;
2817 :
2818 : //! @endcond
|