Coverage Report

Created: 2025-11-16 06:25

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