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