Coverage Report

Created: 2025-08-28 06:57

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