LCOV - code coverage report
Current view: top level - frmts/vrt - vrtprocesseddatasetfunctions.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 577 638 90.4 %
Date: 2024-11-21 22:18:42 Functions: 19 19 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Virtual GDAL Datasets
       4             :  * Purpose:  Implementation of VRTProcessedDataset processing functions
       5             :  * Author:   Even Rouault <even.rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, Even Rouault <even.rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_minixml.h"
      14             : #include "cpl_string.h"
      15             : #include "vrtdataset.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <limits>
      19             : #include <map>
      20             : #include <set>
      21             : #include <vector>
      22             : 
      23             : /************************************************************************/
      24             : /*                               GetDstValue()                          */
      25             : /************************************************************************/
      26             : 
      27             : /** Return a destination value given an initial value, the destination no data
      28             :  * value and its replacement value
      29             :  */
      30         466 : static inline double GetDstValue(double dfVal, double dfDstNoData,
      31             :                                  double dfReplacementDstNodata,
      32             :                                  GDALDataType eIntendedDstDT,
      33             :                                  bool bDstIntendedDTIsInteger)
      34             : {
      35         466 :     if (bDstIntendedDTIsInteger && std::round(dfVal) == dfDstNoData)
      36             :     {
      37           1 :         return dfReplacementDstNodata;
      38             :     }
      39         465 :     else if (eIntendedDstDT == GDT_Float32 &&
      40           0 :              static_cast<float>(dfVal) == static_cast<float>(dfDstNoData))
      41             :     {
      42           0 :         return dfReplacementDstNodata;
      43             :     }
      44         465 :     else if (eIntendedDstDT == GDT_Float64 && dfVal == dfDstNoData)
      45             :     {
      46           1 :         return dfReplacementDstNodata;
      47             :     }
      48             :     else
      49             :     {
      50         464 :         return dfVal;
      51             :     }
      52             : }
      53             : 
      54             : /************************************************************************/
      55             : /*                    BandAffineCombinationData                         */
      56             : /************************************************************************/
      57             : 
      58             : namespace
      59             : {
      60             : /** Working structure for 'BandAffineCombination' builtin function. */
      61             : struct BandAffineCombinationData
      62             : {
      63             :     static constexpr const char *const EXPECTED_SIGNATURE =
      64             :         "BandAffineCombination";
      65             :     //! Signature (to make sure callback functions are called with the right argument)
      66             :     const std::string m_osSignature = EXPECTED_SIGNATURE;
      67             : 
      68             :     /** Replacement nodata value */
      69             :     std::vector<double> m_adfReplacementDstNodata{};
      70             : 
      71             :     /** Intended destination data type. */
      72             :     GDALDataType m_eIntendedDstDT = GDT_Float64;
      73             : 
      74             :     /** Affine transformation coefficients.
      75             :      * m_aadfCoefficients[i][0] is the constant term for the i(th) dst band
      76             :      * m_aadfCoefficients[i][j] is the weight of the j(th) src band for the
      77             :      * i(th) dst vand.
      78             :      * Said otherwise dst[i] = m_aadfCoefficients[i][0] +
      79             :      *      sum(m_aadfCoefficients[i][j + 1] * src[j] for j in 0...nSrcBands-1)
      80             :      */
      81             :     std::vector<std::vector<double>> m_aadfCoefficients{};
      82             : 
      83             :     //! Minimum clamping value.
      84             :     double m_dfClampMin = std::numeric_limits<double>::quiet_NaN();
      85             : 
      86             :     //! Maximum clamping value.
      87             :     double m_dfClampMax = std::numeric_limits<double>::quiet_NaN();
      88             : };
      89             : }  // namespace
      90             : 
      91             : /************************************************************************/
      92             : /*               SetOutputValuesForInNoDataAndOutNoData()               */
      93             : /************************************************************************/
      94             : 
      95          19 : static std::vector<double> SetOutputValuesForInNoDataAndOutNoData(
      96             :     int nInBands, double *padfInNoData, int *pnOutBands,
      97             :     double **ppadfOutNoData, bool bSrcNodataSpecified, double dfSrcNoData,
      98             :     bool bDstNodataSpecified, double dfDstNoData, bool bIsFinalStep)
      99             : {
     100          19 :     if (bSrcNodataSpecified)
     101             :     {
     102           3 :         std::vector<double> adfNoData(nInBands, dfSrcNoData);
     103           3 :         memcpy(padfInNoData, adfNoData.data(),
     104           3 :                adfNoData.size() * sizeof(double));
     105             :     }
     106             : 
     107          19 :     std::vector<double> adfDstNoData;
     108          19 :     if (bDstNodataSpecified)
     109             :     {
     110           3 :         adfDstNoData.resize(*pnOutBands, dfDstNoData);
     111             :     }
     112          16 :     else if (bIsFinalStep)
     113             :     {
     114             :         adfDstNoData =
     115          14 :             std::vector<double>(*ppadfOutNoData, *ppadfOutNoData + *pnOutBands);
     116             :     }
     117             :     else
     118             :     {
     119             :         adfDstNoData =
     120           2 :             std::vector<double>(padfInNoData, padfInNoData + nInBands);
     121           2 :         adfDstNoData.resize(*pnOutBands, *padfInNoData);
     122             :     }
     123             : 
     124          19 :     if (*ppadfOutNoData == nullptr)
     125             :     {
     126           2 :         *ppadfOutNoData =
     127           2 :             static_cast<double *>(CPLMalloc(*pnOutBands * sizeof(double)));
     128             :     }
     129          19 :     memcpy(*ppadfOutNoData, adfDstNoData.data(), *pnOutBands * sizeof(double));
     130             : 
     131          19 :     return adfDstNoData;
     132             : }
     133             : 
     134             : /************************************************************************/
     135             : /*                    BandAffineCombinationInit()                       */
     136             : /************************************************************************/
     137             : 
     138             : /** Init function for 'BandAffineCombination' builtin function. */
     139          12 : static CPLErr BandAffineCombinationInit(
     140             :     const char * /*pszFuncName*/, void * /*pUserData*/,
     141             :     CSLConstList papszFunctionArgs, int nInBands, GDALDataType eInDT,
     142             :     double *padfInNoData, int *pnOutBands, GDALDataType *peOutDT,
     143             :     double **ppadfOutNoData, const char * /* pszVRTPath */,
     144             :     VRTPDWorkingDataPtr *ppWorkingData)
     145             : {
     146          12 :     CPLAssert(eInDT == GDT_Float64);
     147             : 
     148          12 :     *peOutDT = eInDT;
     149          12 :     *ppWorkingData = nullptr;
     150             : 
     151          24 :     auto data = std::make_unique<BandAffineCombinationData>();
     152             : 
     153          24 :     std::map<int, std::vector<double>> oMapCoefficients{};
     154          12 :     double dfSrcNoData = std::numeric_limits<double>::quiet_NaN();
     155          12 :     bool bSrcNodataSpecified = false;
     156          12 :     double dfDstNoData = std::numeric_limits<double>::quiet_NaN();
     157          12 :     bool bDstNodataSpecified = false;
     158          12 :     double dfReplacementDstNodata = std::numeric_limits<double>::quiet_NaN();
     159          12 :     bool bReplacementDstNodataSpecified = false;
     160             : 
     161          65 :     for (const auto &[pszKey, pszValue] :
     162          76 :          cpl::IterateNameValue(papszFunctionArgs))
     163             :     {
     164          33 :         if (EQUAL(pszKey, "src_nodata"))
     165             :         {
     166           2 :             bSrcNodataSpecified = true;
     167           2 :             dfSrcNoData = CPLAtof(pszValue);
     168             :         }
     169          31 :         else if (EQUAL(pszKey, "dst_nodata"))
     170             :         {
     171           2 :             bDstNodataSpecified = true;
     172           2 :             dfDstNoData = CPLAtof(pszValue);
     173             :         }
     174          29 :         else if (EQUAL(pszKey, "replacement_nodata"))
     175             :         {
     176           1 :             bReplacementDstNodataSpecified = true;
     177           1 :             dfReplacementDstNodata = CPLAtof(pszValue);
     178             :         }
     179          28 :         else if (EQUAL(pszKey, "dst_intended_datatype"))
     180             :         {
     181           1 :             for (GDALDataType eDT = GDT_Byte; eDT < GDT_TypeCount;
     182           0 :                  eDT = static_cast<GDALDataType>(eDT + 1))
     183             :             {
     184           1 :                 if (EQUAL(GDALGetDataTypeName(eDT), pszValue))
     185             :                 {
     186           1 :                     data->m_eIntendedDstDT = eDT;
     187           1 :                     break;
     188             :                 }
     189             :             }
     190             :         }
     191          27 :         else if (STARTS_WITH_CI(pszKey, "coefficients_"))
     192             :         {
     193          25 :             const int nTargetBand = atoi(pszKey + strlen("coefficients_"));
     194          25 :             if (nTargetBand <= 0 || nTargetBand > 65536)
     195             :             {
     196           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     197             :                          "Invalid band in argument '%s'", pszKey);
     198           1 :                 return CE_Failure;
     199             :             }
     200          25 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
     201          25 :             if (aosTokens.size() != 1 + nInBands)
     202             :             {
     203           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     204             :                          "Argument %s has %d values, whereas %d are expected",
     205             :                          pszKey, aosTokens.size(), 1 + nInBands);
     206           1 :                 return CE_Failure;
     207             :             }
     208          24 :             std::vector<double> adfValues;
     209         110 :             for (int i = 0; i < aosTokens.size(); ++i)
     210             :             {
     211          86 :                 adfValues.push_back(CPLAtof(aosTokens[i]));
     212             :             }
     213          24 :             oMapCoefficients[nTargetBand - 1] = std::move(adfValues);
     214             :         }
     215           2 :         else if (EQUAL(pszKey, "min"))
     216             :         {
     217           1 :             data->m_dfClampMin = CPLAtof(pszValue);
     218             :         }
     219           1 :         else if (EQUAL(pszKey, "max"))
     220             :         {
     221           1 :             data->m_dfClampMax = CPLAtof(pszValue);
     222             :         }
     223             :         else
     224             :         {
     225           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     226             :                      "Unrecognized argument name %s. Ignored", pszKey);
     227             :         }
     228             :     }
     229             : 
     230          11 :     const bool bIsFinalStep = *pnOutBands != 0;
     231          11 :     if (bIsFinalStep)
     232             :     {
     233           9 :         if (*pnOutBands != static_cast<int>(oMapCoefficients.size()))
     234             :         {
     235           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     236             :                      "Final step expect %d bands, but only %d coefficient_XX "
     237             :                      "are provided",
     238           1 :                      *pnOutBands, static_cast<int>(oMapCoefficients.size()));
     239           1 :             return CE_Failure;
     240             :         }
     241             :     }
     242             :     else
     243             :     {
     244           2 :         *pnOutBands = static_cast<int>(oMapCoefficients.size());
     245             :     }
     246             : 
     247             :     const std::vector<double> adfDstNoData =
     248             :         SetOutputValuesForInNoDataAndOutNoData(
     249             :             nInBands, padfInNoData, pnOutBands, ppadfOutNoData,
     250             :             bSrcNodataSpecified, dfSrcNoData, bDstNodataSpecified, dfDstNoData,
     251          20 :             bIsFinalStep);
     252             : 
     253          10 :     if (bReplacementDstNodataSpecified)
     254             :     {
     255           1 :         data->m_adfReplacementDstNodata.resize(*pnOutBands,
     256             :                                                dfReplacementDstNodata);
     257             :     }
     258             :     else
     259             :     {
     260          30 :         for (double dfVal : adfDstNoData)
     261             :         {
     262          21 :             data->m_adfReplacementDstNodata.emplace_back(
     263          21 :                 GDALGetNoDataReplacementValue(data->m_eIntendedDstDT, dfVal));
     264             :         }
     265             :     }
     266             : 
     267             :     // Check we have a set of coefficient for all output bands and
     268             :     // convert the map to a vector
     269          32 :     for (auto &oIter : oMapCoefficients)
     270             :     {
     271          23 :         const int iExpected = static_cast<int>(data->m_aadfCoefficients.size());
     272          23 :         if (oIter.first != iExpected)
     273             :         {
     274           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     275             :                      "Argument coefficients_%d is missing", iExpected + 1);
     276           1 :             return CE_Failure;
     277             :         }
     278          22 :         data->m_aadfCoefficients.emplace_back(std::move(oIter.second));
     279             :     }
     280           9 :     *ppWorkingData = data.release();
     281           9 :     return CE_None;
     282             : }
     283             : 
     284             : /************************************************************************/
     285             : /*                    BandAffineCombinationFree()                       */
     286             : /************************************************************************/
     287             : 
     288             : /** Free function for 'BandAffineCombination' builtin function. */
     289           9 : static void BandAffineCombinationFree(const char * /*pszFuncName*/,
     290             :                                       void * /*pUserData*/,
     291             :                                       VRTPDWorkingDataPtr pWorkingData)
     292             : {
     293           9 :     BandAffineCombinationData *data =
     294             :         static_cast<BandAffineCombinationData *>(pWorkingData);
     295           9 :     CPLAssert(data->m_osSignature ==
     296             :               BandAffineCombinationData::EXPECTED_SIGNATURE);
     297           9 :     CPL_IGNORE_RET_VAL(data->m_osSignature);
     298           9 :     delete data;
     299           9 : }
     300             : 
     301             : /************************************************************************/
     302             : /*                    BandAffineCombinationProcess()                    */
     303             : /************************************************************************/
     304             : 
     305             : /** Processing function for 'BandAffineCombination' builtin function. */
     306           9 : static CPLErr BandAffineCombinationProcess(
     307             :     const char * /*pszFuncName*/, void * /*pUserData*/,
     308             :     VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs*/,
     309             :     int nBufXSize, int nBufYSize, const void *pInBuffer, size_t nInBufferSize,
     310             :     GDALDataType eInDT, int nInBands, const double *CPL_RESTRICT padfInNoData,
     311             :     void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, int nOutBands,
     312             :     const double *CPL_RESTRICT padfOutNoData, double /*dfSrcXOff*/,
     313             :     double /*dfSrcYOff*/, double /*dfSrcXSize*/, double /*dfSrcYSize*/,
     314             :     const double /*adfSrcGT*/[], const char * /* pszVRTPath */,
     315             :     CSLConstList /*papszExtra*/)
     316             : {
     317           9 :     const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
     318             : 
     319           9 :     CPL_IGNORE_RET_VAL(eInDT);
     320           9 :     CPLAssert(eInDT == GDT_Float64);
     321           9 :     CPL_IGNORE_RET_VAL(eOutDT);
     322           9 :     CPLAssert(eOutDT == GDT_Float64);
     323           9 :     CPL_IGNORE_RET_VAL(nInBufferSize);
     324           9 :     CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
     325           9 :     CPL_IGNORE_RET_VAL(nOutBufferSize);
     326           9 :     CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
     327             : 
     328           9 :     const BandAffineCombinationData *data =
     329             :         static_cast<BandAffineCombinationData *>(pWorkingData);
     330           9 :     CPLAssert(data->m_osSignature ==
     331             :               BandAffineCombinationData::EXPECTED_SIGNATURE);
     332           9 :     const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
     333           9 :     double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
     334             :     const bool bDstIntendedDTIsInteger =
     335           9 :         GDALDataTypeIsInteger(data->m_eIntendedDstDT);
     336           9 :     const double dfClampMin = data->m_dfClampMin;
     337           9 :     const double dfClampMax = data->m_dfClampMax;
     338         171 :     for (size_t i = 0; i < nElts; ++i)
     339             :     {
     340         634 :         for (int iDst = 0; iDst < nOutBands; ++iDst)
     341             :         {
     342         472 :             const auto &adfCoefficients = data->m_aadfCoefficients[iDst];
     343         472 :             double dfVal = adfCoefficients[0];
     344         472 :             bool bSetNoData = false;
     345        1856 :             for (int iSrc = 0; iSrc < nInBands; ++iSrc)
     346             :             {
     347             :                 // written this way to work with a NaN value
     348        1390 :                 if (!(padfSrc[iSrc] != padfInNoData[iSrc]))
     349             :                 {
     350           6 :                     bSetNoData = true;
     351           6 :                     break;
     352             :                 }
     353        1384 :                 dfVal += adfCoefficients[iSrc + 1] * padfSrc[iSrc];
     354             :             }
     355         472 :             if (bSetNoData)
     356             :             {
     357           6 :                 *padfDst = padfOutNoData[iDst];
     358             :             }
     359             :             else
     360             :             {
     361         932 :                 double dfDstVal = GetDstValue(
     362         466 :                     dfVal, padfOutNoData[iDst],
     363         466 :                     data->m_adfReplacementDstNodata[iDst],
     364         466 :                     data->m_eIntendedDstDT, bDstIntendedDTIsInteger);
     365         466 :                 if (dfDstVal < dfClampMin)
     366           1 :                     dfDstVal = dfClampMin;
     367         466 :                 if (dfDstVal > dfClampMax)
     368           1 :                     dfDstVal = dfClampMax;
     369         466 :                 *padfDst = dfDstVal;
     370             :             }
     371         472 :             ++padfDst;
     372             :         }
     373         162 :         padfSrc += nInBands;
     374             :     }
     375             : 
     376           9 :     return CE_None;
     377             : }
     378             : 
     379             : /************************************************************************/
     380             : /*                                LUTData                               */
     381             : /************************************************************************/
     382             : 
     383             : namespace
     384             : {
     385             : /** Working structure for 'LUT' builtin function. */
     386             : struct LUTData
     387             : {
     388             :     static constexpr const char *const EXPECTED_SIGNATURE = "LUT";
     389             :     //! Signature (to make sure callback functions are called with the right argument)
     390             :     const std::string m_osSignature = EXPECTED_SIGNATURE;
     391             : 
     392             :     //! m_aadfLUTInputs[i][j] is the j(th) input value for that LUT of band i.
     393             :     std::vector<std::vector<double>> m_aadfLUTInputs{};
     394             : 
     395             :     //! m_aadfLUTOutputs[i][j] is the j(th) output value for that LUT of band i.
     396             :     std::vector<std::vector<double>> m_aadfLUTOutputs{};
     397             : 
     398             :     /************************************************************************/
     399             :     /*                              LookupValue()                           */
     400             :     /************************************************************************/
     401             : 
     402          18 :     double LookupValue(int iBand, double dfInput) const
     403             :     {
     404          18 :         const auto &adfInput = m_aadfLUTInputs[iBand];
     405          18 :         const auto &afdOutput = m_aadfLUTOutputs[iBand];
     406             : 
     407             :         // Find the index of the first element in the LUT input array that
     408             :         // is not smaller than the input value.
     409             :         int i = static_cast<int>(
     410          18 :             std::lower_bound(adfInput.data(), adfInput.data() + adfInput.size(),
     411          18 :                              dfInput) -
     412          18 :             adfInput.data());
     413             : 
     414          18 :         if (i == 0)
     415           6 :             return afdOutput[0];
     416             : 
     417             :         // If the index is beyond the end of the LUT input array, the input
     418             :         // value is larger than all the values in the array.
     419          12 :         if (i == static_cast<int>(adfInput.size()))
     420           6 :             return afdOutput.back();
     421             : 
     422           6 :         if (adfInput[i] == dfInput)
     423           0 :             return afdOutput[i];
     424             : 
     425             :         // Otherwise, interpolate.
     426           6 :         return afdOutput[i - 1] + (dfInput - adfInput[i - 1]) *
     427           6 :                                       ((afdOutput[i] - afdOutput[i - 1]) /
     428           6 :                                        (adfInput[i] - adfInput[i - 1]));
     429             :     }
     430             : };
     431             : }  // namespace
     432             : 
     433             : /************************************************************************/
     434             : /*                                LUTInit()                             */
     435             : /************************************************************************/
     436             : 
     437             : /** Init function for 'LUT' builtin function. */
     438           6 : static CPLErr LUTInit(const char * /*pszFuncName*/, void * /*pUserData*/,
     439             :                       CSLConstList papszFunctionArgs, int nInBands,
     440             :                       GDALDataType eInDT, double *padfInNoData, int *pnOutBands,
     441             :                       GDALDataType *peOutDT, double **ppadfOutNoData,
     442             :                       const char * /* pszVRTPath */,
     443             :                       VRTPDWorkingDataPtr *ppWorkingData)
     444             : {
     445           6 :     CPLAssert(eInDT == GDT_Float64);
     446             : 
     447           6 :     const bool bIsFinalStep = *pnOutBands != 0;
     448           6 :     *peOutDT = eInDT;
     449           6 :     *ppWorkingData = nullptr;
     450             : 
     451           6 :     if (!bIsFinalStep)
     452             :     {
     453           0 :         *pnOutBands = nInBands;
     454             :     }
     455             : 
     456          12 :     auto data = std::make_unique<LUTData>();
     457             : 
     458           6 :     double dfSrcNoData = std::numeric_limits<double>::quiet_NaN();
     459           6 :     bool bSrcNodataSpecified = false;
     460           6 :     double dfDstNoData = std::numeric_limits<double>::quiet_NaN();
     461           6 :     bool bDstNodataSpecified = false;
     462             : 
     463          12 :     std::map<int, std::pair<std::vector<double>, std::vector<double>>> oMap{};
     464             : 
     465          22 :     for (const auto &[pszKey, pszValue] :
     466          26 :          cpl::IterateNameValue(papszFunctionArgs))
     467             :     {
     468          12 :         if (EQUAL(pszKey, "src_nodata"))
     469             :         {
     470           1 :             bSrcNodataSpecified = true;
     471           1 :             dfSrcNoData = CPLAtof(pszValue);
     472             :         }
     473          11 :         else if (EQUAL(pszKey, "dst_nodata"))
     474             :         {
     475           1 :             bDstNodataSpecified = true;
     476           1 :             dfDstNoData = CPLAtof(pszValue);
     477             :         }
     478          10 :         else if (STARTS_WITH_CI(pszKey, "lut_"))
     479             :         {
     480          10 :             const int nBand = atoi(pszKey + strlen("lut_"));
     481          10 :             if (nBand <= 0 || nBand > nInBands)
     482             :             {
     483           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     484             :                          "Invalid band in argument '%s'", pszKey);
     485           2 :                 return CE_Failure;
     486             :             }
     487           9 :             const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
     488           9 :             std::vector<double> adfInputValues;
     489           9 :             std::vector<double> adfOutputValues;
     490          26 :             for (int i = 0; i < aosTokens.size(); ++i)
     491             :             {
     492             :                 const CPLStringList aosTokens2(
     493          18 :                     CSLTokenizeString2(aosTokens[i], ":", 0));
     494          18 :                 if (aosTokens2.size() != 2)
     495             :                 {
     496           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     497             :                              "Invalid value for argument '%s'", pszKey);
     498           1 :                     return CE_Failure;
     499             :                 }
     500          17 :                 adfInputValues.push_back(CPLAtof(aosTokens2[0]));
     501          17 :                 adfOutputValues.push_back(CPLAtof(aosTokens2[1]));
     502             :             }
     503          16 :             oMap[nBand - 1] = std::pair(std::move(adfInputValues),
     504          16 :                                         std::move(adfOutputValues));
     505             :         }
     506             :         else
     507             :         {
     508           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     509             :                      "Unrecognized argument name %s. Ignored", pszKey);
     510             :         }
     511             :     }
     512             : 
     513           4 :     SetOutputValuesForInNoDataAndOutNoData(
     514             :         nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bSrcNodataSpecified,
     515             :         dfSrcNoData, bDstNodataSpecified, dfDstNoData, bIsFinalStep);
     516             : 
     517           4 :     int iExpected = 0;
     518             :     // Check we have values for all bands and convert to vector
     519          11 :     for (auto &oIter : oMap)
     520             :     {
     521           7 :         if (oIter.first != iExpected)
     522             :         {
     523           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Argument lut_%d is missing",
     524             :                      iExpected + 1);
     525           0 :             return CE_Failure;
     526             :         }
     527           7 :         ++iExpected;
     528           7 :         data->m_aadfLUTInputs.emplace_back(std::move(oIter.second.first));
     529           7 :         data->m_aadfLUTOutputs.emplace_back(std::move(oIter.second.second));
     530             :     }
     531             : 
     532           4 :     if (static_cast<int>(oMap.size()) < *pnOutBands)
     533             :     {
     534           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing lut_XX element(s)");
     535           1 :         return CE_Failure;
     536             :     }
     537             : 
     538           3 :     *ppWorkingData = data.release();
     539           3 :     return CE_None;
     540             : }
     541             : 
     542             : /************************************************************************/
     543             : /*                                LUTFree()                             */
     544             : /************************************************************************/
     545             : 
     546             : /** Free function for 'LUT' builtin function. */
     547           3 : static void LUTFree(const char * /*pszFuncName*/, void * /*pUserData*/,
     548             :                     VRTPDWorkingDataPtr pWorkingData)
     549             : {
     550           3 :     LUTData *data = static_cast<LUTData *>(pWorkingData);
     551           3 :     CPLAssert(data->m_osSignature == LUTData::EXPECTED_SIGNATURE);
     552           3 :     CPL_IGNORE_RET_VAL(data->m_osSignature);
     553           3 :     delete data;
     554           3 : }
     555             : 
     556             : /************************************************************************/
     557             : /*                             LUTProcess()                             */
     558             : /************************************************************************/
     559             : 
     560             : /** Processing function for 'LUT' builtin function. */
     561             : static CPLErr
     562           3 : LUTProcess(const char * /*pszFuncName*/, void * /*pUserData*/,
     563             :            VRTPDWorkingDataPtr pWorkingData,
     564             :            CSLConstList /* papszFunctionArgs*/, int nBufXSize, int nBufYSize,
     565             :            const void *pInBuffer, size_t nInBufferSize, GDALDataType eInDT,
     566             :            int nInBands, const double *CPL_RESTRICT padfInNoData,
     567             :            void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT,
     568             :            int nOutBands, const double *CPL_RESTRICT padfOutNoData,
     569             :            double /*dfSrcXOff*/, double /*dfSrcYOff*/, double /*dfSrcXSize*/,
     570             :            double /*dfSrcYSize*/, const double /*adfSrcGT*/[],
     571             :            const char * /* pszVRTPath */, CSLConstList /*papszExtra*/)
     572             : {
     573           3 :     const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
     574             : 
     575           3 :     CPL_IGNORE_RET_VAL(eInDT);
     576           3 :     CPLAssert(eInDT == GDT_Float64);
     577           3 :     CPL_IGNORE_RET_VAL(eOutDT);
     578           3 :     CPLAssert(eOutDT == GDT_Float64);
     579           3 :     CPL_IGNORE_RET_VAL(nInBufferSize);
     580           3 :     CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
     581           3 :     CPL_IGNORE_RET_VAL(nOutBufferSize);
     582           3 :     CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
     583           3 :     CPLAssert(nInBands == nOutBands);
     584           3 :     CPL_IGNORE_RET_VAL(nOutBands);
     585             : 
     586           3 :     const LUTData *data = static_cast<LUTData *>(pWorkingData);
     587           3 :     CPLAssert(data->m_osSignature == LUTData::EXPECTED_SIGNATURE);
     588           3 :     const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
     589           3 :     double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
     590          14 :     for (size_t i = 0; i < nElts; ++i)
     591             :     {
     592          33 :         for (int iBand = 0; iBand < nInBands; ++iBand)
     593             :         {
     594             :             // written this way to work with a NaN value
     595          22 :             if (!(*padfSrc != padfInNoData[iBand]))
     596           4 :                 *padfDst = padfOutNoData[iBand];
     597             :             else
     598          18 :                 *padfDst = data->LookupValue(iBand, *padfSrc);
     599          22 :             ++padfSrc;
     600          22 :             ++padfDst;
     601             :         }
     602             :     }
     603             : 
     604           3 :     return CE_None;
     605             : }
     606             : 
     607             : /************************************************************************/
     608             : /*                        LocalScaleOffsetData                          */
     609             : /************************************************************************/
     610             : 
     611             : namespace
     612             : {
     613             : /** Working structure for 'LocalScaleOffset' builtin function. */
     614             : struct LocalScaleOffsetData
     615             : {
     616             :     static constexpr const char *const EXPECTED_SIGNATURE = "LocalScaleOffset";
     617             :     //! Signature (to make sure callback functions are called with the right argument)
     618             :     const std::string m_osSignature = EXPECTED_SIGNATURE;
     619             : 
     620             :     //! Nodata value for gain dataset(s)
     621             :     double m_dfGainNodata = std::numeric_limits<double>::quiet_NaN();
     622             : 
     623             :     //! Nodata value for offset dataset(s)
     624             :     double m_dfOffsetNodata = std::numeric_limits<double>::quiet_NaN();
     625             : 
     626             :     //! Minimum clamping value.
     627             :     double m_dfClampMin = std::numeric_limits<double>::quiet_NaN();
     628             : 
     629             :     //! Maximum clamping value.
     630             :     double m_dfClampMax = std::numeric_limits<double>::quiet_NaN();
     631             : 
     632             :     //! Map from gain/offset dataset name to datasets
     633             :     std::map<std::string, std::unique_ptr<GDALDataset>> m_oDatasetMap{};
     634             : 
     635             :     //! Vector of size nInBands that point to the raster band from which to read gains.
     636             :     std::vector<GDALRasterBand *> m_oGainBands{};
     637             : 
     638             :     //! Vector of size nInBands that point to the raster band from which to read offsets.
     639             :     std::vector<GDALRasterBand *> m_oOffsetBands{};
     640             : 
     641             :     //! Working buffer that contain gain values.
     642             :     std::vector<VRTProcessedDataset::NoInitByte> m_abyGainBuffer{};
     643             : 
     644             :     //! Working buffer that contain offset values.
     645             :     std::vector<VRTProcessedDataset::NoInitByte> m_abyOffsetBuffer{};
     646             : };
     647             : }  // namespace
     648             : 
     649             : /************************************************************************/
     650             : /*                           CheckAllBands()                            */
     651             : /************************************************************************/
     652             : 
     653             : /** Return true if the key of oMap is the sequence of all integers between
     654             :  * 0 and nExpectedBandCount-1.
     655             :  */
     656             : template <class T>
     657          28 : static bool CheckAllBands(const std::map<int, T> &oMap, int nExpectedBandCount)
     658             : {
     659          28 :     int iExpected = 0;
     660          60 :     for (const auto &kv : oMap)
     661             :     {
     662          32 :         if (kv.first != iExpected)
     663           0 :             return false;
     664          32 :         ++iExpected;
     665             :     }
     666          28 :     return iExpected == nExpectedBandCount;
     667             : }
     668             : 
     669             : /************************************************************************/
     670             : /*                        LocalScaleOffsetInit()                        */
     671             : /************************************************************************/
     672             : 
     673             : /** Init function for 'LocalScaleOffset' builtin function. */
     674             : static CPLErr
     675          11 : LocalScaleOffsetInit(const char * /*pszFuncName*/, void * /*pUserData*/,
     676             :                      CSLConstList papszFunctionArgs, int nInBands,
     677             :                      GDALDataType eInDT, double *padfInNoData, int *pnOutBands,
     678             :                      GDALDataType *peOutDT, double **ppadfOutNoData,
     679             :                      const char *pszVRTPath, VRTPDWorkingDataPtr *ppWorkingData)
     680             : {
     681          11 :     CPLAssert(eInDT == GDT_Float64);
     682             : 
     683          11 :     const bool bIsFinalStep = *pnOutBands != 0;
     684          11 :     *peOutDT = eInDT;
     685          11 :     *ppWorkingData = nullptr;
     686             : 
     687          11 :     if (!bIsFinalStep)
     688             :     {
     689           0 :         *pnOutBands = nInBands;
     690             :     }
     691             : 
     692          22 :     auto data = std::make_unique<LocalScaleOffsetData>();
     693             : 
     694          11 :     bool bNodataSpecified = false;
     695          11 :     double dfNoData = std::numeric_limits<double>::quiet_NaN();
     696             : 
     697          11 :     bool bGainNodataSpecified = false;
     698          11 :     bool bOffsetNodataSpecified = false;
     699             : 
     700          22 :     std::map<int, std::string> oGainDatasetNameMap;
     701          22 :     std::map<int, int> oGainDatasetBandMap;
     702             : 
     703          22 :     std::map<int, std::string> oOffsetDatasetNameMap;
     704          22 :     std::map<int, int> oOffsetDatasetBandMap;
     705             : 
     706          11 :     bool bRelativeToVRT = false;
     707             : 
     708          84 :     for (const auto &[pszKey, pszValue] :
     709          91 :          cpl::IterateNameValue(papszFunctionArgs))
     710             :     {
     711          44 :         if (EQUAL(pszKey, "relativeToVRT"))
     712             :         {
     713           0 :             bRelativeToVRT = CPLTestBool(pszValue);
     714             :         }
     715          44 :         else if (EQUAL(pszKey, "nodata"))
     716             :         {
     717           0 :             bNodataSpecified = true;
     718           0 :             dfNoData = CPLAtof(pszValue);
     719             :         }
     720          44 :         else if (EQUAL(pszKey, "gain_nodata"))
     721             :         {
     722           0 :             bGainNodataSpecified = true;
     723           0 :             data->m_dfGainNodata = CPLAtof(pszValue);
     724             :         }
     725          44 :         else if (EQUAL(pszKey, "offset_nodata"))
     726             :         {
     727           0 :             bOffsetNodataSpecified = true;
     728           0 :             data->m_dfOffsetNodata = CPLAtof(pszValue);
     729             :         }
     730          44 :         else if (STARTS_WITH_CI(pszKey, "gain_dataset_filename_"))
     731             :         {
     732          12 :             const int nBand = atoi(pszKey + strlen("gain_dataset_filename_"));
     733          12 :             if (nBand <= 0 || nBand > nInBands)
     734             :             {
     735           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     736             :                          "Invalid band in argument '%s'", pszKey);
     737           4 :                 return CE_Failure;
     738             :             }
     739          11 :             oGainDatasetNameMap[nBand - 1] = pszValue;
     740             :         }
     741          32 :         else if (STARTS_WITH_CI(pszKey, "gain_dataset_band_"))
     742             :         {
     743          11 :             const int nBand = atoi(pszKey + strlen("gain_dataset_band_"));
     744          11 :             if (nBand <= 0 || nBand > nInBands)
     745             :             {
     746           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     747             :                          "Invalid band in argument '%s'", pszKey);
     748           1 :                 return CE_Failure;
     749             :             }
     750          10 :             oGainDatasetBandMap[nBand - 1] = atoi(pszValue);
     751             :         }
     752          21 :         else if (STARTS_WITH_CI(pszKey, "offset_dataset_filename_"))
     753             :         {
     754          10 :             const int nBand = atoi(pszKey + strlen("offset_dataset_filename_"));
     755          10 :             if (nBand <= 0 || nBand > nInBands)
     756             :             {
     757           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     758             :                          "Invalid band in argument '%s'", pszKey);
     759           1 :                 return CE_Failure;
     760             :             }
     761           9 :             oOffsetDatasetNameMap[nBand - 1] = pszValue;
     762             :         }
     763          11 :         else if (STARTS_WITH_CI(pszKey, "offset_dataset_band_"))
     764             :         {
     765           9 :             const int nBand = atoi(pszKey + strlen("offset_dataset_band_"));
     766           9 :             if (nBand <= 0 || nBand > nInBands)
     767             :             {
     768           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     769             :                          "Invalid band in argument '%s'", pszKey);
     770           1 :                 return CE_Failure;
     771             :             }
     772           8 :             oOffsetDatasetBandMap[nBand - 1] = atoi(pszValue);
     773             :         }
     774           2 :         else if (EQUAL(pszKey, "min"))
     775             :         {
     776           1 :             data->m_dfClampMin = CPLAtof(pszValue);
     777             :         }
     778           1 :         else if (EQUAL(pszKey, "max"))
     779             :         {
     780           1 :             data->m_dfClampMax = CPLAtof(pszValue);
     781             :         }
     782             :         else
     783             :         {
     784           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     785             :                      "Unrecognized argument name %s. Ignored", pszKey);
     786             :         }
     787             :     }
     788             : 
     789           7 :     if (!CheckAllBands(oGainDatasetNameMap, nInBands))
     790             :     {
     791           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     792             :                  "Missing gain_dataset_filename_XX element(s)");
     793           0 :         return CE_Failure;
     794             :     }
     795           7 :     if (!CheckAllBands(oGainDatasetBandMap, nInBands))
     796             :     {
     797           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     798             :                  "Missing gain_dataset_band_XX element(s)");
     799           0 :         return CE_Failure;
     800             :     }
     801           7 :     if (!CheckAllBands(oOffsetDatasetNameMap, nInBands))
     802             :     {
     803           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     804             :                  "Missing offset_dataset_filename_XX element(s)");
     805           0 :         return CE_Failure;
     806             :     }
     807           7 :     if (!CheckAllBands(oOffsetDatasetBandMap, nInBands))
     808             :     {
     809           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     810             :                  "Missing offset_dataset_band_XX element(s)");
     811           0 :         return CE_Failure;
     812             :     }
     813             : 
     814           7 :     data->m_oGainBands.resize(nInBands);
     815           7 :     data->m_oOffsetBands.resize(nInBands);
     816             : 
     817           7 :     constexpr int IDX_GAIN = 0;
     818           7 :     constexpr int IDX_OFFSET = 1;
     819          15 :     for (int i : {IDX_GAIN, IDX_OFFSET})
     820             :     {
     821          11 :         const auto &oMapNames =
     822             :             (i == IDX_GAIN) ? oGainDatasetNameMap : oOffsetDatasetNameMap;
     823          11 :         const auto &oMapBands =
     824             :             (i == IDX_GAIN) ? oGainDatasetBandMap : oOffsetDatasetBandMap;
     825          21 :         for (const auto &kv : oMapNames)
     826             :         {
     827          13 :             const int nInBandIdx = kv.first;
     828             :             const auto osFilename = VRTDataset::BuildSourceFilename(
     829          13 :                 kv.second.c_str(), pszVRTPath, bRelativeToVRT);
     830          13 :             auto oIter = data->m_oDatasetMap.find(osFilename);
     831          13 :             if (oIter == data->m_oDatasetMap.end())
     832             :             {
     833             :                 auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     834             :                     osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
     835          11 :                     nullptr, nullptr, nullptr));
     836          11 :                 if (!poDS)
     837           1 :                     return CE_Failure;
     838             :                 double adfAuxGT[6];
     839          10 :                 if (poDS->GetGeoTransform(adfAuxGT) != CE_None)
     840             :                 {
     841           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
     842             :                              "%s lacks a geotransform", osFilename.c_str());
     843           1 :                     return CE_Failure;
     844             :                 }
     845           9 :                 oIter = data->m_oDatasetMap
     846           9 :                             .insert(std::pair(osFilename, std::move(poDS)))
     847             :                             .first;
     848             :             }
     849          11 :             auto poDS = oIter->second.get();
     850          11 :             const auto oIterBand = oMapBands.find(nInBandIdx);
     851          11 :             CPLAssert(oIterBand != oMapBands.end());
     852          11 :             const int nAuxBand = oIterBand->second;
     853          11 :             if (nAuxBand <= 0 || nAuxBand > poDS->GetRasterCount())
     854             :             {
     855           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     856             :                          "Invalid band number (%d) for a %s dataset", nAuxBand,
     857             :                          (i == IDX_GAIN) ? "gain" : "offset");
     858           1 :                 return CE_Failure;
     859             :             }
     860          10 :             auto poAuxBand = poDS->GetRasterBand(nAuxBand);
     861          10 :             int bAuxBandHasNoData = false;
     862             :             const double dfAuxNoData =
     863          10 :                 poAuxBand->GetNoDataValue(&bAuxBandHasNoData);
     864          10 :             if (i == IDX_GAIN)
     865             :             {
     866           5 :                 data->m_oGainBands[nInBandIdx] = poAuxBand;
     867           5 :                 if (!bGainNodataSpecified && bAuxBandHasNoData)
     868           2 :                     data->m_dfGainNodata = dfAuxNoData;
     869             :             }
     870             :             else
     871             :             {
     872           5 :                 data->m_oOffsetBands[nInBandIdx] = poAuxBand;
     873           5 :                 if (!bOffsetNodataSpecified && bAuxBandHasNoData)
     874           2 :                     data->m_dfOffsetNodata = dfAuxNoData;
     875             :             }
     876             :         }
     877             :     }
     878             : 
     879           4 :     SetOutputValuesForInNoDataAndOutNoData(
     880             :         nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bNodataSpecified,
     881             :         dfNoData, bNodataSpecified, dfNoData, bIsFinalStep);
     882             : 
     883           4 :     *ppWorkingData = data.release();
     884           4 :     return CE_None;
     885             : }
     886             : 
     887             : /************************************************************************/
     888             : /*                       LocalScaleOffsetFree()                         */
     889             : /************************************************************************/
     890             : 
     891             : /** Free function for 'LocalScaleOffset' builtin function. */
     892           4 : static void LocalScaleOffsetFree(const char * /*pszFuncName*/,
     893             :                                  void * /*pUserData*/,
     894             :                                  VRTPDWorkingDataPtr pWorkingData)
     895             : {
     896           4 :     LocalScaleOffsetData *data =
     897             :         static_cast<LocalScaleOffsetData *>(pWorkingData);
     898           4 :     CPLAssert(data->m_osSignature == LocalScaleOffsetData::EXPECTED_SIGNATURE);
     899           4 :     CPL_IGNORE_RET_VAL(data->m_osSignature);
     900           4 :     delete data;
     901           4 : }
     902             : 
     903             : /************************************************************************/
     904             : /*                          LoadAuxData()                               */
     905             : /************************************************************************/
     906             : 
     907             : // Load auxiliary corresponding offset, gain or trimming data.
     908          17 : static bool LoadAuxData(double dfULX, double dfULY, double dfLRX, double dfLRY,
     909             :                         size_t nElts, int nBufXSize, int nBufYSize,
     910             :                         const char *pszAuxType, GDALRasterBand *poAuxBand,
     911             :                         std::vector<VRTProcessedDataset::NoInitByte> &abyBuffer)
     912             : {
     913             :     double adfAuxGT[6];
     914             :     double adfAuxInvGT[6];
     915             : 
     916             :     // Compute pixel/line coordinates from the georeferenced extent
     917          34 :     CPL_IGNORE_RET_VAL(poAuxBand->GetDataset()->GetGeoTransform(
     918          17 :         adfAuxGT));  // return code already tested
     919          17 :     CPL_IGNORE_RET_VAL(GDALInvGeoTransform(adfAuxGT, adfAuxInvGT));
     920          17 :     const double dfULPixel =
     921          17 :         adfAuxInvGT[0] + adfAuxInvGT[1] * dfULX + adfAuxInvGT[2] * dfULY;
     922          17 :     const double dfULLine =
     923          17 :         adfAuxInvGT[3] + adfAuxInvGT[4] * dfULX + adfAuxInvGT[5] * dfULY;
     924          17 :     const double dfLRPixel =
     925          17 :         adfAuxInvGT[0] + adfAuxInvGT[1] * dfLRX + adfAuxInvGT[2] * dfLRY;
     926          17 :     const double dfLRLine =
     927          17 :         adfAuxInvGT[3] + adfAuxInvGT[4] * dfLRX + adfAuxInvGT[5] * dfLRY;
     928          17 :     if (dfULPixel >= dfLRPixel || dfULLine >= dfLRLine)
     929             :     {
     930           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     931             :                  "Unexpected computed %s pixel/line", pszAuxType);
     932           0 :         return false;
     933             :     }
     934          17 :     if (dfULPixel < -1 || dfULLine < -1)
     935             :     {
     936           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     937             :                  "Unexpected computed %s upper left (pixel,line)=(%f,%f)",
     938             :                  pszAuxType, dfULPixel, dfULLine);
     939           0 :         return false;
     940             :     }
     941          34 :     if (dfLRPixel > poAuxBand->GetXSize() + 1 ||
     942          17 :         dfLRLine > poAuxBand->GetYSize() + 1)
     943             :     {
     944           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     945             :                  "Unexpected computed %s lower right (pixel,line)=(%f,%f)",
     946             :                  pszAuxType, dfLRPixel, dfLRLine);
     947           0 :         return false;
     948             :     }
     949             : 
     950          34 :     const int nAuxXOff = std::clamp(static_cast<int>(std::round(dfULPixel)), 0,
     951          17 :                                     poAuxBand->GetXSize() - 1);
     952          34 :     const int nAuxYOff = std::clamp(static_cast<int>(std::round(dfULLine)), 0,
     953          17 :                                     poAuxBand->GetYSize() - 1);
     954          51 :     const int nAuxX2Off = std::min(poAuxBand->GetXSize(),
     955          17 :                                    static_cast<int>(std::round(dfLRPixel)));
     956             :     const int nAuxY2Off =
     957          17 :         std::min(poAuxBand->GetYSize(), static_cast<int>(std::round(dfLRLine)));
     958             : 
     959             :     try
     960             :     {
     961          17 :         abyBuffer.resize(nElts * sizeof(float));
     962             :     }
     963           0 :     catch (const std::bad_alloc &)
     964             :     {
     965           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
     966             :                  "Out of memory allocating working buffer");
     967           0 :         return false;
     968             :     }
     969             :     GDALRasterIOExtraArg sExtraArg;
     970          17 :     INIT_RASTERIO_EXTRA_ARG(sExtraArg);
     971          17 :     sExtraArg.bFloatingPointWindowValidity = true;
     972          17 :     CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
     973          17 :     sExtraArg.eResampleAlg = GRIORA_Bilinear;
     974          17 :     sExtraArg.dfXOff = std::max(0.0, dfULPixel);
     975          17 :     sExtraArg.dfYOff = std::max(0.0, dfULLine);
     976          17 :     sExtraArg.dfXSize = std::min<double>(poAuxBand->GetXSize(), dfLRPixel) -
     977          17 :                         std::max(0.0, dfULPixel);
     978          17 :     sExtraArg.dfYSize = std::min<double>(poAuxBand->GetYSize(), dfLRLine) -
     979          17 :                         std::max(0.0, dfULLine);
     980          17 :     return (poAuxBand->RasterIO(
     981          17 :                 GF_Read, nAuxXOff, nAuxYOff, std::max(1, nAuxX2Off - nAuxXOff),
     982          17 :                 std::max(1, nAuxY2Off - nAuxYOff), abyBuffer.data(), nBufXSize,
     983          17 :                 nBufYSize, GDT_Float32, 0, 0, &sExtraArg) == CE_None);
     984             : }
     985             : 
     986             : /************************************************************************/
     987             : /*                      LocalScaleOffsetProcess()                       */
     988             : /************************************************************************/
     989             : 
     990             : /** Processing function for 'LocalScaleOffset' builtin function. */
     991           7 : static CPLErr LocalScaleOffsetProcess(
     992             :     const char * /*pszFuncName*/, void * /*pUserData*/,
     993             :     VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs*/,
     994             :     int nBufXSize, int nBufYSize, const void *pInBuffer, size_t nInBufferSize,
     995             :     GDALDataType eInDT, int nInBands, const double *CPL_RESTRICT padfInNoData,
     996             :     void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, int nOutBands,
     997             :     const double *CPL_RESTRICT padfOutNoData, double dfSrcXOff,
     998             :     double dfSrcYOff, double dfSrcXSize, double dfSrcYSize,
     999             :     const double adfSrcGT[], const char * /* pszVRTPath */,
    1000             :     CSLConstList /*papszExtra*/)
    1001             : {
    1002           7 :     const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
    1003             : 
    1004           7 :     CPL_IGNORE_RET_VAL(eInDT);
    1005           7 :     CPLAssert(eInDT == GDT_Float64);
    1006           7 :     CPL_IGNORE_RET_VAL(eOutDT);
    1007           7 :     CPLAssert(eOutDT == GDT_Float64);
    1008           7 :     CPL_IGNORE_RET_VAL(nInBufferSize);
    1009           7 :     CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
    1010           7 :     CPL_IGNORE_RET_VAL(nOutBufferSize);
    1011           7 :     CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
    1012           7 :     CPLAssert(nInBands == nOutBands);
    1013           7 :     CPL_IGNORE_RET_VAL(nOutBands);
    1014             : 
    1015           7 :     LocalScaleOffsetData *data =
    1016             :         static_cast<LocalScaleOffsetData *>(pWorkingData);
    1017           7 :     CPLAssert(data->m_osSignature == LocalScaleOffsetData::EXPECTED_SIGNATURE);
    1018           7 :     const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
    1019           7 :     double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
    1020             : 
    1021             :     // Compute georeferenced extent of input region
    1022           7 :     const double dfULX =
    1023           7 :         adfSrcGT[0] + adfSrcGT[1] * dfSrcXOff + adfSrcGT[2] * dfSrcYOff;
    1024           7 :     const double dfULY =
    1025           7 :         adfSrcGT[3] + adfSrcGT[4] * dfSrcXOff + adfSrcGT[5] * dfSrcYOff;
    1026           7 :     const double dfLRX = adfSrcGT[0] + adfSrcGT[1] * (dfSrcXOff + dfSrcXSize) +
    1027           7 :                          adfSrcGT[2] * (dfSrcYOff + dfSrcYSize);
    1028           7 :     const double dfLRY = adfSrcGT[3] + adfSrcGT[4] * (dfSrcXOff + dfSrcXSize) +
    1029           7 :                          adfSrcGT[5] * (dfSrcYOff + dfSrcYSize);
    1030             : 
    1031           7 :     auto &abyOffsetBuffer = data->m_abyGainBuffer;
    1032           7 :     auto &abyGainBuffer = data->m_abyOffsetBuffer;
    1033             : 
    1034          15 :     for (int iBand = 0; iBand < nInBands; ++iBand)
    1035             :     {
    1036           8 :         if (!LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize,
    1037           8 :                          nBufYSize, "gain", data->m_oGainBands[iBand],
    1038          16 :                          abyGainBuffer) ||
    1039           8 :             !LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize,
    1040           8 :                          nBufYSize, "offset", data->m_oOffsetBands[iBand],
    1041             :                          abyOffsetBuffer))
    1042             :         {
    1043           0 :             return CE_Failure;
    1044             :         }
    1045             : 
    1046           8 :         const double *CPL_RESTRICT padfSrcThisBand = padfSrc + iBand;
    1047           8 :         double *CPL_RESTRICT padfDstThisBand = padfDst + iBand;
    1048             :         const float *pafGain =
    1049           8 :             reinterpret_cast<const float *>(abyGainBuffer.data());
    1050             :         const float *pafOffset =
    1051           8 :             reinterpret_cast<const float *>(abyOffsetBuffer.data());
    1052           8 :         const double dfSrcNodata = padfInNoData[iBand];
    1053           8 :         const double dfDstNodata = padfOutNoData[iBand];
    1054           8 :         const double dfGainNodata = data->m_dfGainNodata;
    1055           8 :         const double dfOffsetNodata = data->m_dfOffsetNodata;
    1056           8 :         const double dfClampMin = data->m_dfClampMin;
    1057           8 :         const double dfClampMax = data->m_dfClampMax;
    1058       66084 :         for (size_t i = 0; i < nElts; ++i)
    1059             :         {
    1060       66076 :             const double dfSrcVal = *padfSrcThisBand;
    1061             :             // written this way to work with a NaN value
    1062       66076 :             if (!(dfSrcVal != dfSrcNodata))
    1063             :             {
    1064           2 :                 *padfDstThisBand = dfDstNodata;
    1065             :             }
    1066             :             else
    1067             :             {
    1068       66074 :                 const double dfGain = pafGain[i];
    1069       66074 :                 const double dfOffset = pafOffset[i];
    1070       66074 :                 if (!(dfGain != dfGainNodata) || !(dfOffset != dfOffsetNodata))
    1071             :                 {
    1072           4 :                     *padfDstThisBand = dfDstNodata;
    1073             :                 }
    1074             :                 else
    1075             :                 {
    1076       66070 :                     double dfUnscaled = dfSrcVal * dfGain - dfOffset;
    1077       66070 :                     if (dfUnscaled < dfClampMin)
    1078           2 :                         dfUnscaled = dfClampMin;
    1079       66070 :                     if (dfUnscaled > dfClampMax)
    1080           1 :                         dfUnscaled = dfClampMax;
    1081             : 
    1082       66070 :                     *padfDstThisBand = dfUnscaled;
    1083             :                 }
    1084             :             }
    1085       66076 :             padfSrcThisBand += nInBands;
    1086       66076 :             padfDstThisBand += nInBands;
    1087             :         }
    1088             :     }
    1089             : 
    1090           7 :     return CE_None;
    1091             : }
    1092             : 
    1093             : /************************************************************************/
    1094             : /*                           TrimmingData                               */
    1095             : /************************************************************************/
    1096             : 
    1097             : namespace
    1098             : {
    1099             : /** Working structure for 'Trimming' builtin function. */
    1100             : struct TrimmingData
    1101             : {
    1102             :     static constexpr const char *const EXPECTED_SIGNATURE = "Trimming";
    1103             :     //! Signature (to make sure callback functions are called with the right argument)
    1104             :     const std::string m_osSignature = EXPECTED_SIGNATURE;
    1105             : 
    1106             :     //! Nodata value for trimming dataset
    1107             :     double m_dfTrimmingNodata = std::numeric_limits<double>::quiet_NaN();
    1108             : 
    1109             :     //! Maximum saturating RGB output value.
    1110             :     double m_dfTopRGB = 0;
    1111             : 
    1112             :     //! Maximum threshold beyond which we give up saturation
    1113             :     double m_dfToneCeil = 0;
    1114             : 
    1115             :     //! Margin to allow for dynamics in brighest areas (in [0,1] range)
    1116             :     double m_dfTopMargin = 0;
    1117             : 
    1118             :     //! Index (zero-based) of input/output red band.
    1119             :     int m_nRedBand = 1 - 1;
    1120             : 
    1121             :     //! Index (zero-based) of input/output green band.
    1122             :     int m_nGreenBand = 2 - 1;
    1123             : 
    1124             :     //! Index (zero-based) of input/output blue band.
    1125             :     int m_nBlueBand = 3 - 1;
    1126             : 
    1127             :     //! Trimming dataset
    1128             :     std::unique_ptr<GDALDataset> m_poTrimmingDS{};
    1129             : 
    1130             :     //! Trimming raster band.
    1131             :     GDALRasterBand *m_poTrimmingBand = nullptr;
    1132             : 
    1133             :     //! Working buffer that contain trimming values.
    1134             :     std::vector<VRTProcessedDataset::NoInitByte> m_abyTrimmingBuffer{};
    1135             : };
    1136             : }  // namespace
    1137             : 
    1138             : /************************************************************************/
    1139             : /*                           TrimmingInit()                             */
    1140             : /************************************************************************/
    1141             : 
    1142             : /** Init function for 'Trimming' builtin function. */
    1143          12 : static CPLErr TrimmingInit(const char * /*pszFuncName*/, void * /*pUserData*/,
    1144             :                            CSLConstList papszFunctionArgs, int nInBands,
    1145             :                            GDALDataType eInDT, double *padfInNoData,
    1146             :                            int *pnOutBands, GDALDataType *peOutDT,
    1147             :                            double **ppadfOutNoData, const char *pszVRTPath,
    1148             :                            VRTPDWorkingDataPtr *ppWorkingData)
    1149             : {
    1150          12 :     CPLAssert(eInDT == GDT_Float64);
    1151             : 
    1152          12 :     const bool bIsFinalStep = *pnOutBands != 0;
    1153          12 :     *peOutDT = eInDT;
    1154          12 :     *ppWorkingData = nullptr;
    1155             : 
    1156          12 :     if (!bIsFinalStep)
    1157             :     {
    1158           0 :         *pnOutBands = nInBands;
    1159             :     }
    1160             : 
    1161          24 :     auto data = std::make_unique<TrimmingData>();
    1162             : 
    1163          12 :     bool bNodataSpecified = false;
    1164          12 :     double dfNoData = std::numeric_limits<double>::quiet_NaN();
    1165          24 :     std::string osTrimmingFilename;
    1166          12 :     bool bTrimmingNodataSpecified = false;
    1167          12 :     bool bRelativeToVRT = false;
    1168             : 
    1169          84 :     for (const auto &[pszKey, pszValue] :
    1170          90 :          cpl::IterateNameValue(papszFunctionArgs))
    1171             :     {
    1172          45 :         if (EQUAL(pszKey, "relativeToVRT"))
    1173             :         {
    1174           0 :             bRelativeToVRT = CPLTestBool(pszValue);
    1175             :         }
    1176          45 :         else if (EQUAL(pszKey, "nodata"))
    1177             :         {
    1178           0 :             bNodataSpecified = true;
    1179           0 :             dfNoData = CPLAtof(pszValue);
    1180             :         }
    1181          45 :         else if (EQUAL(pszKey, "trimming_nodata"))
    1182             :         {
    1183           0 :             bTrimmingNodataSpecified = true;
    1184           0 :             data->m_dfTrimmingNodata = CPLAtof(pszValue);
    1185             :         }
    1186          45 :         else if (EQUAL(pszKey, "trimming_dataset_filename"))
    1187             :         {
    1188          12 :             osTrimmingFilename = pszValue;
    1189             :         }
    1190          33 :         else if (EQUAL(pszKey, "red_band"))
    1191             :         {
    1192           5 :             const int nBand = atoi(pszValue) - 1;
    1193           5 :             if (nBand < 0 || nBand >= nInBands)
    1194             :             {
    1195           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1196             :                          "Invalid band in argument '%s'", pszKey);
    1197           6 :                 return CE_Failure;
    1198             :             }
    1199           3 :             data->m_nRedBand = nBand;
    1200             :         }
    1201          28 :         else if (EQUAL(pszKey, "green_band"))
    1202             :         {
    1203           5 :             const int nBand = atoi(pszValue) - 1;
    1204           5 :             if (nBand < 0 || nBand >= nInBands)
    1205             :             {
    1206           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1207             :                          "Invalid band in argument '%s'", pszKey);
    1208           2 :                 return CE_Failure;
    1209             :             }
    1210           3 :             data->m_nGreenBand = nBand;
    1211             :         }
    1212          23 :         else if (EQUAL(pszKey, "blue_band"))
    1213             :         {
    1214           5 :             const int nBand = atoi(pszValue) - 1;
    1215           5 :             if (nBand < 0 || nBand >= nInBands)
    1216             :             {
    1217           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1218             :                          "Invalid band in argument '%s'", pszKey);
    1219           2 :                 return CE_Failure;
    1220             :             }
    1221           3 :             data->m_nBlueBand = nBand;
    1222             :         }
    1223          18 :         else if (EQUAL(pszKey, "top_rgb"))
    1224             :         {
    1225           6 :             data->m_dfTopRGB = CPLAtof(pszValue);
    1226             :         }
    1227          12 :         else if (EQUAL(pszKey, "tone_ceil"))
    1228             :         {
    1229           6 :             data->m_dfToneCeil = CPLAtof(pszValue);
    1230             :         }
    1231           6 :         else if (EQUAL(pszKey, "top_margin"))
    1232             :         {
    1233           6 :             data->m_dfTopMargin = CPLAtof(pszValue);
    1234             :         }
    1235             :         else
    1236             :         {
    1237           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1238             :                      "Unrecognized argument name %s. Ignored", pszKey);
    1239             :         }
    1240             :     }
    1241             : 
    1242           6 :     if (data->m_nRedBand == data->m_nGreenBand ||
    1243          10 :         data->m_nRedBand == data->m_nBlueBand ||
    1244           4 :         data->m_nGreenBand == data->m_nBlueBand)
    1245             :     {
    1246           3 :         CPLError(
    1247             :             CE_Failure, CPLE_NotSupported,
    1248             :             "red_band, green_band and blue_band must have distinct values");
    1249           3 :         return CE_Failure;
    1250             :     }
    1251             : 
    1252             :     const auto osFilename = VRTDataset::BuildSourceFilename(
    1253           6 :         osTrimmingFilename.c_str(), pszVRTPath, bRelativeToVRT);
    1254           3 :     data->m_poTrimmingDS.reset(GDALDataset::Open(
    1255             :         osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, nullptr,
    1256             :         nullptr, nullptr));
    1257           3 :     if (!data->m_poTrimmingDS)
    1258           1 :         return CE_Failure;
    1259           2 :     if (data->m_poTrimmingDS->GetRasterCount() != 1)
    1260             :     {
    1261           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    1262             :                  "Trimming dataset should have a single band");
    1263           1 :         return CE_Failure;
    1264             :     }
    1265           1 :     data->m_poTrimmingBand = data->m_poTrimmingDS->GetRasterBand(1);
    1266             : 
    1267             :     double adfAuxGT[6];
    1268           1 :     if (data->m_poTrimmingDS->GetGeoTransform(adfAuxGT) != CE_None)
    1269             :     {
    1270           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s lacks a geotransform",
    1271             :                  osFilename.c_str());
    1272           0 :         return CE_Failure;
    1273             :     }
    1274           1 :     int bAuxBandHasNoData = false;
    1275             :     const double dfAuxNoData =
    1276           1 :         data->m_poTrimmingBand->GetNoDataValue(&bAuxBandHasNoData);
    1277           1 :     if (!bTrimmingNodataSpecified && bAuxBandHasNoData)
    1278           0 :         data->m_dfTrimmingNodata = dfAuxNoData;
    1279             : 
    1280           1 :     SetOutputValuesForInNoDataAndOutNoData(
    1281             :         nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bNodataSpecified,
    1282             :         dfNoData, bNodataSpecified, dfNoData, bIsFinalStep);
    1283             : 
    1284           1 :     *ppWorkingData = data.release();
    1285           1 :     return CE_None;
    1286             : }
    1287             : 
    1288             : /************************************************************************/
    1289             : /*                           TrimmingFree()                             */
    1290             : /************************************************************************/
    1291             : 
    1292             : /** Free function for 'Trimming' builtin function. */
    1293           1 : static void TrimmingFree(const char * /*pszFuncName*/, void * /*pUserData*/,
    1294             :                          VRTPDWorkingDataPtr pWorkingData)
    1295             : {
    1296           1 :     TrimmingData *data = static_cast<TrimmingData *>(pWorkingData);
    1297           1 :     CPLAssert(data->m_osSignature == TrimmingData::EXPECTED_SIGNATURE);
    1298           1 :     CPL_IGNORE_RET_VAL(data->m_osSignature);
    1299           1 :     delete data;
    1300           1 : }
    1301             : 
    1302             : /************************************************************************/
    1303             : /*                         TrimmingProcess()                            */
    1304             : /************************************************************************/
    1305             : 
    1306             : /** Processing function for 'Trimming' builtin function. */
    1307           1 : static CPLErr TrimmingProcess(
    1308             :     const char * /*pszFuncName*/, void * /*pUserData*/,
    1309             :     VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs*/,
    1310             :     int nBufXSize, int nBufYSize, const void *pInBuffer, size_t nInBufferSize,
    1311             :     GDALDataType eInDT, int nInBands, const double *CPL_RESTRICT padfInNoData,
    1312             :     void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, int nOutBands,
    1313             :     const double *CPL_RESTRICT padfOutNoData, double dfSrcXOff,
    1314             :     double dfSrcYOff, double dfSrcXSize, double dfSrcYSize,
    1315             :     const double adfSrcGT[], const char * /* pszVRTPath */,
    1316             :     CSLConstList /*papszExtra*/)
    1317             : {
    1318           1 :     const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
    1319             : 
    1320           1 :     CPL_IGNORE_RET_VAL(eInDT);
    1321           1 :     CPLAssert(eInDT == GDT_Float64);
    1322           1 :     CPL_IGNORE_RET_VAL(eOutDT);
    1323           1 :     CPLAssert(eOutDT == GDT_Float64);
    1324           1 :     CPL_IGNORE_RET_VAL(nInBufferSize);
    1325           1 :     CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
    1326           1 :     CPL_IGNORE_RET_VAL(nOutBufferSize);
    1327           1 :     CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
    1328           1 :     CPLAssert(nInBands == nOutBands);
    1329           1 :     CPL_IGNORE_RET_VAL(nOutBands);
    1330             : 
    1331           1 :     TrimmingData *data = static_cast<TrimmingData *>(pWorkingData);
    1332           1 :     CPLAssert(data->m_osSignature == TrimmingData::EXPECTED_SIGNATURE);
    1333           1 :     const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
    1334           1 :     double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
    1335             : 
    1336             :     // Compute georeferenced extent of input region
    1337           1 :     const double dfULX =
    1338           1 :         adfSrcGT[0] + adfSrcGT[1] * dfSrcXOff + adfSrcGT[2] * dfSrcYOff;
    1339           1 :     const double dfULY =
    1340           1 :         adfSrcGT[3] + adfSrcGT[4] * dfSrcXOff + adfSrcGT[5] * dfSrcYOff;
    1341           1 :     const double dfLRX = adfSrcGT[0] + adfSrcGT[1] * (dfSrcXOff + dfSrcXSize) +
    1342           1 :                          adfSrcGT[2] * (dfSrcYOff + dfSrcYSize);
    1343           1 :     const double dfLRY = adfSrcGT[3] + adfSrcGT[4] * (dfSrcXOff + dfSrcXSize) +
    1344           1 :                          adfSrcGT[5] * (dfSrcYOff + dfSrcYSize);
    1345             : 
    1346           1 :     if (!LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize, nBufYSize,
    1347             :                      "trimming", data->m_poTrimmingBand,
    1348           1 :                      data->m_abyTrimmingBuffer))
    1349             :     {
    1350           0 :         return CE_Failure;
    1351             :     }
    1352             : 
    1353             :     const float *pafTrimming =
    1354           1 :         reinterpret_cast<const float *>(data->m_abyTrimmingBuffer.data());
    1355           1 :     const int nRedBand = data->m_nRedBand;
    1356           1 :     const int nGreenBand = data->m_nGreenBand;
    1357           1 :     const int nBlueBand = data->m_nBlueBand;
    1358           1 :     const double dfTopMargin = data->m_dfTopMargin;
    1359           1 :     const double dfTopRGB = data->m_dfTopRGB;
    1360           1 :     const double dfToneCeil = data->m_dfToneCeil;
    1361             : #if !defined(trimming_non_optimized_version)
    1362           1 :     const double dfInvToneCeil = 1.0 / dfToneCeil;
    1363             : #endif
    1364             :     const bool bRGBBandsAreFirst =
    1365           1 :         std::max(std::max(nRedBand, nGreenBand), nBlueBand) <= 2;
    1366           1 :     const double dfNoDataTrimming = data->m_dfTrimmingNodata;
    1367           1 :     const double dfNoDataRed = padfInNoData[nRedBand];
    1368           1 :     const double dfNoDataGreen = padfInNoData[nGreenBand];
    1369           1 :     const double dfNoDataBlue = padfInNoData[nBlueBand];
    1370           7 :     for (size_t i = 0; i < nElts; ++i)
    1371             :     {
    1372             :         // Extract local saturation value from trimming image
    1373           6 :         const double dfLocalMaxRGB = pafTrimming[i];
    1374             :         const double dfReducedRGB =
    1375           6 :             std::min((1.0 - dfTopMargin) * dfTopRGB / dfLocalMaxRGB, 1.0);
    1376             : 
    1377           6 :         const double dfRed = padfSrc[nRedBand];
    1378           6 :         const double dfGreen = padfSrc[nGreenBand];
    1379           6 :         const double dfBlue = padfSrc[nBlueBand];
    1380           6 :         bool bNoDataPixel = false;
    1381           6 :         if ((dfLocalMaxRGB != dfNoDataTrimming) && (dfRed != dfNoDataRed) &&
    1382           6 :             (dfGreen != dfNoDataGreen) && (dfBlue != dfNoDataBlue))
    1383             :         {
    1384             :             // RGB bands specific process
    1385           6 :             const double dfMaxRGB = std::max(std::max(dfRed, dfGreen), dfBlue);
    1386             : #if !defined(trimming_non_optimized_version)
    1387           6 :             const double dfRedTimesToneRed = std::min(dfRed, dfToneCeil);
    1388           6 :             const double dfGreenTimesToneGreen = std::min(dfGreen, dfToneCeil);
    1389           6 :             const double dfBlueTimesToneBlue = std::min(dfBlue, dfToneCeil);
    1390             :             const double dfInvToneMaxRGB =
    1391           6 :                 std::max(dfMaxRGB * dfInvToneCeil, 1.0);
    1392           6 :             const double dfReducedRGBTimesInvToneMaxRGB =
    1393             :                 dfReducedRGB * dfInvToneMaxRGB;
    1394           6 :             padfDst[nRedBand] = std::min(
    1395           6 :                 dfRedTimesToneRed * dfReducedRGBTimesInvToneMaxRGB, dfTopRGB);
    1396           6 :             padfDst[nGreenBand] =
    1397          12 :                 std::min(dfGreenTimesToneGreen * dfReducedRGBTimesInvToneMaxRGB,
    1398           6 :                          dfTopRGB);
    1399           6 :             padfDst[nBlueBand] = std::min(
    1400           6 :                 dfBlueTimesToneBlue * dfReducedRGBTimesInvToneMaxRGB, dfTopRGB);
    1401             : #else
    1402             :             // Original formulas. Slightly less optimized than the above ones.
    1403             :             const double dfToneMaxRGB = std::min(dfToneCeil / dfMaxRGB, 1.0);
    1404             :             const double dfToneRed = std::min(dfToneCeil / dfRed, 1.0);
    1405             :             const double dfToneGreen = std::min(dfToneCeil / dfGreen, 1.0);
    1406             :             const double dfToneBlue = std::min(dfToneCeil / dfBlue, 1.0);
    1407             :             padfDst[nRedBand] = std::min(
    1408             :                 dfReducedRGB * dfRed * dfToneRed / dfToneMaxRGB, dfTopRGB);
    1409             :             padfDst[nGreenBand] = std::min(
    1410             :                 dfReducedRGB * dfGreen * dfToneGreen / dfToneMaxRGB, dfTopRGB);
    1411             :             padfDst[nBlueBand] = std::min(
    1412             :                 dfReducedRGB * dfBlue * dfToneBlue / dfToneMaxRGB, dfTopRGB);
    1413             : #endif
    1414             : 
    1415             :             // Other bands processing (NIR, ...): only apply RGB reduction factor
    1416           6 :             if (bRGBBandsAreFirst)
    1417             :             {
    1418             :                 // optimization
    1419          12 :                 for (int iBand = 3; iBand < nInBands; ++iBand)
    1420             :                 {
    1421           6 :                     if (padfSrc[iBand] != padfInNoData[iBand])
    1422             :                     {
    1423           6 :                         padfDst[iBand] = dfReducedRGB * padfSrc[iBand];
    1424             :                     }
    1425             :                     else
    1426             :                     {
    1427           0 :                         bNoDataPixel = true;
    1428           0 :                         break;
    1429             :                     }
    1430             :                 }
    1431             :             }
    1432             :             else
    1433             :             {
    1434           0 :                 for (int iBand = 0; iBand < nInBands; ++iBand)
    1435             :                 {
    1436           0 :                     if (iBand != nRedBand && iBand != nGreenBand &&
    1437           0 :                         iBand != nBlueBand)
    1438             :                     {
    1439           0 :                         if (padfSrc[iBand] != padfInNoData[iBand])
    1440             :                         {
    1441           0 :                             padfDst[iBand] = dfReducedRGB * padfSrc[iBand];
    1442             :                         }
    1443             :                         else
    1444             :                         {
    1445           0 :                             bNoDataPixel = true;
    1446           0 :                             break;
    1447             :                         }
    1448             :                     }
    1449             :                 }
    1450           6 :             }
    1451             :         }
    1452             :         else
    1453             :         {
    1454           0 :             bNoDataPixel = true;
    1455             :         }
    1456           6 :         if (bNoDataPixel)
    1457             :         {
    1458           0 :             for (int iBand = 0; iBand < nInBands; ++iBand)
    1459             :             {
    1460           0 :                 padfDst[iBand] = padfOutNoData[iBand];
    1461             :             }
    1462             :         }
    1463             : 
    1464           6 :         padfSrc += nInBands;
    1465           6 :         padfDst += nInBands;
    1466             :     }
    1467             : 
    1468           1 :     return CE_None;
    1469             : }
    1470             : 
    1471             : /************************************************************************/
    1472             : /*              GDALVRTRegisterDefaultProcessedDatasetFuncs()           */
    1473             : /************************************************************************/
    1474             : 
    1475             : /** Register builtin functions that can be used in a VRTProcessedDataset.
    1476             :  */
    1477        1292 : void GDALVRTRegisterDefaultProcessedDatasetFuncs()
    1478             : {
    1479        1292 :     GDALVRTRegisterProcessedDatasetFunc(
    1480             :         "BandAffineCombination", nullptr,
    1481             :         "<ProcessedDatasetFunctionArgumentsList>"
    1482             :         "   <Argument name='src_nodata' type='double' "
    1483             :         "description='Override input nodata value'/>"
    1484             :         "   <Argument name='dst_nodata' type='double' "
    1485             :         "description='Override output nodata value'/>"
    1486             :         "   <Argument name='replacement_nodata' "
    1487             :         "description='value to substitute to a valid computed value that "
    1488             :         "would be nodata' type='double'/>"
    1489             :         "   <Argument name='dst_intended_datatype' type='string' "
    1490             :         "description='Intented datatype of output (which might be "
    1491             :         "different than the working data type)'/>"
    1492             :         "   <Argument name='coefficients_{band}' "
    1493             :         "description='Comma-separated coefficients for combining bands. "
    1494             :         "First one is constant term' "
    1495             :         "type='double_list' required='true'/>"
    1496             :         "   <Argument name='min' description='clamp min value' type='double'/>"
    1497             :         "   <Argument name='max' description='clamp max value' type='double'/>"
    1498             :         "</ProcessedDatasetFunctionArgumentsList>",
    1499             :         GDT_Float64, nullptr, 0, nullptr, 0, BandAffineCombinationInit,
    1500             :         BandAffineCombinationFree, BandAffineCombinationProcess, nullptr);
    1501             : 
    1502        1292 :     GDALVRTRegisterProcessedDatasetFunc(
    1503             :         "LUT", nullptr,
    1504             :         "<ProcessedDatasetFunctionArgumentsList>"
    1505             :         "   <Argument name='src_nodata' type='double' "
    1506             :         "description='Override input nodata value'/>"
    1507             :         "   <Argument name='dst_nodata' type='double' "
    1508             :         "description='Override output nodata value'/>"
    1509             :         "   <Argument name='lut_{band}' "
    1510             :         "description='List of the form [src value 1]:[dest value 1],"
    1511             :         "[src value 2]:[dest value 2],...' "
    1512             :         "type='string' required='true'/>"
    1513             :         "</ProcessedDatasetFunctionArgumentsList>",
    1514             :         GDT_Float64, nullptr, 0, nullptr, 0, LUTInit, LUTFree, LUTProcess,
    1515             :         nullptr);
    1516             : 
    1517        1292 :     GDALVRTRegisterProcessedDatasetFunc(
    1518             :         "LocalScaleOffset", nullptr,
    1519             :         "<ProcessedDatasetFunctionArgumentsList>"
    1520             :         "   <Argument name='relativeToVRT' "
    1521             :         "description='Whether gain and offset filenames are relative to "
    1522             :         "the VRT' type='boolean' default='false'/>"
    1523             :         "   <Argument name='gain_dataset_filename_{band}' "
    1524             :         "description='Filename to the gain dataset' "
    1525             :         "type='string' required='true'/>"
    1526             :         "   <Argument name='gain_dataset_band_{band}' "
    1527             :         "description='Band of the gain dataset' "
    1528             :         "type='integer' required='true'/>"
    1529             :         "   <Argument name='offset_dataset_filename_{band}' "
    1530             :         "description='Filename to the offset dataset' "
    1531             :         "type='string' required='true'/>"
    1532             :         "   <Argument name='offset_dataset_band_{band}' "
    1533             :         "description='Band of the offset dataset' "
    1534             :         "type='integer' required='true'/>"
    1535             :         "   <Argument name='min' description='clamp min value' type='double'/>"
    1536             :         "   <Argument name='max' description='clamp max value' type='double'/>"
    1537             :         "   <Argument name='nodata' type='double' "
    1538             :         "description='Override dataset nodata value'/>"
    1539             :         "   <Argument name='gain_nodata' type='double' "
    1540             :         "description='Override gain dataset nodata value'/>"
    1541             :         "   <Argument name='offset_nodata' type='double' "
    1542             :         "description='Override offset dataset nodata value'/>"
    1543             :         "</ProcessedDatasetFunctionArgumentsList>",
    1544             :         GDT_Float64, nullptr, 0, nullptr, 0, LocalScaleOffsetInit,
    1545             :         LocalScaleOffsetFree, LocalScaleOffsetProcess, nullptr);
    1546             : 
    1547        1292 :     GDALVRTRegisterProcessedDatasetFunc(
    1548             :         "Trimming", nullptr,
    1549             :         "<ProcessedDatasetFunctionArgumentsList>"
    1550             :         "   <Argument name='relativeToVRT' "
    1551             :         "description='Whether trimming_dataset_filename is relative to the VRT'"
    1552             :         " type='boolean' default='false'/>"
    1553             :         "   <Argument name='trimming_dataset_filename' "
    1554             :         "description='Filename to the trimming dataset' "
    1555             :         "type='string' required='true'/>"
    1556             :         "   <Argument name='red_band' type='integer' default='1'/>"
    1557             :         "   <Argument name='green_band' type='integer' default='2'/>"
    1558             :         "   <Argument name='blue_band' type='integer' default='3'/>"
    1559             :         "   <Argument name='top_rgb' "
    1560             :         "description='Maximum saturating RGB output value' "
    1561             :         "type='double' required='true'/>"
    1562             :         "   <Argument name='tone_ceil' "
    1563             :         "description='Maximum threshold beyond which we give up saturation' "
    1564             :         "type='double' required='true'/>"
    1565             :         "   <Argument name='top_margin' "
    1566             :         "description='Margin to allow for dynamics in brighest areas "
    1567             :         "(between 0 and 1, should be close to 0)' "
    1568             :         "type='double' required='true'/>"
    1569             :         "   <Argument name='nodata' type='double' "
    1570             :         "description='Override dataset nodata value'/>"
    1571             :         "   <Argument name='trimming_nodata' type='double' "
    1572             :         "description='Override trimming dataset nodata value'/>"
    1573             :         "</ProcessedDatasetFunctionArgumentsList>",
    1574             :         GDT_Float64, nullptr, 0, nullptr, 0, TrimmingInit, TrimmingFree,
    1575             :         TrimmingProcess, nullptr);
    1576        1292 : }

Generated by: LCOV version 1.14