LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_blend.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 616 677 91.0 %
Date: 2025-10-01 17:07:58 Functions: 27 30 90.0 %

          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             : 
      19             : #include <algorithm>
      20             : #include <array>
      21             : #include <cassert>
      22             : #include <limits>
      23             : #include <utility>
      24             : 
      25             : #if defined(__x86_64) || defined(_M_X64)
      26             : #define HAVE_SSE2
      27             : #include <immintrin.h>
      28             : #endif
      29             : #ifdef HAVE_SSE2
      30             : #include "gdalsse_priv.h"
      31             : #endif
      32             : 
      33             : //! @cond Doxygen_Suppress
      34             : 
      35             : #ifndef _
      36             : #define _(x) (x)
      37             : #endif
      38             : 
      39             : constexpr const char *SRC_OVER = "src-over";
      40             : constexpr const char *HSV_VALUE = "hsv-value";
      41             : 
      42             : /************************************************************************/
      43             : /*       GDALRasterBlendAlgorithm::GDALRasterBlendAlgorithm()           */
      44             : /************************************************************************/
      45             : 
      46          84 : GDALRasterBlendAlgorithm::GDALRasterBlendAlgorithm(bool standaloneStep)
      47             :     : GDALRasterPipelineStepAlgorithm(
      48             :           NAME, DESCRIPTION, HELP_URL,
      49           0 :           ConstructorOptions()
      50          84 :               .SetStandaloneStep(standaloneStep)
      51          84 :               .SetAddDefaultArguments(false)
      52         168 :               .SetInputDatasetHelpMsg(_("Input raster dataset"))
      53         168 :               .SetInputDatasetAlias("color-input")
      54         168 :               .SetInputDatasetMetaVar("COLOR-INPUT")
      55         252 :               .SetOutputDatasetHelpMsg(_("Output raster dataset")))
      56             : {
      57          84 :     const auto AddOverlayDatasetArg = [this]()
      58             :     {
      59             :         auto &arg = AddArg("overlay", 0, _("Overlay dataset"),
      60         168 :                            &m_overlayDataset, GDAL_OF_RASTER)
      61          84 :                         .SetPositional()
      62          84 :                         .SetRequired();
      63             : 
      64          84 :         SetAutoCompleteFunctionForFilename(arg, GDAL_OF_RASTER);
      65          84 :     };
      66             : 
      67          84 :     if (standaloneStep)
      68             :     {
      69          42 :         AddRasterInputArgs(false, false);
      70          42 :         AddOverlayDatasetArg();
      71          42 :         AddProgressArg();
      72          42 :         AddRasterOutputArgs(false);
      73             :     }
      74             :     else
      75             :     {
      76          42 :         AddRasterHiddenInputDatasetArg();
      77          42 :         AddOverlayDatasetArg();
      78             :     }
      79             : 
      80         168 :     AddArg("operator", 0, _("Composition operator"), &m_operator)
      81          84 :         .SetChoices(SRC_OVER, HSV_VALUE)
      82          84 :         .SetDefault(SRC_OVER);
      83             :     AddArg("opacity", 0,
      84             :            _("Opacity percentage to apply to the overlay dataset (0=fully "
      85             :              "transparent, 100=full use of overlay opacity)"),
      86         168 :            &m_opacity)
      87          84 :         .SetDefault(m_opacity)
      88          84 :         .SetMinValueIncluded(0)
      89          84 :         .SetMaxValueIncluded(OPACITY_INPUT_RANGE);
      90             : 
      91         128 :     AddValidationAction([this]() { return ValidateGlobal(); });
      92          84 : }
      93             : 
      94             : namespace
      95             : {
      96             : 
      97             : /************************************************************************/
      98             : /*                            BlendDataset                              */
      99             : /************************************************************************/
     100             : 
     101             : class BlendDataset final : public GDALDataset
     102             : {
     103             :   public:
     104             :     BlendDataset(GDALDataset &oColorDS, GDALDataset &oOverlayDS,
     105             :                  const std::string &sOperator, int nOpacity255Scale);
     106             :     ~BlendDataset() override;
     107             : 
     108           2 :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override
     109             :     {
     110           2 :         return m_oColorDS.GetGeoTransform(gt);
     111             :     }
     112             : 
     113           2 :     const OGRSpatialReference *GetSpatialRef() const override
     114             :     {
     115           2 :         return m_oColorDS.GetSpatialRef();
     116             :     }
     117             : 
     118             :     bool AcquireSourcePixels(int nXOff, int nYOff, int nXSize, int nYSize,
     119             :                              int nBufXSize, int nBufYSize,
     120             :                              GDALRasterIOExtraArg *psExtraArg);
     121             : 
     122             :   protected:
     123             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     124             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     125             :                      GDALDataType eBufType, int nBandCount,
     126             :                      BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     127             :                      GSpacing nLineSpace, GSpacing nBandSpace,
     128             :                      GDALRasterIOExtraArg *psExtraArg) override;
     129             : 
     130             :   private:
     131             :     friend class BlendBand;
     132             :     GDALDataset &m_oColorDS;
     133             :     GDALDataset &m_oOverlayDS;
     134             :     const std::string m_operator;
     135             :     const int m_opacity255Scale;
     136             :     std::vector<std::unique_ptr<BlendDataset>> m_apoOverviews{};
     137             :     int m_nCachedXOff = 0;
     138             :     int m_nCachedYOff = 0;
     139             :     int m_nCachedXSize = 0;
     140             :     int m_nCachedYSize = 0;
     141             :     int m_nCachedBufXSize = 0;
     142             :     int m_nCachedBufYSize = 0;
     143             :     GDALRasterIOExtraArg m_sCachedExtraArg{};
     144             :     std::vector<GByte> m_abyBuffer{};
     145             :     bool m_ioError = false;
     146             : };
     147             : 
     148             : /************************************************************************/
     149             : /*                           rgb_to_hs()                                */
     150             : /************************************************************************/
     151             : 
     152             : // rgb comes in as [r,g,b] with values in the range [0,255]. The returned
     153             : // values will be with hue and saturation in the range [0,1].
     154             : 
     155             : // Derived from hsv_merge.py
     156             : 
     157     4982560 : static void rgb_to_hs(int r, int g, int b, float *h, float *s)
     158             : {
     159             :     int minc, maxc;
     160     4982560 :     if (r <= g)
     161             :     {
     162     2531110 :         if (r <= b)
     163             :         {
     164     1700760 :             minc = r;
     165     1700760 :             maxc = std::max(g, b);
     166             :         }
     167             :         else /* b < r */
     168             :         {
     169      830353 :             minc = b;
     170      830353 :             maxc = g;
     171             :         }
     172             :     }
     173             :     else /* g < r */
     174             :     {
     175     2451460 :         if (g <= b)
     176             :         {
     177     1659840 :             minc = g;
     178     1659840 :             maxc = std::max(r, b);
     179             :         }
     180             :         else /* b < g */
     181             :         {
     182      791616 :             minc = b;
     183      791616 :             maxc = r;
     184             :         }
     185             :     }
     186     4982560 :     const int maxc_minus_minc = maxc - minc;
     187     4982560 :     if (s)
     188     4982560 :         *s = maxc_minus_minc / static_cast<float>(std::max(1, maxc));
     189     4982560 :     if (h)
     190             :     {
     191     4982560 :         const float maxc_minus_minc_times_6 =
     192     4982560 :             maxc_minus_minc == 0 ? 1.0f : 6.0f * maxc_minus_minc;
     193     4982560 :         if (maxc == b)
     194     1700100 :             *h = 4.0f / 6.0f + (r - g) / maxc_minus_minc_times_6;
     195     3282460 :         else if (maxc == g)
     196     1660930 :             *h = 2.0f / 6.0f + (b - r) / maxc_minus_minc_times_6;
     197             :         else
     198             :         {
     199     1621540 :             const float tmp = (g - b) / maxc_minus_minc_times_6;
     200     1621540 :             *h = tmp < 0.0f ? tmp + 1.0f : tmp;
     201             :         }
     202             :     }
     203     4982560 : }
     204             : 
     205             : /************************************************************************/
     206             : /*                           choose_among()                             */
     207             : /************************************************************************/
     208             : 
     209             : template <typename T>
     210     5509780 : static inline T choose_among(int idx, T a0, T a1, T a2, T a3, T a4, T a5)
     211             : {
     212     5509780 :     switch (idx)
     213             :     {
     214      917280 :         case 0:
     215      917280 :             return a0;
     216      918255 :         case 1:
     217      918255 :             return a1;
     218      919239 :         case 2:
     219      919239 :             return a2;
     220      918978 :         case 3:
     221      918978 :             return a3;
     222      918751 :         case 4:
     223      918751 :             return a4;
     224      917280 :         default:
     225      917280 :             break;
     226             :     }
     227      917280 :     return a5;
     228             : }
     229             : 
     230             : /************************************************************************/
     231             : /*                           hsv_to_rgb()                               */
     232             : /************************************************************************/
     233             : 
     234             : // hsv comes in as [h,s,v] with hue and saturation in the range [0,1],
     235             : // but value in the range [0,255].
     236             : 
     237             : // Derived from hsv_merge.py
     238             : 
     239     4982560 : static void hsv_to_rgb(float h, float s, GByte v, GByte *r, GByte *g, GByte *b)
     240             : {
     241     4982560 :     const int i = static_cast<int>(6.0f * h);
     242     4982560 :     const float f = 6.0f * h - i;
     243     4982560 :     const GByte p = static_cast<GByte>(v * (1.0f - s) + 0.5f);
     244     4982560 :     const GByte q = static_cast<GByte>(v * (1.0f - s * f) + 0.5f);
     245     4982560 :     const GByte t = static_cast<GByte>(v * (1.0f - s * (1.0f - f)) + 0.5f);
     246             : 
     247     4982560 :     if (r)
     248     1836600 :         *r = choose_among(i, v, q, p, p, t, v);
     249     4982560 :     if (g)
     250     1836590 :         *g = choose_among(i, t, v, v, q, p, p);
     251     4982560 :     if (b)
     252     1836590 :         *b = choose_among(i, p, p, t, v, v, q);
     253     4982560 : }
     254             : 
     255             : /************************************************************************/
     256             : /*                           XMM_RGB_to_HS()                            */
     257             : /************************************************************************/
     258             : 
     259             : #ifdef HAVE_SSE2
     260             : static inline void
     261      538808 : XMM_RGB_to_HS(const GByte *CPL_RESTRICT pInR, const GByte *CPL_RESTRICT pInG,
     262             :               const GByte *CPL_RESTRICT pInB, const XMMReg4Float &zero,
     263             :               const XMMReg4Float &one, const XMMReg4Float &six,
     264             :               const XMMReg4Float &two_over_six,
     265             :               const XMMReg4Float &four_over_six, XMMReg4Float &h,
     266             :               XMMReg4Float &s)
     267             : {
     268      538808 :     const auto r = XMMReg4Float::Load4Val(pInR);
     269      538808 :     const auto g = XMMReg4Float::Load4Val(pInG);
     270      538808 :     const auto b = XMMReg4Float::Load4Val(pInB);
     271      538808 :     const auto minc = XMMReg4Float::Min(XMMReg4Float::Min(r, g), b);
     272      538808 :     const auto maxc = XMMReg4Float::Max(XMMReg4Float::Max(r, g), b);
     273      538808 :     const auto max_minus_min = maxc - minc;
     274      538808 :     s = max_minus_min / XMMReg4Float::Max(one, maxc);
     275             :     const auto inv_max_minus_min_times_6_0 =
     276      538808 :         XMMReg4Float::Ternary(XMMReg4Float::Equals(max_minus_min, zero), one,
     277      538808 :                               six * max_minus_min)
     278      538808 :             .inverse();
     279      538808 :     const auto tmp = (g - b) * inv_max_minus_min_times_6_0;
     280      538808 :     h = XMMReg4Float::Ternary(
     281      538808 :         XMMReg4Float::Equals(maxc, b),
     282      538808 :         four_over_six + (r - g) * inv_max_minus_min_times_6_0,
     283      538808 :         XMMReg4Float::Ternary(
     284      538808 :             XMMReg4Float::Equals(maxc, g),
     285      538808 :             two_over_six + (b - r) * inv_max_minus_min_times_6_0,
     286      538808 :             XMMReg4Float::Ternary(XMMReg4Float::Lesser(tmp, zero), tmp + one,
     287      538808 :                                   tmp)));
     288      538808 : }
     289             : #endif
     290             : 
     291             : /************************************************************************/
     292             : /*                         patch_value_line()                           */
     293             : /************************************************************************/
     294             : 
     295             : static
     296             : #ifdef __GNUC__
     297             :     __attribute__((__noinline__))
     298             : #endif
     299             :     void
     300         695 :     patch_value_line(int nCount, const GByte *CPL_RESTRICT pInR,
     301             :                      const GByte *CPL_RESTRICT pInG,
     302             :                      const GByte *CPL_RESTRICT pInB,
     303             :                      const GByte *CPL_RESTRICT pInGray,
     304             :                      GByte *CPL_RESTRICT pOutR, GByte *CPL_RESTRICT pOutG,
     305             :                      GByte *CPL_RESTRICT pOutB)
     306             : {
     307         695 :     int i = 0;
     308             : #ifdef HAVE_SSE2
     309         695 :     const auto zero = XMMReg4Float::Zero();
     310         695 :     const auto one = XMMReg4Float::Set1(1.0f);
     311         695 :     const auto six = XMMReg4Float::Set1(6.0f);
     312         695 :     const auto two_over_six = XMMReg4Float::Set1(2.0f / 6.0f);
     313         695 :     const auto four_over_six = two_over_six + two_over_six;
     314             : 
     315         695 :     constexpr int ELTS = 8;
     316      270099 :     for (; i + (ELTS - 1) < nCount; i += ELTS)
     317             :     {
     318      269404 :         XMMReg4Float h0, s0;
     319      269404 :         XMM_RGB_to_HS(pInR + i, pInG + i, pInB + i, zero, one, six,
     320             :                       two_over_six, four_over_six, h0, s0);
     321      269404 :         XMMReg4Float h1, s1;
     322      269404 :         XMM_RGB_to_HS(pInR + i + ELTS / 2, pInG + i + ELTS / 2,
     323      269404 :                       pInB + i + ELTS / 2, zero, one, six, two_over_six,
     324             :                       four_over_six, h1, s1);
     325             : 
     326      269404 :         XMMReg4Float v0, v1;
     327      269404 :         XMMReg4Float::Load8Val(pInGray + i, v0, v1);
     328             : 
     329      269404 :         const auto half = XMMReg4Float::Set1(0.5f);
     330      269404 :         const auto six_h0 = six * h0;
     331      269404 :         const auto idx0 = six_h0.truncate_to_int();
     332      269404 :         const auto f0 = six_h0 - idx0.to_float();
     333      269404 :         const auto p0 = (v0 * (one - s0) + half).truncate_to_int();
     334      269404 :         const auto q0 = (v0 * (one - s0 * f0) + half).truncate_to_int();
     335      269404 :         const auto t0 = (v0 * (one - s0 * (one - f0)) + half).truncate_to_int();
     336             : 
     337      269404 :         const auto six_h1 = six * h1;
     338      269404 :         const auto idx1 = six_h1.truncate_to_int();
     339      269404 :         const auto f1 = six_h1 - idx1.to_float();
     340      269404 :         const auto p1 = (v1 * (one - s1) + half).truncate_to_int();
     341      269404 :         const auto q1 = (v1 * (one - s1 * f1) + half).truncate_to_int();
     342      269404 :         const auto t1 = (v1 * (one - s1 * (one - f1)) + half).truncate_to_int();
     343             : 
     344      269404 :         const auto idx = XMMReg8Byte::Pack(idx0, idx1);
     345             :         const auto v =
     346      269404 :             XMMReg8Byte::Pack(v0.truncate_to_int(), v1.truncate_to_int());
     347      269404 :         const auto p = XMMReg8Byte::Pack(p0, p1);
     348      269404 :         const auto q = XMMReg8Byte::Pack(q0, q1);
     349      269404 :         const auto t = XMMReg8Byte::Pack(t0, t1);
     350             : 
     351      269404 :         const auto equalsTo0 = XMMReg8Byte::Equals(idx, XMMReg8Byte::Zero());
     352      269404 :         const auto one8Byte = XMMReg8Byte::Set1(1);
     353      269404 :         const auto equalsTo1 = XMMReg8Byte::Equals(idx, one8Byte);
     354      269404 :         const auto two8Byte = one8Byte + one8Byte;
     355      269404 :         const auto equalsTo2 = XMMReg8Byte::Equals(idx, two8Byte);
     356      269404 :         const auto four8Byte = two8Byte + two8Byte;
     357      269404 :         const auto equalsTo4 = XMMReg8Byte::Equals(idx, four8Byte);
     358      269404 :         const auto equalsTo3 = XMMReg8Byte::Equals(idx, four8Byte - one8Byte);
     359             :         // clang-format off
     360      269404 :         if (pOutR)
     361             :         {
     362             :             const auto out_r =
     363             :                 XMMReg8Byte::Ternary(equalsTo0, v,
     364      134702 :                 XMMReg8Byte::Ternary(equalsTo1, q,
     365      134702 :                 XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo2, equalsTo3), p,
     366      269404 :                 XMMReg8Byte::Ternary(equalsTo4, t, v))));
     367      134702 :             out_r.Store8Val(pOutR + i);
     368             :         }
     369      269404 :         if (pOutG)
     370             :         {
     371             :             const auto out_g =
     372             :                 XMMReg8Byte::Ternary(equalsTo0, t,
     373      118318 :                 XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo1, equalsTo2), v,
     374      236636 :                 XMMReg8Byte::Ternary(equalsTo3, q, p)));
     375      118318 :             out_g.Store8Val(pOutG + i);
     376             :         }
     377      269404 :         if (pOutB)
     378             :         {
     379             :             const auto out_b =
     380      118318 :                 XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo0, equalsTo1), p,
     381      118318 :                 XMMReg8Byte::Ternary(equalsTo2, t,
     382      118318 :                 XMMReg8Byte::Ternary(XMMReg8Byte::Or(equalsTo3, equalsTo4),
     383      118318 :                                      v, q)));
     384      118318 :             out_b.Store8Val(pOutB + i);
     385             :         }
     386             :         // clang-format on
     387             :     }
     388             : #endif
     389             : 
     390        2524 :     for (; i < nCount; ++i)
     391             :     {
     392             :         float h, s;
     393        1829 :         rgb_to_hs(pInR[i], pInG[i], pInB[i], &h, &s);
     394        5001 :         hsv_to_rgb(h, s, pInGray[i], pOutR ? pOutR + i : nullptr,
     395        3172 :                    pOutG ? pOutG + i : nullptr, pOutB ? pOutB + i : nullptr);
     396             :     }
     397         695 : }
     398             : 
     399             : /************************************************************************/
     400             : /*                          BlendBand                                   */
     401             : /************************************************************************/
     402             : 
     403             : class BlendBand final : public GDALRasterBand
     404             : {
     405             :   public:
     406         120 :     BlendBand(BlendDataset &oBlendDataset, int nBandIn)
     407         120 :         : m_oBlendDataset(oBlendDataset)
     408             :     {
     409         120 :         nBand = nBandIn;
     410         120 :         nRasterXSize = oBlendDataset.GetRasterXSize();
     411         120 :         nRasterYSize = oBlendDataset.GetRasterYSize();
     412         120 :         oBlendDataset.m_oColorDS.GetRasterBand(1)->GetBlockSize(&nBlockXSize,
     413             :                                                                 &nBlockYSize);
     414         120 :         eDataType = GDT_Byte;
     415         120 :     }
     416             : 
     417          10 :     GDALColorInterp GetColorInterpretation() override
     418             :     {
     419          10 :         if (m_oBlendDataset.GetRasterCount() <= 2 && nBand == 1)
     420           0 :             return GCI_GrayIndex;
     421          10 :         else if (m_oBlendDataset.GetRasterCount() == 2 || nBand == 4)
     422           0 :             return GCI_AlphaBand;
     423             :         else
     424          10 :             return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
     425             :     }
     426             : 
     427          18 :     int GetOverviewCount() override
     428             :     {
     429          18 :         return static_cast<int>(m_oBlendDataset.m_apoOverviews.size());
     430             :     }
     431             : 
     432          14 :     GDALRasterBand *GetOverview(int idx) override
     433             :     {
     434          13 :         return idx >= 0 && idx < GetOverviewCount()
     435          27 :                    ? m_oBlendDataset.m_apoOverviews[idx]->GetRasterBand(nBand)
     436          14 :                    : nullptr;
     437             :     }
     438             : 
     439             :   protected:
     440          62 :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) override
     441             :     {
     442          62 :         int nReqXSize = 0;
     443          62 :         int nReqYSize = 0;
     444          62 :         GetActualBlockSize(nBlockXOff, nBlockYOff, &nReqXSize, &nReqYSize);
     445         124 :         return RasterIO(GF_Read, nBlockXOff * nBlockXSize,
     446          62 :                         nBlockYOff * nBlockYSize, nReqXSize, nReqYSize, pData,
     447          62 :                         nReqXSize, nReqYSize, GDT_Byte, 1, nBlockXSize,
     448         124 :                         nullptr);
     449             :     }
     450             : 
     451             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     452             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     453             :                      GDALDataType eBufType, GSpacing nPixelSpace,
     454             :                      GSpacing nLineSpace,
     455             :                      GDALRasterIOExtraArg *psExtraArg) override;
     456             : 
     457             :   private:
     458             :     BlendDataset &m_oBlendDataset;
     459             : };
     460             : 
     461             : /************************************************************************/
     462             : /*                       BlendDataset::BlendDataset()                   */
     463             : /************************************************************************/
     464             : 
     465          41 : BlendDataset::BlendDataset(GDALDataset &oColorDS, GDALDataset &oOverlayDS,
     466          41 :                            const std::string &sOperator, int nOpacity255Scale)
     467             :     : m_oColorDS(oColorDS), m_oOverlayDS(oOverlayDS), m_operator(sOperator),
     468          41 :       m_opacity255Scale(nOpacity255Scale)
     469             : {
     470          41 :     m_oColorDS.Reference();
     471          41 :     m_oOverlayDS.Reference();
     472             : 
     473          41 :     CPLAssert(oColorDS.GetRasterXSize() == oOverlayDS.GetRasterXSize());
     474          41 :     CPLAssert(oColorDS.GetRasterYSize() == oOverlayDS.GetRasterYSize());
     475          41 :     nRasterXSize = oColorDS.GetRasterXSize();
     476          41 :     nRasterYSize = oColorDS.GetRasterYSize();
     477          41 :     const int nOvrCount = oOverlayDS.GetRasterBand(1)->GetOverviewCount();
     478          41 :     bool bCanCreateOvr = true;
     479         161 :     for (int iBand = 1; iBand <= oColorDS.GetRasterCount(); ++iBand)
     480             :     {
     481         120 :         SetBand(iBand, std::make_unique<BlendBand>(*this, iBand));
     482         120 :         bCanCreateOvr =
     483         120 :             bCanCreateOvr &&
     484         120 :             (iBand > oColorDS.GetRasterCount() ||
     485         360 :              oColorDS.GetRasterBand(iBand)->GetOverviewCount() == nOvrCount) &&
     486         120 :             (iBand > oOverlayDS.GetRasterCount() ||
     487          67 :              oOverlayDS.GetRasterBand(iBand)->GetOverviewCount() == nOvrCount);
     488             :         const int nColorBandxIdx =
     489         120 :             iBand <= oColorDS.GetRasterCount() ? iBand : 1;
     490             :         const int nOverlayBandIdx =
     491         120 :             iBand <= oOverlayDS.GetRasterCount() ? iBand : 1;
     492         140 :         for (int iOvr = 0; iOvr < nOvrCount && bCanCreateOvr; ++iOvr)
     493             :         {
     494             :             const auto poColorOvrBand =
     495          20 :                 oColorDS.GetRasterBand(nColorBandxIdx)->GetOverview(iOvr);
     496             :             const auto poGSOvrBand =
     497          20 :                 oOverlayDS.GetRasterBand(nOverlayBandIdx)->GetOverview(iOvr);
     498          20 :             bCanCreateOvr =
     499          20 :                 poColorOvrBand->GetDataset() != &oColorDS &&
     500          20 :                 poColorOvrBand->GetDataset() == oColorDS.GetRasterBand(1)
     501          20 :                                                     ->GetOverview(iOvr)
     502          20 :                                                     ->GetDataset() &&
     503          20 :                 poGSOvrBand->GetDataset() != &oOverlayDS &&
     504          20 :                 poGSOvrBand->GetDataset() == oOverlayDS.GetRasterBand(1)
     505          20 :                                                  ->GetOverview(iOvr)
     506          20 :                                                  ->GetDataset() &&
     507          60 :                 poColorOvrBand->GetXSize() == poGSOvrBand->GetXSize() &&
     508          20 :                 poColorOvrBand->GetYSize() == poGSOvrBand->GetYSize();
     509             :         }
     510             :     }
     511             : 
     512          41 :     SetDescription(CPLSPrintf("Blend %s width %s", m_oColorDS.GetDescription(),
     513          41 :                               m_oOverlayDS.GetDescription()));
     514          41 :     if (nBands > 1)
     515             :     {
     516          34 :         SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
     517             :     }
     518             : 
     519          41 :     if (bCanCreateOvr)
     520             :     {
     521          46 :         for (int iOvr = 0; iOvr < nOvrCount; ++iOvr)
     522             :         {
     523           5 :             m_apoOverviews.push_back(std::make_unique<BlendDataset>(
     524           5 :                 *(oColorDS.GetRasterBand(1)->GetOverview(iOvr)->GetDataset()),
     525           5 :                 *(oOverlayDS.GetRasterBand(1)->GetOverview(iOvr)->GetDataset()),
     526           5 :                 m_operator, nOpacity255Scale));
     527             :         }
     528             :     }
     529          41 : }
     530             : 
     531             : /************************************************************************/
     532             : /*                     ~BlendDataset::BlendDataset()                    */
     533             : /************************************************************************/
     534             : 
     535          82 : BlendDataset::~BlendDataset()
     536             : {
     537          41 :     m_oColorDS.ReleaseRef();
     538          41 :     m_oOverlayDS.ReleaseRef();
     539          82 : }
     540             : 
     541             : /************************************************************************/
     542             : /*                  BlendDataset::AcquireSourcePixels()                 */
     543             : /************************************************************************/
     544             : 
     545         347 : bool BlendDataset::AcquireSourcePixels(int nXOff, int nYOff, int nXSize,
     546             :                                        int nYSize, int nBufXSize, int nBufYSize,
     547             :                                        GDALRasterIOExtraArg *psExtraArg)
     548             : {
     549         347 :     if (nXOff == m_nCachedXOff && nYOff == m_nCachedYOff &&
     550          92 :         nXSize == m_nCachedXSize && nYSize == m_nCachedYSize &&
     551          55 :         nBufXSize == m_nCachedBufXSize && nBufYSize == m_nCachedBufYSize &&
     552          55 :         psExtraArg->eResampleAlg == m_sCachedExtraArg.eResampleAlg &&
     553          55 :         psExtraArg->bFloatingPointWindowValidity ==
     554          55 :             m_sCachedExtraArg.bFloatingPointWindowValidity &&
     555          55 :         (!psExtraArg->bFloatingPointWindowValidity ||
     556           0 :          (psExtraArg->dfXOff == m_sCachedExtraArg.dfXOff &&
     557           0 :           psExtraArg->dfYOff == m_sCachedExtraArg.dfYOff &&
     558           0 :           psExtraArg->dfXSize == m_sCachedExtraArg.dfXSize &&
     559           0 :           psExtraArg->dfYSize == m_sCachedExtraArg.dfYSize)))
     560             :     {
     561          55 :         return !m_abyBuffer.empty();
     562             :     }
     563             : 
     564         292 :     const int nColorCount = m_oColorDS.GetRasterCount();
     565         292 :     const int nOverlayCount = m_oOverlayDS.GetRasterCount();
     566         292 :     const int nCompsInBuffer = nColorCount + nOverlayCount;
     567         292 :     assert(nCompsInBuffer > 0);
     568             : 
     569         584 :     if (static_cast<size_t>(nBufXSize) >
     570         292 :         std::numeric_limits<size_t>::max() / nBufYSize / nCompsInBuffer)
     571             :     {
     572           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     573             :                  "Out of memory allocating temporary buffer");
     574           0 :         m_abyBuffer.clear();
     575           0 :         m_ioError = true;
     576           0 :         return false;
     577             :     }
     578             : 
     579         292 :     const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
     580             :     try
     581             :     {
     582         292 :         if (m_abyBuffer.size() < nPixelCount * nCompsInBuffer)
     583          36 :             m_abyBuffer.resize(nPixelCount * nCompsInBuffer);
     584             :     }
     585           4 :     catch (const std::exception &)
     586             :     {
     587           4 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     588             :                  "Out of memory allocating temporary buffer");
     589           4 :         m_abyBuffer.clear();
     590           4 :         m_ioError = true;
     591           4 :         return false;
     592             :     }
     593             : 
     594             :     const bool bOK =
     595         288 :         (m_oColorDS.RasterIO(
     596         288 :              GF_Read, nXOff, nYOff, nXSize, nYSize, m_abyBuffer.data(),
     597             :              nBufXSize, nBufYSize, GDT_Byte, nColorCount, nullptr, 1, nBufXSize,
     598         574 :              static_cast<GSpacing>(nPixelCount), psExtraArg) == CE_None &&
     599         286 :          m_oOverlayDS.RasterIO(
     600             :              GF_Read, nXOff, nYOff, nXSize, nYSize,
     601         286 :              m_abyBuffer.data() + nPixelCount * nColorCount, nBufXSize,
     602             :              nBufYSize, GDT_Byte, nOverlayCount, nullptr, 1, nBufXSize,
     603         288 :              static_cast<GSpacing>(nPixelCount), psExtraArg) == CE_None);
     604         288 :     if (bOK)
     605             :     {
     606         286 :         m_nCachedXOff = nXOff;
     607         286 :         m_nCachedYOff = nYOff;
     608         286 :         m_nCachedXSize = nXSize;
     609         286 :         m_nCachedYSize = nYSize;
     610         286 :         m_nCachedBufXSize = nBufXSize;
     611         286 :         m_nCachedBufYSize = nBufYSize;
     612         286 :         m_sCachedExtraArg = *psExtraArg;
     613             :     }
     614             :     else
     615             :     {
     616           2 :         m_abyBuffer.clear();
     617           2 :         m_ioError = true;
     618             :     }
     619         288 :     return bOK;
     620             : }
     621             : 
     622             : /************************************************************************/
     623             : /*                             gTabInvDstA                              */
     624             : /************************************************************************/
     625             : 
     626             : constexpr int SHIFT_DIV_DSTA = 8;
     627             : 
     628             : // Table of (255 * 256 + k/2) / k values for k in [0,255]
     629             : constexpr auto gTabInvDstA = []()
     630             : {
     631             :     std::array<uint16_t, 256> arr{};
     632             : 
     633             :     arr[0] = 0;
     634             :     for (int k = 1; k <= 255; ++k)
     635             :     {
     636             :         arr[k] = static_cast<uint16_t>(((255 << SHIFT_DIV_DSTA) + (k / 2)) / k);
     637             :     }
     638             : 
     639             :     return arr;
     640             : }();
     641             : 
     642             : /************************************************************************/
     643             : /*                         BlendSrcOverRGBA_SSE2()                      */
     644             : /************************************************************************/
     645             : 
     646             : #ifdef HAVE_SSE2
     647             : #if defined(__GNUC__)
     648             : __attribute__((noinline))
     649             : #endif
     650             : static int
     651           1 : BlendSrcOverRGBA_SSE2(const GByte *CPL_RESTRICT pabyR,
     652             :                       const GByte *CPL_RESTRICT pabyG,
     653             :                       const GByte *CPL_RESTRICT pabyB,
     654             :                       const GByte *CPL_RESTRICT pabyA,
     655             :                       const GByte *CPL_RESTRICT pabyOverlayR,
     656             :                       const GByte *CPL_RESTRICT pabyOverlayG,
     657             :                       const GByte *CPL_RESTRICT pabyOverlayB,
     658             :                       const GByte *CPL_RESTRICT pabyOverlayA,
     659             :                       GByte *CPL_RESTRICT pabyDst, GSpacing nBandSpace, int N,
     660             :                       GByte nOpacity)
     661             : {
     662             :     // See scalar code after call to BlendSrcOverRGBA_SSE2() below for the
     663             :     // non-obfuscated formulas...
     664             : 
     665           0 :     const auto load_and_unpack = [](const void *p)
     666             :     {
     667           0 :         const auto zero = _mm_setzero_si128();
     668           0 :         auto overlayA = _mm_loadu_si128(reinterpret_cast<const __m128i *>(p));
     669           0 :         return std::make_pair(_mm_unpacklo_epi8(overlayA, zero),
     670           0 :                               _mm_unpackhi_epi8(overlayA, zero));
     671             :     };
     672             : 
     673           0 :     const auto pack_and_store = [](void *p, __m128i lo, __m128i hi) {
     674           0 :         _mm_storeu_si128(reinterpret_cast<__m128i *>(p),
     675             :                          _mm_packus_epi16(lo, hi));
     676           0 :     };
     677             : 
     678           0 :     const auto mul16bit_8bit_result = [](__m128i a, __m128i b)
     679             :     {
     680           0 :         const auto r255 = _mm_set1_epi16(255);
     681           0 :         return _mm_srli_epi16(_mm_add_epi16(_mm_mullo_epi16(a, b), r255), 8);
     682             :     };
     683             : 
     684           2 :     const auto opacity = _mm_set1_epi16(nOpacity);
     685           1 :     const auto r255 = _mm_set1_epi16(255);
     686             :     const int16_t *tabInvDstASigned =
     687           1 :         reinterpret_cast<const int16_t *>(gTabInvDstA.data());
     688           1 :     constexpr int REG_WIDTH = static_cast<int>(sizeof(opacity));
     689             : 
     690           1 :     int i = 0;
     691           1 :     for (; i <= N - REG_WIDTH; i += REG_WIDTH)
     692             :     {
     693           0 :         auto [overlayA_lo, overlayA_hi] = load_and_unpack(pabyOverlayA + i);
     694           0 :         auto [srcA_lo, srcA_hi] = load_and_unpack(pabyA + i);
     695           0 :         overlayA_lo = mul16bit_8bit_result(overlayA_lo, opacity);
     696           0 :         overlayA_hi = mul16bit_8bit_result(overlayA_hi, opacity);
     697             :         auto srcAMul255MinusOverlayA_lo =
     698           0 :             mul16bit_8bit_result(srcA_lo, _mm_sub_epi16(r255, overlayA_lo));
     699             :         auto srcAMul255MinusOverlayA_hi =
     700           0 :             mul16bit_8bit_result(srcA_hi, _mm_sub_epi16(r255, overlayA_hi));
     701           0 :         auto dstA_lo = _mm_add_epi16(overlayA_lo, srcAMul255MinusOverlayA_lo);
     702           0 :         auto dstA_hi = _mm_add_epi16(overlayA_hi, srcAMul255MinusOverlayA_hi);
     703             : 
     704             :         // The & 0xff should not be necessary. This is mostly a safety
     705             :         // belt if the above math yields a result outside [0, 255]...
     706           0 :         dstA_lo = _mm_and_si128(dstA_lo, r255);
     707           0 :         dstA_hi = _mm_and_si128(dstA_hi, r255);
     708             : 
     709             :         // This would be the equivalent of a "_mm_i16gather_epi16" operation
     710             :         // which does not exist...
     711             :         // invDstA_{i} = [tabInvDstASigned[dstA_{i}] for i in range(8)]
     712           0 :         auto invDstA_lo = _mm_undefined_si128();
     713           0 :         auto invDstA_hi = _mm_undefined_si128();
     714             : #define SET_INVDSTA(k)                                                         \
     715             :     do                                                                         \
     716             :     {                                                                          \
     717             :         const int idxLo = _mm_extract_epi16(dstA_lo, k);                       \
     718             :         const int idxHi = _mm_extract_epi16(dstA_hi, k);                       \
     719             :         invDstA_lo = _mm_insert_epi16(invDstA_lo, tabInvDstASigned[idxLo], k); \
     720             :         invDstA_hi = _mm_insert_epi16(invDstA_hi, tabInvDstASigned[idxHi], k); \
     721             :     } while (0)
     722             : 
     723           0 :         SET_INVDSTA(0);
     724           0 :         SET_INVDSTA(1);
     725           0 :         SET_INVDSTA(2);
     726           0 :         SET_INVDSTA(3);
     727           0 :         SET_INVDSTA(4);
     728           0 :         SET_INVDSTA(5);
     729           0 :         SET_INVDSTA(6);
     730           0 :         SET_INVDSTA(7);
     731             : 
     732           0 :         pack_and_store(pabyDst + i + 3 * nBandSpace, dstA_lo, dstA_hi);
     733             : 
     734             : #define PROCESS_COMPONENT(pabySrc, pabyOverlay, iBand)                         \
     735             :     do                                                                         \
     736             :     {                                                                          \
     737             :         auto [src_lo, src_hi] = load_and_unpack((pabySrc) + i);                \
     738             :         auto [overlay_lo, overlay_hi] = load_and_unpack((pabyOverlay) + i);    \
     739             :         auto dst_lo = _mm_srli_epi16(                                          \
     740             :             _mm_add_epi16(                                                     \
     741             :                 _mm_add_epi16(                                                 \
     742             :                     _mm_mullo_epi16(overlay_lo, overlayA_lo),                  \
     743             :                     _mm_mullo_epi16(src_lo, srcAMul255MinusOverlayA_lo)),      \
     744             :                 r255),                                                         \
     745             :             8);                                                                \
     746             :         auto dst_hi = _mm_srli_epi16(                                          \
     747             :             _mm_add_epi16(                                                     \
     748             :                 _mm_add_epi16(                                                 \
     749             :                     _mm_mullo_epi16(overlay_hi, overlayA_hi),                  \
     750             :                     _mm_mullo_epi16(src_hi, srcAMul255MinusOverlayA_hi)),      \
     751             :                 r255),                                                         \
     752             :             8);                                                                \
     753             :         dst_lo = mul16bit_8bit_result(dst_lo, invDstA_lo);                     \
     754             :         dst_hi = mul16bit_8bit_result(dst_hi, invDstA_hi);                     \
     755             :         pack_and_store(pabyDst + i + (iBand)*nBandSpace, dst_lo, dst_hi);      \
     756             :     } while (0)
     757             : 
     758           0 :         PROCESS_COMPONENT(pabyR, pabyOverlayR, 0);
     759           0 :         PROCESS_COMPONENT(pabyG, pabyOverlayG, 1);
     760           0 :         PROCESS_COMPONENT(pabyB, pabyOverlayB, 2);
     761             :     }
     762           1 :     return i;
     763             : }
     764             : #endif
     765             : 
     766             : template <bool bPixelSpaceIsOne>
     767             : #if defined(__GNUC__)
     768             : __attribute__((noinline))
     769             : #endif
     770             : static void
     771           1 : BlendSrcOverRGBA_Generic(const GByte *CPL_RESTRICT pabyR,
     772             :                          const GByte *CPL_RESTRICT pabyG,
     773             :                          const GByte *CPL_RESTRICT pabyB,
     774             :                          const GByte *CPL_RESTRICT pabyA,
     775             :                          const GByte *CPL_RESTRICT pabyOverlayR,
     776             :                          const GByte *CPL_RESTRICT pabyOverlayG,
     777             :                          const GByte *CPL_RESTRICT pabyOverlayB,
     778             :                          const GByte *CPL_RESTRICT pabyOverlayA,
     779             :                          GByte *CPL_RESTRICT pabyDst,
     780             :                          [[maybe_unused]] GSpacing nPixelSpace,
     781             :                          GSpacing nBandSpace, int i, int N, GByte nOpacity)
     782             : {
     783             : #if !(defined(HAVE_SSE2) && defined(__GNUC__))
     784             :     if constexpr (!bPixelSpaceIsOne)
     785             :     {
     786             :         assert(nPixelSpace != 1);
     787             :     }
     788             : #endif
     789           1 :     [[maybe_unused]] GSpacing nDstOffset = 0;
     790             : #if defined(HAVE_SSE2) && defined(__clang__) && !defined(__INTEL_CLANG_COMPILER)
     791             : // No need to vectorize for SSE2 and clang
     792             : #pragma clang loop vectorize(disable)
     793             : #endif
     794           2 :     for (; i < N; ++i)
     795             :     {
     796           1 :         const GByte nOverlayR = pabyOverlayR[i];
     797           1 :         const GByte nOverlayG = pabyOverlayG[i];
     798           1 :         const GByte nOverlayB = pabyOverlayB[i];
     799           1 :         const GByte nOverlayA =
     800           1 :             static_cast<GByte>((pabyOverlayA[i] * nOpacity + 255) / 256);
     801           1 :         const GByte nR = pabyR[i];
     802           1 :         const GByte nG = pabyG[i];
     803           1 :         const GByte nB = pabyB[i];
     804           1 :         const GByte nA = pabyA[i];
     805           1 :         const GByte nSrcAMul255MinusOverlayA =
     806           1 :             static_cast<GByte>((nA * (255 - nOverlayA) + 255) / 256);
     807           1 :         const GByte nDstA =
     808             :             static_cast<GByte>(nOverlayA + nSrcAMul255MinusOverlayA);
     809           1 :         GByte nDstR = static_cast<GByte>(
     810           1 :             (nOverlayR * nOverlayA + nR * nSrcAMul255MinusOverlayA + 255) /
     811             :             256);
     812           1 :         GByte nDstG = static_cast<GByte>(
     813           1 :             (nOverlayG * nOverlayA + nG * nSrcAMul255MinusOverlayA + 255) /
     814             :             256);
     815           1 :         GByte nDstB = static_cast<GByte>(
     816           1 :             (nOverlayB * nOverlayA + nB * nSrcAMul255MinusOverlayA + 255) /
     817             :             256);
     818             :         // nInvDstA = (255 << SHIFT_DIV_DSTA) / nDstA;
     819           1 :         const uint16_t nInvDstA = gTabInvDstA[nDstA];
     820           1 :         constexpr unsigned ROUND_OFFSET_DIV_DSTA = ((1 << SHIFT_DIV_DSTA) - 1);
     821           1 :         nDstR = static_cast<GByte>((nDstR * nInvDstA + ROUND_OFFSET_DIV_DSTA) >>
     822             :                                    SHIFT_DIV_DSTA);
     823           1 :         nDstG = static_cast<GByte>((nDstG * nInvDstA + ROUND_OFFSET_DIV_DSTA) >>
     824             :                                    SHIFT_DIV_DSTA);
     825           1 :         nDstB = static_cast<GByte>((nDstB * nInvDstA + ROUND_OFFSET_DIV_DSTA) >>
     826             :                                    SHIFT_DIV_DSTA);
     827             :         if constexpr (bPixelSpaceIsOne)
     828             :         {
     829             :             pabyDst[i] = nDstR;
     830             :             pabyDst[i + nBandSpace] = nDstG;
     831             :             pabyDst[i + 2 * nBandSpace] = nDstB;
     832             :             pabyDst[i + 3 * nBandSpace] = nDstA;
     833             :         }
     834             :         else
     835             :         {
     836           1 :             pabyDst[nDstOffset] = nDstR;
     837           1 :             pabyDst[nDstOffset + nBandSpace] = nDstG;
     838           1 :             pabyDst[nDstOffset + 2 * nBandSpace] = nDstB;
     839           1 :             pabyDst[nDstOffset + 3 * nBandSpace] = nDstA;
     840           1 :             nDstOffset += nPixelSpace;
     841             :         }
     842             :     }
     843           1 : }
     844             : 
     845             : /************************************************************************/
     846             : /*                       BlendDataset::IRasterIO()                      */
     847             : /************************************************************************/
     848             : 
     849         229 : CPLErr BlendDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     850             :                                int nXSize, int nYSize, void *pData,
     851             :                                int nBufXSize, int nBufYSize,
     852             :                                GDALDataType eBufType, int nBandCount,
     853             :                                BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     854             :                                GSpacing nLineSpace, GSpacing nBandSpace,
     855             :                                GDALRasterIOExtraArg *psExtraArg)
     856             : {
     857             :     // Try to pass the request to the most appropriate overview dataset.
     858         229 :     if (nBufXSize < nXSize && nBufYSize < nYSize)
     859             :     {
     860           2 :         int bTried = FALSE;
     861           2 :         const CPLErr eErr = TryOverviewRasterIO(
     862             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     863             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
     864             :             nBandSpace, psExtraArg, &bTried);
     865           2 :         if (bTried)
     866           2 :             return eErr;
     867             :     }
     868             : 
     869         227 :     GByte *const CPL_RESTRICT pabyDst = static_cast<GByte *>(pData);
     870         227 :     const int nColorCount = m_oColorDS.GetRasterCount();
     871         227 :     const int nOverlayCount = m_oOverlayDS.GetRasterCount();
     872         419 :     if (nOverlayCount == 1 && m_opacity255Scale == 255 &&
     873         410 :         m_operator == HSV_VALUE && eRWFlag == GF_Read && eBufType == GDT_Byte &&
     874         643 :         nBandCount == nBands && IsAllBands(nBands, panBandMap) &&
     875         203 :         AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
     876             :                             psExtraArg))
     877             :     {
     878         197 :         const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
     879         197 :         const GByte *pabyR = m_abyBuffer.data();
     880         197 :         const GByte *pabyG = m_abyBuffer.data() + nPixelCount;
     881         197 :         const GByte *pabyB = m_abyBuffer.data() + nPixelCount * 2;
     882         197 :         const GByte *pabyValue = m_abyBuffer.data() + nPixelCount * nColorCount;
     883         197 :         size_t nSrcIdx = 0;
     884         516 :         for (int j = 0; j < nBufYSize; ++j)
     885             :         {
     886         319 :             auto nDstOffset = j * nLineSpace;
     887         319 :             if (nPixelSpace == 1 && nLineSpace >= nPixelSpace * nBufXSize &&
     888         317 :                 nBandSpace >= nLineSpace * nBufYSize)
     889             :             {
     890         317 :                 patch_value_line(nBufXSize, pabyR + nSrcIdx, pabyG + nSrcIdx,
     891             :                                  pabyB + nSrcIdx, pabyValue + nSrcIdx,
     892         317 :                                  pabyDst + nDstOffset,
     893         317 :                                  pabyDst + nDstOffset + nBandSpace,
     894         317 :                                  pabyDst + nDstOffset + 2 * nBandSpace);
     895         317 :                 nSrcIdx += nBufXSize;
     896             :             }
     897             :             else
     898             :             {
     899      262146 :                 for (int i = 0; i < nBufXSize;
     900      262144 :                      ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
     901             :                 {
     902             :                     float h, s;
     903      262144 :                     rgb_to_hs(pabyR[nSrcIdx], pabyG[nSrcIdx], pabyB[nSrcIdx],
     904             :                               &h, &s);
     905      262144 :                     hsv_to_rgb(h, s, pabyValue[nSrcIdx],
     906      262144 :                                &pabyDst[nDstOffset + 0 * nBandSpace],
     907      262144 :                                &pabyDst[nDstOffset + 1 * nBandSpace],
     908      262144 :                                &pabyDst[nDstOffset + 2 * nBandSpace]);
     909             :                 }
     910             :             }
     911             :         }
     912         197 :         if (nColorCount == 4)
     913             :         {
     914         394 :             for (int j = 0; j < nBufYSize; ++j)
     915             :             {
     916         198 :                 auto nDstOffset = 3 * nBandSpace + j * nLineSpace;
     917         198 :                 const GByte *pabyA = m_abyBuffer.data() + nPixelCount * 3;
     918         198 :                 GDALCopyWords64(pabyA, GDT_Byte, 1, pabyDst + nDstOffset,
     919             :                                 GDT_Byte, static_cast<int>(nPixelSpace),
     920             :                                 nBufXSize);
     921             :             }
     922             :         }
     923             : 
     924         197 :         return CE_None;
     925             :     }
     926           3 :     else if (nOverlayCount == 4 && nColorCount == 4 && m_operator == SRC_OVER &&
     927           1 :              eRWFlag == GF_Read && eBufType == GDT_Byte &&
     928          34 :              nBandCount == nBands && IsAllBands(nBands, panBandMap) &&
     929           1 :              AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize, nBufXSize,
     930             :                                  nBufYSize, psExtraArg))
     931             :     {
     932           1 :         const GByte nOpacity = static_cast<GByte>(m_opacity255Scale);
     933           1 :         const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
     934           1 :         const GByte *CPL_RESTRICT pabyR = m_abyBuffer.data();
     935           1 :         const GByte *CPL_RESTRICT pabyG = m_abyBuffer.data() + nPixelCount;
     936           1 :         const GByte *CPL_RESTRICT pabyB = m_abyBuffer.data() + nPixelCount * 2;
     937           1 :         const GByte *CPL_RESTRICT pabyA = m_abyBuffer.data() + nPixelCount * 3;
     938             :         const GByte *CPL_RESTRICT pabyOverlayR =
     939           1 :             m_abyBuffer.data() + nPixelCount * nColorCount;
     940             :         const GByte *CPL_RESTRICT pabyOverlayG =
     941           1 :             m_abyBuffer.data() + nPixelCount * (nColorCount + 1);
     942             :         const GByte *CPL_RESTRICT pabyOverlayB =
     943           1 :             m_abyBuffer.data() + nPixelCount * (nColorCount + 2);
     944             :         const GByte *CPL_RESTRICT pabyOverlayA =
     945           1 :             m_abyBuffer.data() + nPixelCount * (nColorCount + 3);
     946           1 :         size_t nSrcIdx = 0;
     947           2 :         for (int j = 0; j < nBufYSize; ++j)
     948             :         {
     949           1 :             auto nDstOffset = j * nLineSpace;
     950           1 :             int i = 0;
     951             : #ifdef HAVE_SSE2
     952           1 :             if (nPixelSpace == 1)
     953             :             {
     954           2 :                 i = BlendSrcOverRGBA_SSE2(
     955             :                     pabyR + nSrcIdx, pabyG + nSrcIdx, pabyB + nSrcIdx,
     956             :                     pabyA + nSrcIdx, pabyOverlayR + nSrcIdx,
     957             :                     pabyOverlayG + nSrcIdx, pabyOverlayB + nSrcIdx,
     958           1 :                     pabyOverlayA + nSrcIdx, pabyDst + nDstOffset, nBandSpace,
     959             :                     nBufXSize, nOpacity);
     960           1 :                 nSrcIdx += i;
     961           1 :                 nDstOffset += i;
     962             :             }
     963             : #endif
     964             : #if !(defined(HAVE_SSE2) && defined(__GNUC__))
     965             :             if (nPixelSpace == 1)
     966             :             {
     967             :                 // Note: clang 20 does a rather good job at autovectorizing
     968             :                 // for SSE2, but BlendSrcOverRGBA_SSE2() performs better.
     969             :                 BlendSrcOverRGBA_Generic</* bPixelSpaceIsOne = */ true>(
     970             :                     pabyR + nSrcIdx, pabyG + nSrcIdx, pabyB + nSrcIdx,
     971             :                     pabyA + nSrcIdx, pabyOverlayR + nSrcIdx,
     972             :                     pabyOverlayG + nSrcIdx, pabyOverlayB + nSrcIdx,
     973             :                     pabyOverlayA + nSrcIdx, pabyDst + nDstOffset,
     974             :                     /*nPixelSpace = */ 1, nBandSpace, i, nBufXSize, nOpacity);
     975             :             }
     976             :             else
     977             : #endif
     978             :             {
     979           1 :                 BlendSrcOverRGBA_Generic</* bPixelSpaceIsOne = */ false>(
     980             :                     pabyR + nSrcIdx, pabyG + nSrcIdx, pabyB + nSrcIdx,
     981             :                     pabyA + nSrcIdx, pabyOverlayR + nSrcIdx,
     982             :                     pabyOverlayG + nSrcIdx, pabyOverlayB + nSrcIdx,
     983           1 :                     pabyOverlayA + nSrcIdx, pabyDst + nDstOffset, nPixelSpace,
     984             :                     nBandSpace, i, nBufXSize, nOpacity);
     985             :             }
     986           1 :             nSrcIdx += nBufXSize - i;
     987             :         }
     988           1 :         return CE_None;
     989             :     }
     990          29 :     else if (m_ioError)
     991             :     {
     992           6 :         return CE_Failure;
     993             :     }
     994             :     else
     995             :     {
     996          23 :         const CPLErr eErr = GDALDataset::IRasterIO(
     997             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     998             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
     999             :             nBandSpace, psExtraArg);
    1000          23 :         m_ioError = eErr != CE_None;
    1001          23 :         return eErr;
    1002             :     }
    1003             : }
    1004             : 
    1005             : /************************************************************************/
    1006             : /*                        SrcOverRGBOneComponent()                      */
    1007             : /************************************************************************/
    1008             : 
    1009             : // GCC and clang do a god job a auto vectorizing the below function
    1010             : #if defined(__GNUC__) && !defined(__clang__)
    1011             : __attribute__((optimize("tree-vectorize")))
    1012             : #endif
    1013             : static void
    1014          12 : SrcOverRGB(const uint8_t *const __restrict pabyOverlay,
    1015             :            const uint8_t *const __restrict pabySrc,
    1016             :            uint8_t *const __restrict pabyDst, const size_t N,
    1017             :            const uint8_t nOpacity)
    1018             : {
    1019         915 :     for (size_t i = 0; i < N; ++i)
    1020             :     {
    1021         903 :         const uint8_t nOverlay = pabyOverlay[i];
    1022         903 :         const uint8_t nSrc = pabySrc[i];
    1023         903 :         pabyDst[i] = static_cast<uint8_t>(
    1024         903 :             (nOverlay * nOpacity + nSrc * (255 - nOpacity) + 255) / 256);
    1025             :     }
    1026          12 : }
    1027             : 
    1028             : /************************************************************************/
    1029             : /*                        BlendBand::IRasterIO()                        */
    1030             : /************************************************************************/
    1031             : 
    1032         212 : CPLErr BlendBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
    1033             :                             int nXSize, int nYSize, void *pData, int nBufXSize,
    1034             :                             int nBufYSize, GDALDataType eBufType,
    1035             :                             GSpacing nPixelSpace, GSpacing nLineSpace,
    1036             :                             GDALRasterIOExtraArg *psExtraArg)
    1037             : {
    1038             :     // Try to pass the request to the most appropriate overview dataset.
    1039         212 :     if (nBufXSize < nXSize && nBufYSize < nYSize)
    1040             :     {
    1041           1 :         int bTried = FALSE;
    1042           1 :         const CPLErr eErr = TryOverviewRasterIO(
    1043             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1044             :             eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
    1045           1 :         if (bTried)
    1046           1 :             return eErr;
    1047             :     }
    1048             : 
    1049         211 :     const size_t nPixelCount = static_cast<size_t>(nBufXSize) * nBufYSize;
    1050         211 :     const int nColorCount = m_oBlendDataset.m_oColorDS.GetRasterCount();
    1051         211 :     const int nOverlayCount = m_oBlendDataset.m_oOverlayDS.GetRasterCount();
    1052         211 :     if (nBand == 4 && m_oBlendDataset.m_operator == HSV_VALUE)
    1053             :     {
    1054           9 :         if (nColorCount == 3)
    1055             :         {
    1056           0 :             GByte ch = 255;
    1057           0 :             for (int iY = 0; iY < nBufYSize; ++iY)
    1058             :             {
    1059           0 :                 GDALCopyWords64(&ch, GDT_Byte, 0,
    1060           0 :                                 static_cast<GByte *>(pData) + iY * nLineSpace,
    1061             :                                 eBufType, static_cast<int>(nPixelSpace),
    1062             :                                 nBufXSize);
    1063             :             }
    1064           0 :             return CE_None;
    1065             :         }
    1066             :         else
    1067             :         {
    1068           9 :             CPLAssert(nColorCount == 4);
    1069           9 :             return m_oBlendDataset.m_oColorDS.GetRasterBand(4)->RasterIO(
    1070             :                 eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
    1071           9 :                 nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg);
    1072             :         }
    1073             :     }
    1074          43 :     else if (nOverlayCount == 3 && nColorCount == 3 &&
    1075          30 :              m_oBlendDataset.m_operator == SRC_OVER && eRWFlag == GF_Read &&
    1076         227 :              eBufType == GDT_Byte &&
    1077          12 :              m_oBlendDataset.AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize,
    1078             :                                                  nBufXSize, nBufYSize,
    1079             :                                                  psExtraArg))
    1080             :     {
    1081          12 :         const int nOpacity = m_oBlendDataset.m_opacity255Scale;
    1082             :         const GByte *const CPL_RESTRICT pabySrc =
    1083          12 :             m_oBlendDataset.m_abyBuffer.data() + nPixelCount * (nBand - 1);
    1084             :         const GByte *const CPL_RESTRICT pabyOverlay =
    1085          12 :             m_oBlendDataset.m_abyBuffer.data() +
    1086          12 :             nPixelCount * (nColorCount + nBand - 1);
    1087          12 :         GByte *const CPL_RESTRICT pabyDst = static_cast<GByte *>(pData);
    1088          12 :         size_t nSrcIdx = 0;
    1089          24 :         for (int j = 0; j < nBufYSize; ++j)
    1090             :         {
    1091          12 :             auto nDstOffset = j * nLineSpace;
    1092          12 :             if (nPixelSpace == 1)
    1093             :             {
    1094          12 :                 SrcOverRGB(pabyOverlay + nSrcIdx, pabySrc + nSrcIdx,
    1095          12 :                            pabyDst + nDstOffset, nBufXSize,
    1096             :                            static_cast<uint8_t>(nOpacity));
    1097          12 :                 nSrcIdx += nBufXSize;
    1098             :             }
    1099             :             else
    1100             :             {
    1101           0 :                 for (int i = 0; i < nBufXSize;
    1102           0 :                      ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
    1103             :                 {
    1104           0 :                     const int nOverlay = pabyOverlay[nSrcIdx];
    1105           0 :                     const int nSrc = pabySrc[nSrcIdx];
    1106           0 :                     pabyDst[nDstOffset] = static_cast<GByte>(
    1107           0 :                         (nOverlay * nOpacity + nSrc * (255 - nOpacity) + 255) /
    1108             :                         256);
    1109             :                 }
    1110             :             }
    1111             :         }
    1112          12 :         return CE_None;
    1113             :     }
    1114         321 :     else if (eRWFlag == GF_Read && eBufType == GDT_Byte &&
    1115         131 :              m_oBlendDataset.AcquireSourcePixels(nXOff, nYOff, nXSize, nYSize,
    1116             :                                                  nBufXSize, nBufYSize,
    1117             :                                                  psExtraArg))
    1118             :     {
    1119         131 :         GByte *pabyDst = static_cast<GByte *>(pData);
    1120         131 :         if (m_oBlendDataset.m_operator == SRC_OVER)
    1121             :         {
    1122           3 :             const auto RGBToGrayScale = [](int R, int G, int B)
    1123             :             {
    1124             :                 // Equivalent to R * 0.299 + G * 0.587 + B * 0.114
    1125             :                 // but using faster computation
    1126           3 :                 return (R * 306 + G * 601 + B * 117) / 1024;
    1127             :             };
    1128             : 
    1129             :             const GByte *paby =
    1130          83 :                 (nBand <= nColorCount) ? m_oBlendDataset.m_abyBuffer.data() +
    1131          83 :                                              nPixelCount * (nBand - 1)
    1132           0 :                 : (nBand == 4 && nColorCount == 2)
    1133           0 :                     ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount
    1134          83 :                     : nullptr;
    1135             :             const GByte *pabyA =
    1136             :                 (nColorCount == 4)
    1137          87 :                     ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 3
    1138             :                 : nColorCount == 2
    1139           4 :                     ? m_oBlendDataset.m_abyBuffer.data() + nPixelCount
    1140          83 :                     : nullptr;
    1141             :             const GByte *pabyOverlay =
    1142          83 :                 (nBand <= nOverlayCount)
    1143          91 :                     ? m_oBlendDataset.m_abyBuffer.data() +
    1144          75 :                           nPixelCount * (nColorCount + nBand - 1)
    1145           8 :                 : (nBand <= 3) ? m_oBlendDataset.m_abyBuffer.data() +
    1146           7 :                                      nPixelCount * nColorCount
    1147          83 :                                : nullptr;
    1148             :             const GByte *pabyOverlayA =
    1149          80 :                 (nOverlayCount == 2 || nOverlayCount == 4)
    1150         163 :                     ? m_oBlendDataset.m_abyBuffer.data() +
    1151          62 :                           nPixelCount * (nColorCount + nOverlayCount - 1)
    1152          83 :                     : nullptr;
    1153             :             const GByte *pabyOverlayR =
    1154          66 :                 (nOverlayCount >= 3 && nColorCount < 3 && nBand <= 3)
    1155         149 :                     ? m_oBlendDataset.m_abyBuffer.data() +
    1156           3 :                           nPixelCount * nColorCount
    1157          83 :                     : nullptr;
    1158             :             const GByte *pabyOverlayG =
    1159          66 :                 (nOverlayCount >= 3 && nColorCount < 3 && nBand <= 3)
    1160         149 :                     ? m_oBlendDataset.m_abyBuffer.data() +
    1161           3 :                           nPixelCount * (nColorCount + 1)
    1162          83 :                     : nullptr;
    1163             :             const GByte *pabyOverlayB =
    1164          66 :                 (nOverlayCount >= 3 && nColorCount < 3 && nBand <= 3)
    1165         149 :                     ? m_oBlendDataset.m_abyBuffer.data() +
    1166           3 :                           nPixelCount * (nColorCount + 2)
    1167          83 :                     : nullptr;
    1168             : 
    1169          83 :             size_t nSrcIdx = 0;
    1170         714 :             for (int j = 0; j < nBufYSize; ++j)
    1171             :             {
    1172         631 :                 auto nDstOffset = j * nLineSpace;
    1173       98753 :                 for (int i = 0; i < nBufXSize;
    1174       98122 :                      ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
    1175             :                 {
    1176             :                     // Corrected to take into account m_opacity255Scale
    1177       98122 :                     const int nOverlayA =
    1178       98122 :                         pabyOverlayA ? ((pabyOverlayA[nSrcIdx] *
    1179       97606 :                                              m_oBlendDataset.m_opacity255Scale +
    1180             :                                          255) /
    1181             :                                         256)
    1182         516 :                                      : m_oBlendDataset.m_opacity255Scale;
    1183             : 
    1184       98122 :                     const int nSrcA = pabyA ? pabyA[nSrcIdx] : 255;
    1185             : 
    1186       98122 :                     const int nSrcAMul255MinusOverlayA =
    1187       98122 :                         (nSrcA * (255 - nOverlayA) + 255) / 256;
    1188       98122 :                     const int nDstA = nOverlayA + nSrcAMul255MinusOverlayA;
    1189       98122 :                     if (nBand != 4)
    1190             :                     {
    1191             :                         const int nOverlay =
    1192           3 :                             (pabyOverlayR && pabyOverlayG && pabyOverlayB)
    1193      147439 :                                 ? RGBToGrayScale(pabyOverlayR[nSrcIdx],
    1194           3 :                                                  pabyOverlayG[nSrcIdx],
    1195           3 :                                                  pabyOverlayB[nSrcIdx])
    1196       73718 :                             : pabyOverlay ? pabyOverlay[nSrcIdx]
    1197       73721 :                                           : 255;
    1198             : 
    1199       73721 :                         const int nSrc = paby ? paby[nSrcIdx] : 255;
    1200       73721 :                         int nDst = (nOverlay * nOverlayA +
    1201       73721 :                                     nSrc * nSrcAMul255MinusOverlayA + 255) /
    1202             :                                    256;
    1203       73721 :                         if (nDstA != 0 && nDstA != 255)
    1204       19672 :                             nDst = (nDst * 255 + (nDstA / 2)) / nDstA;
    1205       73721 :                         pabyDst[nDstOffset] =
    1206       73721 :                             static_cast<GByte>(std::min(nDst, 255));
    1207             :                     }
    1208             :                     else
    1209             :                     {
    1210       24401 :                         pabyDst[nDstOffset] = static_cast<GByte>(nDstA);
    1211             :                     }
    1212             :                 }
    1213             :             }
    1214             :         }
    1215          48 :         else if (nOverlayCount == 1 && m_oBlendDataset.m_opacity255Scale == 255)
    1216             :         {
    1217          18 :             const GByte *pabyR = m_oBlendDataset.m_abyBuffer.data();
    1218             :             const GByte *pabyG =
    1219          18 :                 m_oBlendDataset.m_abyBuffer.data() + nPixelCount;
    1220             :             const GByte *pabyB =
    1221          18 :                 m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 2;
    1222          18 :             CPLAssert(m_oBlendDataset.m_operator == HSV_VALUE);
    1223          18 :             size_t nSrcIdx = 0;
    1224             :             const GByte *pabyValue =
    1225          18 :                 m_oBlendDataset.m_abyBuffer.data() + nPixelCount * nColorCount;
    1226         402 :             for (int j = 0; j < nBufYSize; ++j)
    1227             :             {
    1228         384 :                 auto nDstOffset = j * nLineSpace;
    1229         384 :                 if (nPixelSpace == 1 && nLineSpace >= nPixelSpace * nBufXSize)
    1230             :                 {
    1231         884 :                     patch_value_line(
    1232             :                         nBufXSize, pabyR + nSrcIdx, pabyG + nSrcIdx,
    1233             :                         pabyB + nSrcIdx, pabyValue + nSrcIdx,
    1234         378 :                         nBand == 1 ? pabyDst + nDstOffset : nullptr,
    1235         378 :                         nBand == 2 ? pabyDst + nDstOffset : nullptr,
    1236         378 :                         nBand == 3 ? pabyDst + nDstOffset : nullptr);
    1237         378 :                     nSrcIdx += nBufXSize;
    1238             :                 }
    1239             :                 else
    1240             :                 {
    1241      786438 :                     for (int i = 0; i < nBufXSize;
    1242      786432 :                          ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
    1243             :                     {
    1244             :                         float h, s;
    1245      786432 :                         rgb_to_hs(pabyR[nSrcIdx], pabyG[nSrcIdx],
    1246      786432 :                                   pabyB[nSrcIdx], &h, &s);
    1247      786432 :                         if (nBand == 1)
    1248             :                         {
    1249      262144 :                             hsv_to_rgb(h, s, pabyValue[nSrcIdx],
    1250      262144 :                                        &pabyDst[nDstOffset], nullptr, nullptr);
    1251             :                         }
    1252      524288 :                         else if (nBand == 2)
    1253             :                         {
    1254      262144 :                             hsv_to_rgb(h, s, pabyValue[nSrcIdx], nullptr,
    1255      262144 :                                        &pabyDst[nDstOffset], nullptr);
    1256             :                         }
    1257             :                         else
    1258             :                         {
    1259      262144 :                             CPLAssert(nBand == 3);
    1260      262144 :                             hsv_to_rgb(h, s, pabyValue[nSrcIdx], nullptr,
    1261      262144 :                                        nullptr, &pabyDst[nDstOffset]);
    1262             :                         }
    1263             :                     }
    1264             :                 }
    1265          18 :             }
    1266             :         }
    1267             :         else
    1268             :         {
    1269          30 :             CPLAssert(m_oBlendDataset.m_operator == HSV_VALUE);
    1270          30 :             CPLAssert(nBand <= 3);
    1271          30 :             const GByte *pabyR = m_oBlendDataset.m_abyBuffer.data();
    1272             :             const GByte *pabyG =
    1273          30 :                 m_oBlendDataset.m_abyBuffer.data() + nPixelCount;
    1274             :             const GByte *pabyB =
    1275          30 :                 m_oBlendDataset.m_abyBuffer.data() + nPixelCount * 2;
    1276             :             const GByte *pabyValue =
    1277          30 :                 m_oBlendDataset.m_abyBuffer.data() + nPixelCount * nColorCount;
    1278             :             const GByte *pabyOverlayR =
    1279          30 :                 nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
    1280          12 :                                          nPixelCount * nColorCount
    1281          30 :                                    : nullptr;
    1282             :             const GByte *pabyOverlayG =
    1283          30 :                 nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
    1284          12 :                                          nPixelCount * (nColorCount + 1)
    1285          30 :                                    : nullptr;
    1286             :             const GByte *pabyOverlayB =
    1287          30 :                 nOverlayCount >= 3 ? m_oBlendDataset.m_abyBuffer.data() +
    1288          12 :                                          nPixelCount * (nColorCount + 2)
    1289          30 :                                    : nullptr;
    1290             :             const GByte *pabyOverlayA =
    1291          24 :                 (nOverlayCount == 2 || nOverlayCount == 4)
    1292          54 :                     ? m_oBlendDataset.m_abyBuffer.data() +
    1293          12 :                           nPixelCount * (nColorCount + nOverlayCount - 1)
    1294          30 :                     : nullptr;
    1295             : 
    1296          30 :             size_t nSrcIdx = 0;
    1297          60 :             for (int j = 0; j < nBufYSize; ++j)
    1298             :             {
    1299          30 :                 auto nDstOffset = j * nLineSpace;
    1300     3932190 :                 for (int i = 0; i < nBufXSize;
    1301     3932160 :                      ++i, ++nSrcIdx, nDstOffset += nPixelSpace)
    1302             :                 {
    1303     3932160 :                     const int nColorR = pabyR[nSrcIdx];
    1304     3932160 :                     const int nColorG = pabyG[nSrcIdx];
    1305     3932160 :                     const int nColorB = pabyB[nSrcIdx];
    1306             :                     const int nOverlayV =
    1307     1572860 :                         (pabyOverlayR && pabyOverlayG && pabyOverlayB)
    1308     5505020 :                             ? std::max({pabyOverlayR[nSrcIdx],
    1309     1572860 :                                         pabyOverlayG[nSrcIdx],
    1310     1572860 :                                         pabyOverlayB[nSrcIdx]})
    1311     2359300 :                             : pabyValue[nSrcIdx];
    1312     3932160 :                     const int nOverlayA =
    1313     3932160 :                         pabyOverlayA ? ((pabyOverlayA[nSrcIdx] *
    1314     1572860 :                                              m_oBlendDataset.m_opacity255Scale +
    1315             :                                          255) /
    1316             :                                         256)
    1317     2359300 :                                      : m_oBlendDataset.m_opacity255Scale;
    1318             :                     const int nColorValue =
    1319     3932160 :                         std::max({nColorR, nColorG, nColorB});
    1320             : 
    1321             :                     float h, s;
    1322     3932160 :                     rgb_to_hs(pabyR[nSrcIdx], pabyG[nSrcIdx], pabyB[nSrcIdx],
    1323             :                               &h, &s);
    1324             : 
    1325     3932160 :                     const GByte nTargetValue = static_cast<GByte>(
    1326     3932160 :                         (nOverlayV * nOverlayA +
    1327     3932160 :                          nColorValue * (255 - nOverlayA) + 255) /
    1328             :                         256);
    1329             : 
    1330     3932160 :                     if (nBand == 1)
    1331             :                     {
    1332     1310720 :                         hsv_to_rgb(h, s, nTargetValue, &pabyDst[nDstOffset],
    1333             :                                    nullptr, nullptr);
    1334             :                     }
    1335     2621440 :                     else if (nBand == 2)
    1336             :                     {
    1337     1310720 :                         hsv_to_rgb(h, s, nTargetValue, nullptr,
    1338     1310720 :                                    &pabyDst[nDstOffset], nullptr);
    1339             :                     }
    1340             :                     else
    1341             :                     {
    1342     1310720 :                         CPLAssert(nBand == 3);
    1343     1310720 :                         hsv_to_rgb(h, s, nTargetValue, nullptr, nullptr,
    1344     1310720 :                                    &pabyDst[nDstOffset]);
    1345             :                     }
    1346             :                 }
    1347             :             }
    1348             :         }
    1349             : 
    1350         131 :         return CE_None;
    1351             :     }
    1352          59 :     else if (m_oBlendDataset.m_ioError)
    1353             :     {
    1354           0 :         return CE_Failure;
    1355             :     }
    1356             :     else
    1357             :     {
    1358          59 :         const CPLErr eErr = GDALRasterBand::IRasterIO(
    1359             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1360             :             eBufType, nPixelSpace, nLineSpace, psExtraArg);
    1361          59 :         m_oBlendDataset.m_ioError = eErr != CE_None;
    1362          59 :         return eErr;
    1363             :     }
    1364             : }
    1365             : 
    1366             : }  // namespace
    1367             : 
    1368             : /************************************************************************/
    1369             : /*                GDALRasterBlendAlgorithm::ValidateGlobal()            */
    1370             : /************************************************************************/
    1371             : 
    1372          80 : bool GDALRasterBlendAlgorithm::ValidateGlobal()
    1373             : {
    1374             :     auto poSrcDS =
    1375          80 :         m_inputDataset.empty() ? nullptr : m_inputDataset[0].GetDatasetRef();
    1376          80 :     auto poOverlayDS = m_overlayDataset.GetDatasetRef();
    1377          80 :     if (poSrcDS)
    1378             :     {
    1379         154 :         if (poSrcDS->GetRasterCount() == 0 || poSrcDS->GetRasterCount() > 4 ||
    1380          77 :             poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
    1381             :         {
    1382           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    1383             :                         "Only 1-band, 2-band, 3-band or 4-band Byte dataset "
    1384             :                         "supported as input");
    1385           1 :             return false;
    1386             :         }
    1387             :     }
    1388          79 :     if (poOverlayDS)
    1389             :     {
    1390          79 :         if (poOverlayDS->GetRasterCount() == 0 ||
    1391         158 :             poOverlayDS->GetRasterCount() > 4 ||
    1392          79 :             poOverlayDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
    1393             :         {
    1394           1 :             ReportError(CE_Failure, CPLE_NotSupported,
    1395             :                         "Only 1-band, 2-band, 3-band or 4-band Byte dataset "
    1396             :                         "supported as overlay");
    1397           1 :             return false;
    1398             :         }
    1399             :     }
    1400             : 
    1401          78 :     if (poSrcDS && poOverlayDS)
    1402             :     {
    1403         149 :         if (poSrcDS->GetRasterXSize() != poOverlayDS->GetRasterXSize() ||
    1404          74 :             poSrcDS->GetRasterYSize() != poOverlayDS->GetRasterYSize())
    1405             :         {
    1406           2 :             ReportError(CE_Failure, CPLE_IllegalArg,
    1407             :                         "Input dataset and overlay dataset must have "
    1408             :                         "the same dimensions");
    1409           2 :             return false;
    1410             :         }
    1411             : 
    1412          84 :         if (m_operator == HSV_VALUE && poSrcDS->GetRasterCount() != 3 &&
    1413          11 :             poSrcDS->GetRasterCount() != 4)
    1414             :         {
    1415           1 :             ReportError(CE_Failure, CPLE_AppDefined,
    1416             :                         "Operator %s requires a 3-band or 4-band input dataset",
    1417             :                         HSV_VALUE);
    1418           1 :             return false;
    1419             :         }
    1420             :     }
    1421             : 
    1422          75 :     return true;
    1423             : }
    1424             : 
    1425             : /************************************************************************/
    1426             : /*                   GDALRasterBlendAlgorithm::RunStep()                */
    1427             : /************************************************************************/
    1428             : 
    1429          36 : bool GDALRasterBlendAlgorithm::RunStep(GDALPipelineStepRunContext &)
    1430             : {
    1431          36 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
    1432          36 :     CPLAssert(poSrcDS);
    1433             : 
    1434          36 :     auto poOverlayDS = m_overlayDataset.GetDatasetRef();
    1435          36 :     CPLAssert(poOverlayDS);
    1436             : 
    1437          36 :     if (!ValidateGlobal())
    1438           0 :         return false;
    1439             : 
    1440          36 :     const int nOpacity255Scale =
    1441          36 :         (m_opacity * 255 + OPACITY_INPUT_RANGE / 2) / OPACITY_INPUT_RANGE;
    1442             : 
    1443          36 :     m_outputDataset.Set(std::make_unique<BlendDataset>(
    1444          36 :         *poSrcDS, *poOverlayDS, m_operator, nOpacity255Scale));
    1445             : 
    1446          36 :     return true;
    1447             : }
    1448             : 
    1449             : GDALRasterBlendAlgorithmStandalone::~GDALRasterBlendAlgorithmStandalone() =
    1450             :     default;
    1451             : 
    1452             : //! @endcond

Generated by: LCOV version 1.14