• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.99
/frmts/vrt/vrtprocesseddatasetfunctions.cpp
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,
3,178✔
35
                                 double dfReplacementDstNodata,
36
                                 GDALDataType eIntendedDstDT,
37
                                 bool bDstIntendedDTIsInteger)
38
{
39
    if (bDstIntendedDTIsInteger && std::round(dfVal) == dfDstNoData)
3,178✔
40
    {
41
        return dfReplacementDstNodata;
1✔
42
    }
43
    else if (eIntendedDstDT == GDT_Float16 &&
3,177✔
44
             static_cast<GFloat16>(dfVal) == static_cast<GFloat16>(dfDstNoData))
3,177✔
45
    {
46
        return dfReplacementDstNodata;
×
47
    }
48
    else if (eIntendedDstDT == GDT_Float32 &&
3,177✔
49
             static_cast<float>(dfVal) == static_cast<float>(dfDstNoData))
×
50
    {
51
        return dfReplacementDstNodata;
×
52
    }
53
    else if (eIntendedDstDT == GDT_Float64 && dfVal == dfDstNoData)
3,177✔
54
    {
55
        return dfReplacementDstNodata;
1✔
56
    }
57
    else
58
    {
59
        return dfVal;
3,176✔
60
    }
61
}
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(
44✔
105
    int nInBands, double *padfInNoData, int *pnOutBands,
106
    double **ppadfOutNoData, bool bSrcNodataSpecified, double dfSrcNoData,
107
    bool bDstNodataSpecified, double dfDstNoData, bool bIsFinalStep)
108
{
109
    if (bSrcNodataSpecified)
44✔
110
    {
111
        std::vector<double> adfNoData(nInBands, dfSrcNoData);
3✔
112
        memcpy(padfInNoData, adfNoData.data(),
3✔
113
               adfNoData.size() * sizeof(double));
3✔
114
    }
115

116
    std::vector<double> adfDstNoData;
44✔
117
    if (bDstNodataSpecified)
44✔
118
    {
119
        adfDstNoData.resize(*pnOutBands, dfDstNoData);
3✔
120
    }
121
    else if (bIsFinalStep)
41✔
122
    {
123
        adfDstNoData =
124
            std::vector<double>(*ppadfOutNoData, *ppadfOutNoData + *pnOutBands);
35✔
125
    }
126
    else
127
    {
128
        adfDstNoData =
129
            std::vector<double>(padfInNoData, padfInNoData + nInBands);
6✔
130
        adfDstNoData.resize(*pnOutBands, *padfInNoData);
6✔
131
    }
132

133
    if (*ppadfOutNoData == nullptr)
44✔
134
    {
135
        *ppadfOutNoData =
6✔
136
            static_cast<double *>(CPLMalloc(*pnOutBands * sizeof(double)));
6✔
137
    }
138
    memcpy(*ppadfOutNoData, adfDstNoData.data(), *pnOutBands * sizeof(double));
44✔
139

140
    return adfDstNoData;
44✔
141
}
142

143
/************************************************************************/
144
/*                    BandAffineCombinationInit()                       */
145
/************************************************************************/
146

147
/** Init function for 'BandAffineCombination' builtin function. */
148
static CPLErr BandAffineCombinationInit(
38✔
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
{
155
    CPLAssert(eInDT == GDT_Float64);
38✔
156

157
    *peOutDT = eInDT;
38✔
158
    *ppWorkingData = nullptr;
38✔
159

160
    auto data = std::make_unique<BandAffineCombinationData>();
76✔
161

162
    std::map<int, std::vector<double>> oMapCoefficients{};
76✔
163
    double dfSrcNoData = std::numeric_limits<double>::quiet_NaN();
38✔
164
    bool bSrcNodataSpecified = false;
38✔
165
    double dfDstNoData = std::numeric_limits<double>::quiet_NaN();
38✔
166
    bool bDstNodataSpecified = false;
38✔
167
    double dfReplacementDstNodata = std::numeric_limits<double>::quiet_NaN();
38✔
168
    bool bReplacementDstNodataSpecified = false;
38✔
169

170
    for (const auto &[pszKey, pszValue] :
195✔
171
         cpl::IterateNameValue(papszFunctionArgs))
232✔
172
    {
173
        if (EQUAL(pszKey, "src_nodata"))
98✔
174
        {
175
            bSrcNodataSpecified = true;
2✔
176
            dfSrcNoData = CPLAtof(pszValue);
2✔
177
        }
178
        else if (EQUAL(pszKey, "dst_nodata"))
96✔
179
        {
180
            bDstNodataSpecified = true;
2✔
181
            dfDstNoData = CPLAtof(pszValue);
2✔
182
        }
183
        else if (EQUAL(pszKey, "replacement_nodata"))
94✔
184
        {
185
            bReplacementDstNodataSpecified = true;
1✔
186
            dfReplacementDstNodata = CPLAtof(pszValue);
1✔
187
        }
188
        else if (EQUAL(pszKey, "dst_intended_datatype"))
93✔
189
        {
190
            for (GDALDataType eDT = GDT_Byte; eDT < GDT_TypeCount;
1✔
191
                 eDT = static_cast<GDALDataType>(eDT + 1))
×
192
            {
193
                if (EQUAL(GDALGetDataTypeName(eDT), pszValue))
1✔
194
                {
195
                    data->m_eIntendedDstDT = eDT;
1✔
196
                    break;
1✔
197
                }
198
            }
199
        }
200
        else if (STARTS_WITH_CI(pszKey, "coefficients_"))
92✔
201
        {
202
            const int nTargetBand = atoi(pszKey + strlen("coefficients_"));
88✔
203
            if (nTargetBand <= 0 || nTargetBand > 65536)
88✔
204
            {
205
                CPLError(CE_Failure, CPLE_AppDefined,
×
206
                         "Invalid band in argument '%s'", pszKey);
207
                return CE_Failure;
1✔
208
            }
209
            const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
88✔
210
            if (aosTokens.size() != 1 + nInBands)
88✔
211
            {
212
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
213
                         "Argument %s has %d values, whereas %d are expected",
214
                         pszKey, aosTokens.size(), 1 + nInBands);
215
                return CE_Failure;
1✔
216
            }
217
            std::vector<double> adfValues;
87✔
218
            for (int i = 0; i < aosTokens.size(); ++i)
401✔
219
            {
220
                adfValues.push_back(CPLAtof(aosTokens[i]));
314✔
221
            }
222
            oMapCoefficients[nTargetBand - 1] = std::move(adfValues);
87✔
223
        }
224
        else if (EQUAL(pszKey, "min"))
4✔
225
        {
226
            data->m_dfClampMin = CPLAtof(pszValue);
2✔
227
        }
228
        else if (EQUAL(pszKey, "max"))
2✔
229
        {
230
            data->m_dfClampMax = CPLAtof(pszValue);
2✔
231
        }
232
        else
233
        {
234
            CPLError(CE_Warning, CPLE_AppDefined,
×
235
                     "Unrecognized argument name %s. Ignored", pszKey);
236
        }
237
    }
238

239
    const bool bIsFinalStep = *pnOutBands != 0;
37✔
240
    if (bIsFinalStep)
37✔
241
    {
242
        if (*pnOutBands != static_cast<int>(oMapCoefficients.size()))
31✔
243
        {
244
            CPLError(CE_Failure, CPLE_AppDefined,
2✔
245
                     "Final step expect %d bands, but only %d coefficient_XX "
246
                     "are provided",
247
                     *pnOutBands, static_cast<int>(oMapCoefficients.size()));
2✔
248
            return CE_Failure;
2✔
249
        }
250
    }
251
    else
252
    {
253
        *pnOutBands = static_cast<int>(oMapCoefficients.size());
6✔
254
    }
255

256
    const std::vector<double> adfDstNoData =
257
        SetOutputValuesForInNoDataAndOutNoData(
258
            nInBands, padfInNoData, pnOutBands, ppadfOutNoData,
259
            bSrcNodataSpecified, dfSrcNoData, bDstNodataSpecified, dfDstNoData,
260
            bIsFinalStep);
70✔
261

262
    if (bReplacementDstNodataSpecified)
35✔
263
    {
264
        data->m_adfReplacementDstNodata.resize(*pnOutBands,
1✔
265
                                               dfReplacementDstNodata);
266
    }
267
    else
268
    {
269
        for (double dfVal : adfDstNoData)
116✔
270
        {
271
            data->m_adfReplacementDstNodata.emplace_back(
82✔
272
                GDALGetNoDataReplacementValue(data->m_eIntendedDstDT, dfVal));
82✔
273
        }
274
    }
275

276
    // Check we have a set of coefficient for all output bands and
277
    // convert the map to a vector
278
    for (auto &oIter : oMapCoefficients)
117✔
279
    {
280
        const int iExpected = static_cast<int>(data->m_aadfCoefficients.size());
84✔
281
        if (oIter.first != iExpected)
84✔
282
        {
283
            CPLError(CE_Failure, CPLE_AppDefined,
2✔
284
                     "Argument coefficients_%d is missing", iExpected + 1);
285
            return CE_Failure;
2✔
286
        }
287
        data->m_aadfCoefficients.emplace_back(std::move(oIter.second));
82✔
288
    }
289
    *ppWorkingData = data.release();
33✔
290
    return CE_None;
33✔
291
}
292

293
/************************************************************************/
294
/*                    BandAffineCombinationFree()                       */
295
/************************************************************************/
296

297
/** Free function for 'BandAffineCombination' builtin function. */
298
static void BandAffineCombinationFree(const char * /*pszFuncName*/,
33✔
299
                                      void * /*pUserData*/,
300
                                      VRTPDWorkingDataPtr pWorkingData)
301
{
302
    BandAffineCombinationData *data =
33✔
303
        static_cast<BandAffineCombinationData *>(pWorkingData);
304
    CPLAssert(data->m_osSignature ==
33✔
305
              BandAffineCombinationData::EXPECTED_SIGNATURE);
306
    CPL_IGNORE_RET_VAL(data->m_osSignature);
33✔
307
    delete data;
33✔
308
}
33✔
309

310
/************************************************************************/
311
/*                    BandAffineCombinationProcess()                    */
312
/************************************************************************/
313

314
/** Processing function for 'BandAffineCombination' builtin function. */
315
static CPLErr BandAffineCombinationProcess(
41✔
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
{
326
    const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
41✔
327

328
    CPL_IGNORE_RET_VAL(eInDT);
41✔
329
    CPLAssert(eInDT == GDT_Float64);
41✔
330
    CPL_IGNORE_RET_VAL(eOutDT);
41✔
331
    CPLAssert(eOutDT == GDT_Float64);
41✔
332
    CPL_IGNORE_RET_VAL(nInBufferSize);
41✔
333
    CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
41✔
334
    CPL_IGNORE_RET_VAL(nOutBufferSize);
41✔
335
    CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
41✔
336

337
    const BandAffineCombinationData *data =
41✔
338
        static_cast<BandAffineCombinationData *>(pWorkingData);
339
    CPLAssert(data->m_osSignature ==
41✔
340
              BandAffineCombinationData::EXPECTED_SIGNATURE);
341
    const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
41✔
342
    double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
41✔
343
    const bool bDstIntendedDTIsInteger =
344
        GDALDataTypeIsInteger(data->m_eIntendedDstDT);
41✔
345
    const double dfClampMin = data->m_dfClampMin;
41✔
346
    const double dfClampMax = data->m_dfClampMax;
41✔
347
    for (size_t i = 0; i < nElts; ++i)
1,919✔
348
    {
349
        for (int iDst = 0; iDst < nOutBands; ++iDst)
5,068✔
350
        {
351
            const auto &adfCoefficients = data->m_aadfCoefficients[iDst];
3,190✔
352
            double dfVal = adfCoefficients[0];
3,190✔
353
            bool bSetNoData = false;
3,190✔
354
            for (int iSrc = 0; iSrc < nInBands; ++iSrc)
7,940✔
355
            {
356
                // written this way to work with a NaN value
357
                if (!(padfSrc[iSrc] != padfInNoData[iSrc]))
4,762✔
358
                {
359
                    bSetNoData = true;
12✔
360
                    break;
12✔
361
                }
362
                dfVal += adfCoefficients[iSrc + 1] * padfSrc[iSrc];
4,750✔
363
            }
364
            if (bSetNoData)
3,190✔
365
            {
366
                *padfDst = padfOutNoData[iDst];
12✔
367
            }
368
            else
369
            {
370
                double dfDstVal = GetDstValue(
9,534✔
371
                    dfVal, padfOutNoData[iDst],
3,178✔
372
                    data->m_adfReplacementDstNodata[iDst],
3,178✔
373
                    data->m_eIntendedDstDT, bDstIntendedDTIsInteger);
3,178✔
374
                if (dfDstVal < dfClampMin)
3,178✔
375
                    dfDstVal = dfClampMin;
2✔
376
                if (dfDstVal > dfClampMax)
3,178✔
377
                    dfDstVal = dfClampMax;
2✔
378
                *padfDst = dfDstVal;
3,178✔
379
            }
380
            ++padfDst;
3,190✔
381
        }
382
        padfSrc += nInBands;
1,878✔
383
    }
384

385
    return CE_None;
41✔
386
}
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
18✔
412
    {
413
        const auto &adfInput = m_aadfLUTInputs[iBand];
18✔
414
        const auto &afdOutput = m_aadfLUTOutputs[iBand];
18✔
415

416
        // Find the index of the first element in the LUT input array that
417
        // is not smaller than the input value.
418
        int i = static_cast<int>(
419
            std::lower_bound(adfInput.data(), adfInput.data() + adfInput.size(),
18✔
420
                             dfInput) -
18✔
421
            adfInput.data());
18✔
422

423
        if (i == 0)
18✔
424
            return afdOutput[0];
6✔
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
        if (i == static_cast<int>(adfInput.size()))
12✔
429
            return afdOutput.back();
6✔
430

431
        if (adfInput[i] == dfInput)
6✔
432
            return afdOutput[i];
×
433

434
        // Otherwise, interpolate.
435
        return afdOutput[i - 1] + (dfInput - adfInput[i - 1]) *
6✔
436
                                      ((afdOutput[i] - afdOutput[i - 1]) /
6✔
437
                                       (adfInput[i] - adfInput[i - 1]));
6✔
438
    }
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*/,
6✔
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
{
454
    CPLAssert(eInDT == GDT_Float64);
6✔
455

456
    const bool bIsFinalStep = *pnOutBands != 0;
6✔
457
    *peOutDT = eInDT;
6✔
458
    *ppWorkingData = nullptr;
6✔
459

460
    if (!bIsFinalStep)
6✔
461
    {
462
        *pnOutBands = nInBands;
×
463
    }
464

465
    auto data = std::make_unique<LUTData>();
12✔
466

467
    double dfSrcNoData = std::numeric_limits<double>::quiet_NaN();
6✔
468
    bool bSrcNodataSpecified = false;
6✔
469
    double dfDstNoData = std::numeric_limits<double>::quiet_NaN();
6✔
470
    bool bDstNodataSpecified = false;
6✔
471

472
    std::map<int, std::pair<std::vector<double>, std::vector<double>>> oMap{};
12✔
473

474
    for (const auto &[pszKey, pszValue] :
22✔
475
         cpl::IterateNameValue(papszFunctionArgs))
26✔
476
    {
477
        if (EQUAL(pszKey, "src_nodata"))
12✔
478
        {
479
            bSrcNodataSpecified = true;
1✔
480
            dfSrcNoData = CPLAtof(pszValue);
1✔
481
        }
482
        else if (EQUAL(pszKey, "dst_nodata"))
11✔
483
        {
484
            bDstNodataSpecified = true;
1✔
485
            dfDstNoData = CPLAtof(pszValue);
1✔
486
        }
487
        else if (STARTS_WITH_CI(pszKey, "lut_"))
10✔
488
        {
489
            const int nBand = atoi(pszKey + strlen("lut_"));
10✔
490
            if (nBand <= 0 || nBand > nInBands)
10✔
491
            {
492
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
493
                         "Invalid band in argument '%s'", pszKey);
494
                return CE_Failure;
2✔
495
            }
496
            const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0));
9✔
497
            std::vector<double> adfInputValues;
9✔
498
            std::vector<double> adfOutputValues;
9✔
499
            for (int i = 0; i < aosTokens.size(); ++i)
26✔
500
            {
501
                const CPLStringList aosTokens2(
502
                    CSLTokenizeString2(aosTokens[i], ":", 0));
18✔
503
                if (aosTokens2.size() != 2)
18✔
504
                {
505
                    CPLError(CE_Failure, CPLE_AppDefined,
1✔
506
                             "Invalid value for argument '%s'", pszKey);
507
                    return CE_Failure;
1✔
508
                }
509
                adfInputValues.push_back(CPLAtof(aosTokens2[0]));
17✔
510
                adfOutputValues.push_back(CPLAtof(aosTokens2[1]));
17✔
511
            }
512
            oMap[nBand - 1] = std::pair(std::move(adfInputValues),
16✔
513
                                        std::move(adfOutputValues));
16✔
514
        }
515
        else
516
        {
517
            CPLError(CE_Warning, CPLE_AppDefined,
×
518
                     "Unrecognized argument name %s. Ignored", pszKey);
519
        }
520
    }
521

522
    SetOutputValuesForInNoDataAndOutNoData(
4✔
523
        nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bSrcNodataSpecified,
524
        dfSrcNoData, bDstNodataSpecified, dfDstNoData, bIsFinalStep);
525

526
    int iExpected = 0;
4✔
527
    // Check we have values for all bands and convert to vector
528
    for (auto &oIter : oMap)
11✔
529
    {
530
        if (oIter.first != iExpected)
7✔
531
        {
532
            CPLError(CE_Failure, CPLE_AppDefined, "Argument lut_%d is missing",
×
533
                     iExpected + 1);
534
            return CE_Failure;
×
535
        }
536
        ++iExpected;
7✔
537
        data->m_aadfLUTInputs.emplace_back(std::move(oIter.second.first));
7✔
538
        data->m_aadfLUTOutputs.emplace_back(std::move(oIter.second.second));
7✔
539
    }
540

541
    if (static_cast<int>(oMap.size()) < *pnOutBands)
4✔
542
    {
543
        CPLError(CE_Failure, CPLE_AppDefined, "Missing lut_XX element(s)");
1✔
544
        return CE_Failure;
1✔
545
    }
546

547
    *ppWorkingData = data.release();
3✔
548
    return CE_None;
3✔
549
}
550

551
/************************************************************************/
552
/*                                LUTFree()                             */
553
/************************************************************************/
554

555
/** Free function for 'LUT' builtin function. */
556
static void LUTFree(const char * /*pszFuncName*/, void * /*pUserData*/,
3✔
557
                    VRTPDWorkingDataPtr pWorkingData)
558
{
559
    LUTData *data = static_cast<LUTData *>(pWorkingData);
3✔
560
    CPLAssert(data->m_osSignature == LUTData::EXPECTED_SIGNATURE);
3✔
561
    CPL_IGNORE_RET_VAL(data->m_osSignature);
3✔
562
    delete data;
3✔
563
}
3✔
564

565
/************************************************************************/
566
/*                             LUTProcess()                             */
567
/************************************************************************/
568

569
/** Processing function for 'LUT' builtin function. */
570
static CPLErr
571
LUTProcess(const char * /*pszFuncName*/, void * /*pUserData*/,
3✔
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
{
582
    const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
3✔
583

584
    CPL_IGNORE_RET_VAL(eInDT);
3✔
585
    CPLAssert(eInDT == GDT_Float64);
3✔
586
    CPL_IGNORE_RET_VAL(eOutDT);
3✔
587
    CPLAssert(eOutDT == GDT_Float64);
3✔
588
    CPL_IGNORE_RET_VAL(nInBufferSize);
3✔
589
    CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
3✔
590
    CPL_IGNORE_RET_VAL(nOutBufferSize);
3✔
591
    CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
3✔
592
    CPLAssert(nInBands == nOutBands);
3✔
593
    CPL_IGNORE_RET_VAL(nOutBands);
3✔
594

595
    const LUTData *data = static_cast<LUTData *>(pWorkingData);
3✔
596
    CPLAssert(data->m_osSignature == LUTData::EXPECTED_SIGNATURE);
3✔
597
    const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
3✔
598
    double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
3✔
599
    for (size_t i = 0; i < nElts; ++i)
14✔
600
    {
601
        for (int iBand = 0; iBand < nInBands; ++iBand)
33✔
602
        {
603
            // written this way to work with a NaN value
604
            if (!(*padfSrc != padfInNoData[iBand]))
22✔
605
                *padfDst = padfOutNoData[iBand];
4✔
606
            else
607
                *padfDst = data->LookupValue(iBand, *padfSrc);
18✔
608
            ++padfSrc;
22✔
609
            ++padfDst;
22✔
610
        }
611
    }
612

613
    return CE_None;
3✔
614
}
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)
28✔
667
{
668
    int iExpected = 0;
28✔
669
    for (const auto &kv : oMap)
60✔
670
    {
671
        if (kv.first != iExpected)
32✔
672
            return false;
×
673
        ++iExpected;
32✔
674
    }
675
    return iExpected == nExpectedBandCount;
28✔
676
}
677

678
/************************************************************************/
679
/*                        LocalScaleOffsetInit()                        */
680
/************************************************************************/
681

682
/** Init function for 'LocalScaleOffset' builtin function. */
683
static CPLErr
684
LocalScaleOffsetInit(const char * /*pszFuncName*/, void * /*pUserData*/,
11✔
685
                     CSLConstList papszFunctionArgs, int nInBands,
686
                     GDALDataType eInDT, double *padfInNoData, int *pnOutBands,
687
                     GDALDataType *peOutDT, double **ppadfOutNoData,
688
                     const char *pszVRTPath, VRTPDWorkingDataPtr *ppWorkingData)
689
{
690
    CPLAssert(eInDT == GDT_Float64);
11✔
691

692
    const bool bIsFinalStep = *pnOutBands != 0;
11✔
693
    *peOutDT = eInDT;
11✔
694
    *ppWorkingData = nullptr;
11✔
695

696
    if (!bIsFinalStep)
11✔
697
    {
698
        *pnOutBands = nInBands;
×
699
    }
700

701
    auto data = std::make_unique<LocalScaleOffsetData>();
22✔
702

703
    bool bNodataSpecified = false;
11✔
704
    double dfNoData = std::numeric_limits<double>::quiet_NaN();
11✔
705

706
    bool bGainNodataSpecified = false;
11✔
707
    bool bOffsetNodataSpecified = false;
11✔
708

709
    std::map<int, std::string> oGainDatasetNameMap;
22✔
710
    std::map<int, int> oGainDatasetBandMap;
22✔
711

712
    std::map<int, std::string> oOffsetDatasetNameMap;
22✔
713
    std::map<int, int> oOffsetDatasetBandMap;
22✔
714

715
    bool bRelativeToVRT = false;
11✔
716

717
    for (const auto &[pszKey, pszValue] :
84✔
718
         cpl::IterateNameValue(papszFunctionArgs))
91✔
719
    {
720
        if (EQUAL(pszKey, "relativeToVRT"))
44✔
721
        {
722
            bRelativeToVRT = CPLTestBool(pszValue);
×
723
        }
724
        else if (EQUAL(pszKey, "nodata"))
44✔
725
        {
726
            bNodataSpecified = true;
×
727
            dfNoData = CPLAtof(pszValue);
×
728
        }
729
        else if (EQUAL(pszKey, "gain_nodata"))
44✔
730
        {
731
            bGainNodataSpecified = true;
×
732
            data->m_dfGainNodata = CPLAtof(pszValue);
×
733
        }
734
        else if (EQUAL(pszKey, "offset_nodata"))
44✔
735
        {
736
            bOffsetNodataSpecified = true;
×
737
            data->m_dfOffsetNodata = CPLAtof(pszValue);
×
738
        }
739
        else if (STARTS_WITH_CI(pszKey, "gain_dataset_filename_"))
44✔
740
        {
741
            const int nBand = atoi(pszKey + strlen("gain_dataset_filename_"));
12✔
742
            if (nBand <= 0 || nBand > nInBands)
12✔
743
            {
744
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
745
                         "Invalid band in argument '%s'", pszKey);
746
                return CE_Failure;
4✔
747
            }
748
            oGainDatasetNameMap[nBand - 1] = pszValue;
11✔
749
        }
750
        else if (STARTS_WITH_CI(pszKey, "gain_dataset_band_"))
32✔
751
        {
752
            const int nBand = atoi(pszKey + strlen("gain_dataset_band_"));
11✔
753
            if (nBand <= 0 || nBand > nInBands)
11✔
754
            {
755
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
756
                         "Invalid band in argument '%s'", pszKey);
757
                return CE_Failure;
1✔
758
            }
759
            oGainDatasetBandMap[nBand - 1] = atoi(pszValue);
10✔
760
        }
761
        else if (STARTS_WITH_CI(pszKey, "offset_dataset_filename_"))
21✔
762
        {
763
            const int nBand = atoi(pszKey + strlen("offset_dataset_filename_"));
10✔
764
            if (nBand <= 0 || nBand > nInBands)
10✔
765
            {
766
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
767
                         "Invalid band in argument '%s'", pszKey);
768
                return CE_Failure;
1✔
769
            }
770
            oOffsetDatasetNameMap[nBand - 1] = pszValue;
9✔
771
        }
772
        else if (STARTS_WITH_CI(pszKey, "offset_dataset_band_"))
11✔
773
        {
774
            const int nBand = atoi(pszKey + strlen("offset_dataset_band_"));
9✔
775
            if (nBand <= 0 || nBand > nInBands)
9✔
776
            {
777
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
778
                         "Invalid band in argument '%s'", pszKey);
779
                return CE_Failure;
1✔
780
            }
781
            oOffsetDatasetBandMap[nBand - 1] = atoi(pszValue);
8✔
782
        }
783
        else if (EQUAL(pszKey, "min"))
2✔
784
        {
785
            data->m_dfClampMin = CPLAtof(pszValue);
1✔
786
        }
787
        else if (EQUAL(pszKey, "max"))
1✔
788
        {
789
            data->m_dfClampMax = CPLAtof(pszValue);
1✔
790
        }
791
        else
792
        {
793
            CPLError(CE_Warning, CPLE_AppDefined,
×
794
                     "Unrecognized argument name %s. Ignored", pszKey);
795
        }
796
    }
797

798
    if (!CheckAllBands(oGainDatasetNameMap, nInBands))
7✔
799
    {
800
        CPLError(CE_Failure, CPLE_AppDefined,
×
801
                 "Missing gain_dataset_filename_XX element(s)");
802
        return CE_Failure;
×
803
    }
804
    if (!CheckAllBands(oGainDatasetBandMap, nInBands))
7✔
805
    {
806
        CPLError(CE_Failure, CPLE_AppDefined,
×
807
                 "Missing gain_dataset_band_XX element(s)");
808
        return CE_Failure;
×
809
    }
810
    if (!CheckAllBands(oOffsetDatasetNameMap, nInBands))
7✔
811
    {
812
        CPLError(CE_Failure, CPLE_AppDefined,
×
813
                 "Missing offset_dataset_filename_XX element(s)");
814
        return CE_Failure;
×
815
    }
816
    if (!CheckAllBands(oOffsetDatasetBandMap, nInBands))
7✔
817
    {
818
        CPLError(CE_Failure, CPLE_AppDefined,
×
819
                 "Missing offset_dataset_band_XX element(s)");
820
        return CE_Failure;
×
821
    }
822

823
    data->m_oGainBands.resize(nInBands);
7✔
824
    data->m_oOffsetBands.resize(nInBands);
7✔
825

826
    constexpr int IDX_GAIN = 0;
7✔
827
    constexpr int IDX_OFFSET = 1;
7✔
828
    for (int i : {IDX_GAIN, IDX_OFFSET})
15✔
829
    {
830
        const auto &oMapNames =
11✔
831
            (i == IDX_GAIN) ? oGainDatasetNameMap : oOffsetDatasetNameMap;
832
        const auto &oMapBands =
11✔
833
            (i == IDX_GAIN) ? oGainDatasetBandMap : oOffsetDatasetBandMap;
834
        for (const auto &kv : oMapNames)
21✔
835
        {
836
            const int nInBandIdx = kv.first;
13✔
837
            const auto osFilename = GDALDataset::BuildFilename(
838
                kv.second.c_str(), pszVRTPath, bRelativeToVRT);
13✔
839
            auto oIter = data->m_oDatasetMap.find(osFilename);
13✔
840
            if (oIter == data->m_oDatasetMap.end())
13✔
841
            {
842
                auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
843
                    osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
844
                    nullptr, nullptr, nullptr));
11✔
845
                if (!poDS)
11✔
846
                    return CE_Failure;
1✔
847
                GDALGeoTransform auxGT;
10✔
848
                if (poDS->GetGeoTransform(auxGT) != CE_None)
10✔
849
                {
850
                    CPLError(CE_Failure, CPLE_AppDefined,
1✔
851
                             "%s lacks a geotransform", osFilename.c_str());
852
                    return CE_Failure;
1✔
853
                }
854
                oIter = data->m_oDatasetMap
9✔
855
                            .insert(std::pair(osFilename, std::move(poDS)))
9✔
856
                            .first;
857
            }
858
            auto poDS = oIter->second.get();
11✔
859
            const auto oIterBand = oMapBands.find(nInBandIdx);
11✔
860
            CPLAssert(oIterBand != oMapBands.end());
11✔
861
            const int nAuxBand = oIterBand->second;
11✔
862
            if (nAuxBand <= 0 || nAuxBand > poDS->GetRasterCount())
11✔
863
            {
864
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
865
                         "Invalid band number (%d) for a %s dataset", nAuxBand,
866
                         (i == IDX_GAIN) ? "gain" : "offset");
867
                return CE_Failure;
1✔
868
            }
869
            auto poAuxBand = poDS->GetRasterBand(nAuxBand);
10✔
870
            int bAuxBandHasNoData = false;
10✔
871
            const double dfAuxNoData =
872
                poAuxBand->GetNoDataValue(&bAuxBandHasNoData);
10✔
873
            if (i == IDX_GAIN)
10✔
874
            {
875
                data->m_oGainBands[nInBandIdx] = poAuxBand;
5✔
876
                if (!bGainNodataSpecified && bAuxBandHasNoData)
5✔
877
                    data->m_dfGainNodata = dfAuxNoData;
2✔
878
            }
879
            else
880
            {
881
                data->m_oOffsetBands[nInBandIdx] = poAuxBand;
5✔
882
                if (!bOffsetNodataSpecified && bAuxBandHasNoData)
5✔
883
                    data->m_dfOffsetNodata = dfAuxNoData;
2✔
884
            }
885
        }
886
    }
887

888
    SetOutputValuesForInNoDataAndOutNoData(
4✔
889
        nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bNodataSpecified,
890
        dfNoData, bNodataSpecified, dfNoData, bIsFinalStep);
891

892
    *ppWorkingData = data.release();
4✔
893
    return CE_None;
4✔
894
}
895

896
/************************************************************************/
897
/*                       LocalScaleOffsetFree()                         */
898
/************************************************************************/
899

900
/** Free function for 'LocalScaleOffset' builtin function. */
901
static void LocalScaleOffsetFree(const char * /*pszFuncName*/,
4✔
902
                                 void * /*pUserData*/,
903
                                 VRTPDWorkingDataPtr pWorkingData)
904
{
905
    LocalScaleOffsetData *data =
4✔
906
        static_cast<LocalScaleOffsetData *>(pWorkingData);
907
    CPLAssert(data->m_osSignature == LocalScaleOffsetData::EXPECTED_SIGNATURE);
4✔
908
    CPL_IGNORE_RET_VAL(data->m_osSignature);
4✔
909
    delete data;
4✔
910
}
4✔
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,
17✔
918
                        size_t nElts, int nBufXSize, int nBufYSize,
919
                        const char *pszAuxType, GDALRasterBand *poAuxBand,
920
                        std::vector<VRTProcessedDataset::NoInitByte> &abyBuffer)
921
{
922
    GDALGeoTransform auxGT, auxInvGT;
17✔
923

924
    // Compute pixel/line coordinates from the georeferenced extent
925
    CPL_IGNORE_RET_VAL(poAuxBand->GetDataset()->GetGeoTransform(
34✔
926
        auxGT));  // return code already tested
17✔
927
    CPL_IGNORE_RET_VAL(auxGT.GetInverse(auxInvGT));
17✔
928
    const double dfULPixel =
929
        auxInvGT[0] + auxInvGT[1] * dfULX + auxInvGT[2] * dfULY;
17✔
930
    const double dfULLine =
931
        auxInvGT[3] + auxInvGT[4] * dfULX + auxInvGT[5] * dfULY;
17✔
932
    const double dfLRPixel =
933
        auxInvGT[0] + auxInvGT[1] * dfLRX + auxInvGT[2] * dfLRY;
17✔
934
    const double dfLRLine =
935
        auxInvGT[3] + auxInvGT[4] * dfLRX + auxInvGT[5] * dfLRY;
17✔
936
    if (dfULPixel >= dfLRPixel || dfULLine >= dfLRLine)
17✔
937
    {
938
        CPLError(CE_Failure, CPLE_AppDefined,
×
939
                 "Unexpected computed %s pixel/line", pszAuxType);
940
        return false;
×
941
    }
942
    if (dfULPixel < -1 || dfULLine < -1)
17✔
943
    {
944
        CPLError(CE_Failure, CPLE_AppDefined,
×
945
                 "Unexpected computed %s upper left (pixel,line)=(%f,%f)",
946
                 pszAuxType, dfULPixel, dfULLine);
947
        return false;
×
948
    }
949
    if (dfLRPixel > poAuxBand->GetXSize() + 1 ||
34✔
950
        dfLRLine > poAuxBand->GetYSize() + 1)
17✔
951
    {
952
        CPLError(CE_Failure, CPLE_AppDefined,
×
953
                 "Unexpected computed %s lower right (pixel,line)=(%f,%f)",
954
                 pszAuxType, dfLRPixel, dfLRLine);
955
        return false;
×
956
    }
957

958
    const int nAuxXOff = std::clamp(static_cast<int>(std::round(dfULPixel)), 0,
34✔
959
                                    poAuxBand->GetXSize() - 1);
17✔
960
    const int nAuxYOff = std::clamp(static_cast<int>(std::round(dfULLine)), 0,
34✔
961
                                    poAuxBand->GetYSize() - 1);
17✔
962
    const int nAuxX2Off = std::min(poAuxBand->GetXSize(),
51✔
963
                                   static_cast<int>(std::round(dfLRPixel)));
17✔
964
    const int nAuxY2Off =
965
        std::min(poAuxBand->GetYSize(), static_cast<int>(std::round(dfLRLine)));
17✔
966

967
    try
968
    {
969
        abyBuffer.resize(nElts * sizeof(float));
17✔
970
    }
971
    catch (const std::bad_alloc &)
×
972
    {
973
        CPLError(CE_Failure, CPLE_OutOfMemory,
×
974
                 "Out of memory allocating working buffer");
975
        return false;
×
976
    }
977
    GDALRasterIOExtraArg sExtraArg;
978
    INIT_RASTERIO_EXTRA_ARG(sExtraArg);
17✔
979
    sExtraArg.bFloatingPointWindowValidity = true;
17✔
980
    CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg);
17✔
981
    sExtraArg.eResampleAlg = GRIORA_Bilinear;
17✔
982
    sExtraArg.dfXOff = std::max(0.0, dfULPixel);
17✔
983
    sExtraArg.dfYOff = std::max(0.0, dfULLine);
17✔
984
    sExtraArg.dfXSize = std::min<double>(poAuxBand->GetXSize(), dfLRPixel) -
17✔
985
                        std::max(0.0, dfULPixel);
17✔
986
    sExtraArg.dfYSize = std::min<double>(poAuxBand->GetYSize(), dfLRLine) -
17✔
987
                        std::max(0.0, dfULLine);
17✔
988
    return (poAuxBand->RasterIO(
17✔
989
                GF_Read, nAuxXOff, nAuxYOff, std::max(1, nAuxX2Off - nAuxXOff),
17✔
990
                std::max(1, nAuxY2Off - nAuxYOff), abyBuffer.data(), nBufXSize,
17✔
991
                nBufYSize, GDT_Float32, 0, 0, &sExtraArg) == CE_None);
17✔
992
}
993

994
/************************************************************************/
995
/*                      LocalScaleOffsetProcess()                       */
996
/************************************************************************/
997

998
/** Processing function for 'LocalScaleOffset' builtin function. */
999
static CPLErr LocalScaleOffsetProcess(
7✔
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
{
1010
    const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
7✔
1011

1012
    CPL_IGNORE_RET_VAL(eInDT);
7✔
1013
    CPLAssert(eInDT == GDT_Float64);
7✔
1014
    CPL_IGNORE_RET_VAL(eOutDT);
7✔
1015
    CPLAssert(eOutDT == GDT_Float64);
7✔
1016
    CPL_IGNORE_RET_VAL(nInBufferSize);
7✔
1017
    CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
7✔
1018
    CPL_IGNORE_RET_VAL(nOutBufferSize);
7✔
1019
    CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
7✔
1020
    CPLAssert(nInBands == nOutBands);
7✔
1021
    CPL_IGNORE_RET_VAL(nOutBands);
7✔
1022

1023
    LocalScaleOffsetData *data =
7✔
1024
        static_cast<LocalScaleOffsetData *>(pWorkingData);
1025
    CPLAssert(data->m_osSignature == LocalScaleOffsetData::EXPECTED_SIGNATURE);
7✔
1026
    const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
7✔
1027
    double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
7✔
1028

1029
    // Compute georeferenced extent of input region
1030
    const double dfULX =
7✔
1031
        adfSrcGT[0] + adfSrcGT[1] * dfSrcXOff + adfSrcGT[2] * dfSrcYOff;
7✔
1032
    const double dfULY =
7✔
1033
        adfSrcGT[3] + adfSrcGT[4] * dfSrcXOff + adfSrcGT[5] * dfSrcYOff;
7✔
1034
    const double dfLRX = adfSrcGT[0] + adfSrcGT[1] * (dfSrcXOff + dfSrcXSize) +
7✔
1035
                         adfSrcGT[2] * (dfSrcYOff + dfSrcYSize);
7✔
1036
    const double dfLRY = adfSrcGT[3] + adfSrcGT[4] * (dfSrcXOff + dfSrcXSize) +
7✔
1037
                         adfSrcGT[5] * (dfSrcYOff + dfSrcYSize);
7✔
1038

1039
    auto &abyOffsetBuffer = data->m_abyGainBuffer;
7✔
1040
    auto &abyGainBuffer = data->m_abyOffsetBuffer;
7✔
1041

1042
    for (int iBand = 0; iBand < nInBands; ++iBand)
15✔
1043
    {
1044
        if (!LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize,
8✔
1045
                         nBufYSize, "gain", data->m_oGainBands[iBand],
8✔
1046
                         abyGainBuffer) ||
16✔
1047
            !LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize,
8✔
1048
                         nBufYSize, "offset", data->m_oOffsetBands[iBand],
8✔
1049
                         abyOffsetBuffer))
1050
        {
1051
            return CE_Failure;
×
1052
        }
1053

1054
        const double *CPL_RESTRICT padfSrcThisBand = padfSrc + iBand;
8✔
1055
        double *CPL_RESTRICT padfDstThisBand = padfDst + iBand;
8✔
1056
        const float *pafGain =
1057
            reinterpret_cast<const float *>(abyGainBuffer.data());
8✔
1058
        const float *pafOffset =
1059
            reinterpret_cast<const float *>(abyOffsetBuffer.data());
8✔
1060
        const double dfSrcNodata = padfInNoData[iBand];
8✔
1061
        const double dfDstNodata = padfOutNoData[iBand];
8✔
1062
        const double dfGainNodata = data->m_dfGainNodata;
8✔
1063
        const double dfOffsetNodata = data->m_dfOffsetNodata;
8✔
1064
        const double dfClampMin = data->m_dfClampMin;
8✔
1065
        const double dfClampMax = data->m_dfClampMax;
8✔
1066
        for (size_t i = 0; i < nElts; ++i)
66,084✔
1067
        {
1068
            const double dfSrcVal = *padfSrcThisBand;
66,076✔
1069
            // written this way to work with a NaN value
1070
            if (!(dfSrcVal != dfSrcNodata))
66,076✔
1071
            {
1072
                *padfDstThisBand = dfDstNodata;
2✔
1073
            }
1074
            else
1075
            {
1076
                const double dfGain = pafGain[i];
66,074✔
1077
                const double dfOffset = pafOffset[i];
66,074✔
1078
                if (!(dfGain != dfGainNodata) || !(dfOffset != dfOffsetNodata))
66,074✔
1079
                {
1080
                    *padfDstThisBand = dfDstNodata;
4✔
1081
                }
1082
                else
1083
                {
1084
                    double dfUnscaled = dfSrcVal * dfGain - dfOffset;
66,070✔
1085
                    if (dfUnscaled < dfClampMin)
66,070✔
1086
                        dfUnscaled = dfClampMin;
2✔
1087
                    if (dfUnscaled > dfClampMax)
66,070✔
1088
                        dfUnscaled = dfClampMax;
1✔
1089

1090
                    *padfDstThisBand = dfUnscaled;
66,070✔
1091
                }
1092
            }
1093
            padfSrcThisBand += nInBands;
66,076✔
1094
            padfDstThisBand += nInBands;
66,076✔
1095
        }
1096
    }
1097

1098
    return CE_None;
7✔
1099
}
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 brighest 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*/,
12✔
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
{
1158
    CPLAssert(eInDT == GDT_Float64);
12✔
1159

1160
    const bool bIsFinalStep = *pnOutBands != 0;
12✔
1161
    *peOutDT = eInDT;
12✔
1162
    *ppWorkingData = nullptr;
12✔
1163

1164
    if (!bIsFinalStep)
12✔
1165
    {
1166
        *pnOutBands = nInBands;
×
1167
    }
1168

1169
    auto data = std::make_unique<TrimmingData>();
24✔
1170

1171
    bool bNodataSpecified = false;
12✔
1172
    double dfNoData = std::numeric_limits<double>::quiet_NaN();
12✔
1173
    std::string osTrimmingFilename;
24✔
1174
    bool bTrimmingNodataSpecified = false;
12✔
1175
    bool bRelativeToVRT = false;
12✔
1176

1177
    for (const auto &[pszKey, pszValue] :
84✔
1178
         cpl::IterateNameValue(papszFunctionArgs))
90✔
1179
    {
1180
        if (EQUAL(pszKey, "relativeToVRT"))
45✔
1181
        {
1182
            bRelativeToVRT = CPLTestBool(pszValue);
×
1183
        }
1184
        else if (EQUAL(pszKey, "nodata"))
45✔
1185
        {
1186
            bNodataSpecified = true;
×
1187
            dfNoData = CPLAtof(pszValue);
×
1188
        }
1189
        else if (EQUAL(pszKey, "trimming_nodata"))
45✔
1190
        {
1191
            bTrimmingNodataSpecified = true;
×
1192
            data->m_dfTrimmingNodata = CPLAtof(pszValue);
×
1193
        }
1194
        else if (EQUAL(pszKey, "trimming_dataset_filename"))
45✔
1195
        {
1196
            osTrimmingFilename = pszValue;
12✔
1197
        }
1198
        else if (EQUAL(pszKey, "red_band"))
33✔
1199
        {
1200
            const int nBand = atoi(pszValue) - 1;
5✔
1201
            if (nBand < 0 || nBand >= nInBands)
5✔
1202
            {
1203
                CPLError(CE_Failure, CPLE_AppDefined,
2✔
1204
                         "Invalid band in argument '%s'", pszKey);
1205
                return CE_Failure;
6✔
1206
            }
1207
            data->m_nRedBand = nBand;
3✔
1208
        }
1209
        else if (EQUAL(pszKey, "green_band"))
28✔
1210
        {
1211
            const int nBand = atoi(pszValue) - 1;
5✔
1212
            if (nBand < 0 || nBand >= nInBands)
5✔
1213
            {
1214
                CPLError(CE_Failure, CPLE_AppDefined,
2✔
1215
                         "Invalid band in argument '%s'", pszKey);
1216
                return CE_Failure;
2✔
1217
            }
1218
            data->m_nGreenBand = nBand;
3✔
1219
        }
1220
        else if (EQUAL(pszKey, "blue_band"))
23✔
1221
        {
1222
            const int nBand = atoi(pszValue) - 1;
5✔
1223
            if (nBand < 0 || nBand >= nInBands)
5✔
1224
            {
1225
                CPLError(CE_Failure, CPLE_AppDefined,
2✔
1226
                         "Invalid band in argument '%s'", pszKey);
1227
                return CE_Failure;
2✔
1228
            }
1229
            data->m_nBlueBand = nBand;
3✔
1230
        }
1231
        else if (EQUAL(pszKey, "top_rgb"))
18✔
1232
        {
1233
            data->m_dfTopRGB = CPLAtof(pszValue);
6✔
1234
        }
1235
        else if (EQUAL(pszKey, "tone_ceil"))
12✔
1236
        {
1237
            data->m_dfToneCeil = CPLAtof(pszValue);
6✔
1238
        }
1239
        else if (EQUAL(pszKey, "top_margin"))
6✔
1240
        {
1241
            data->m_dfTopMargin = CPLAtof(pszValue);
6✔
1242
        }
1243
        else
1244
        {
1245
            CPLError(CE_Warning, CPLE_AppDefined,
×
1246
                     "Unrecognized argument name %s. Ignored", pszKey);
1247
        }
1248
    }
1249

1250
    if (data->m_nRedBand == data->m_nGreenBand ||
6✔
1251
        data->m_nRedBand == data->m_nBlueBand ||
10✔
1252
        data->m_nGreenBand == data->m_nBlueBand)
4✔
1253
    {
1254
        CPLError(
3✔
1255
            CE_Failure, CPLE_NotSupported,
1256
            "red_band, green_band and blue_band must have distinct values");
1257
        return CE_Failure;
3✔
1258
    }
1259

1260
    const auto osFilename = GDALDataset::BuildFilename(
1261
        osTrimmingFilename.c_str(), pszVRTPath, bRelativeToVRT);
6✔
1262
    data->m_poTrimmingDS.reset(GDALDataset::Open(
3✔
1263
        osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, nullptr,
1264
        nullptr, nullptr));
1265
    if (!data->m_poTrimmingDS)
3✔
1266
        return CE_Failure;
1✔
1267
    if (data->m_poTrimmingDS->GetRasterCount() != 1)
2✔
1268
    {
1269
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
1270
                 "Trimming dataset should have a single band");
1271
        return CE_Failure;
1✔
1272
    }
1273
    data->m_poTrimmingBand = data->m_poTrimmingDS->GetRasterBand(1);
1✔
1274

1275
    GDALGeoTransform auxGT;
1✔
1276
    if (data->m_poTrimmingDS->GetGeoTransform(auxGT) != CE_None)
1✔
1277
    {
1278
        CPLError(CE_Failure, CPLE_AppDefined, "%s lacks a geotransform",
×
1279
                 osFilename.c_str());
1280
        return CE_Failure;
×
1281
    }
1282
    int bAuxBandHasNoData = false;
1✔
1283
    const double dfAuxNoData =
1284
        data->m_poTrimmingBand->GetNoDataValue(&bAuxBandHasNoData);
1✔
1285
    if (!bTrimmingNodataSpecified && bAuxBandHasNoData)
1✔
1286
        data->m_dfTrimmingNodata = dfAuxNoData;
×
1287

1288
    SetOutputValuesForInNoDataAndOutNoData(
1✔
1289
        nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bNodataSpecified,
1290
        dfNoData, bNodataSpecified, dfNoData, bIsFinalStep);
1291

1292
    *ppWorkingData = data.release();
1✔
1293
    return CE_None;
1✔
1294
}
1295

1296
/************************************************************************/
1297
/*                           TrimmingFree()                             */
1298
/************************************************************************/
1299

1300
/** Free function for 'Trimming' builtin function. */
1301
static void TrimmingFree(const char * /*pszFuncName*/, void * /*pUserData*/,
1✔
1302
                         VRTPDWorkingDataPtr pWorkingData)
1303
{
1304
    TrimmingData *data = static_cast<TrimmingData *>(pWorkingData);
1✔
1305
    CPLAssert(data->m_osSignature == TrimmingData::EXPECTED_SIGNATURE);
1✔
1306
    CPL_IGNORE_RET_VAL(data->m_osSignature);
1✔
1307
    delete data;
1✔
1308
}
1✔
1309

1310
/************************************************************************/
1311
/*                         TrimmingProcess()                            */
1312
/************************************************************************/
1313

1314
/** Processing function for 'Trimming' builtin function. */
1315
static CPLErr TrimmingProcess(
1✔
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
{
1326
    const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
1✔
1327

1328
    CPL_IGNORE_RET_VAL(eInDT);
1✔
1329
    CPLAssert(eInDT == GDT_Float64);
1✔
1330
    CPL_IGNORE_RET_VAL(eOutDT);
1✔
1331
    CPLAssert(eOutDT == GDT_Float64);
1✔
1332
    CPL_IGNORE_RET_VAL(nInBufferSize);
1✔
1333
    CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double));
1✔
1334
    CPL_IGNORE_RET_VAL(nOutBufferSize);
1✔
1335
    CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double));
1✔
1336
    CPLAssert(nInBands == nOutBands);
1✔
1337
    CPL_IGNORE_RET_VAL(nOutBands);
1✔
1338

1339
    TrimmingData *data = static_cast<TrimmingData *>(pWorkingData);
1✔
1340
    CPLAssert(data->m_osSignature == TrimmingData::EXPECTED_SIGNATURE);
1✔
1341
    const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
1✔
1342
    double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
1✔
1343

1344
    // Compute georeferenced extent of input region
1345
    const double dfULX =
1✔
1346
        adfSrcGT[0] + adfSrcGT[1] * dfSrcXOff + adfSrcGT[2] * dfSrcYOff;
1✔
1347
    const double dfULY =
1✔
1348
        adfSrcGT[3] + adfSrcGT[4] * dfSrcXOff + adfSrcGT[5] * dfSrcYOff;
1✔
1349
    const double dfLRX = adfSrcGT[0] + adfSrcGT[1] * (dfSrcXOff + dfSrcXSize) +
1✔
1350
                         adfSrcGT[2] * (dfSrcYOff + dfSrcYSize);
1✔
1351
    const double dfLRY = adfSrcGT[3] + adfSrcGT[4] * (dfSrcXOff + dfSrcXSize) +
1✔
1352
                         adfSrcGT[5] * (dfSrcYOff + dfSrcYSize);
1✔
1353

1354
    if (!LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize, nBufYSize,
1✔
1355
                     "trimming", data->m_poTrimmingBand,
1356
                     data->m_abyTrimmingBuffer))
1✔
1357
    {
1358
        return CE_Failure;
×
1359
    }
1360

1361
    const float *pafTrimming =
1362
        reinterpret_cast<const float *>(data->m_abyTrimmingBuffer.data());
1✔
1363
    const int nRedBand = data->m_nRedBand;
1✔
1364
    const int nGreenBand = data->m_nGreenBand;
1✔
1365
    const int nBlueBand = data->m_nBlueBand;
1✔
1366
    const double dfTopMargin = data->m_dfTopMargin;
1✔
1367
    const double dfTopRGB = data->m_dfTopRGB;
1✔
1368
    const double dfToneCeil = data->m_dfToneCeil;
1✔
1369
#if !defined(trimming_non_optimized_version)
1370
    const double dfInvToneCeil = 1.0 / dfToneCeil;
1✔
1371
#endif
1372
    const bool bRGBBandsAreFirst =
1373
        std::max(std::max(nRedBand, nGreenBand), nBlueBand) <= 2;
1✔
1374
    const double dfNoDataTrimming = data->m_dfTrimmingNodata;
1✔
1375
    const double dfNoDataRed = padfInNoData[nRedBand];
1✔
1376
    const double dfNoDataGreen = padfInNoData[nGreenBand];
1✔
1377
    const double dfNoDataBlue = padfInNoData[nBlueBand];
1✔
1378
    for (size_t i = 0; i < nElts; ++i)
7✔
1379
    {
1380
        // Extract local saturation value from trimming image
1381
        const double dfLocalMaxRGB = pafTrimming[i];
6✔
1382
        const double dfReducedRGB =
1383
            std::min((1.0 - dfTopMargin) * dfTopRGB / dfLocalMaxRGB, 1.0);
6✔
1384

1385
        const double dfRed = padfSrc[nRedBand];
6✔
1386
        const double dfGreen = padfSrc[nGreenBand];
6✔
1387
        const double dfBlue = padfSrc[nBlueBand];
6✔
1388
        bool bNoDataPixel = false;
6✔
1389
        if ((dfLocalMaxRGB != dfNoDataTrimming) && (dfRed != dfNoDataRed) &&
6✔
1390
            (dfGreen != dfNoDataGreen) && (dfBlue != dfNoDataBlue))
6✔
1391
        {
1392
            // RGB bands specific process
1393
            const double dfMaxRGB = std::max(std::max(dfRed, dfGreen), dfBlue);
6✔
1394
#if !defined(trimming_non_optimized_version)
1395
            const double dfRedTimesToneRed = std::min(dfRed, dfToneCeil);
6✔
1396
            const double dfGreenTimesToneGreen = std::min(dfGreen, dfToneCeil);
6✔
1397
            const double dfBlueTimesToneBlue = std::min(dfBlue, dfToneCeil);
6✔
1398
            const double dfInvToneMaxRGB =
1399
                std::max(dfMaxRGB * dfInvToneCeil, 1.0);
6✔
1400
            const double dfReducedRGBTimesInvToneMaxRGB =
6✔
1401
                dfReducedRGB * dfInvToneMaxRGB;
1402
            padfDst[nRedBand] = std::min(
6✔
1403
                dfRedTimesToneRed * dfReducedRGBTimesInvToneMaxRGB, dfTopRGB);
6✔
1404
            padfDst[nGreenBand] =
6✔
1405
                std::min(dfGreenTimesToneGreen * dfReducedRGBTimesInvToneMaxRGB,
12✔
1406
                         dfTopRGB);
6✔
1407
            padfDst[nBlueBand] = std::min(
6✔
1408
                dfBlueTimesToneBlue * dfReducedRGBTimesInvToneMaxRGB, dfTopRGB);
6✔
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
            if (bRGBBandsAreFirst)
6✔
1425
            {
1426
                // optimization
1427
                for (int iBand = 3; iBand < nInBands; ++iBand)
12✔
1428
                {
1429
                    if (padfSrc[iBand] != padfInNoData[iBand])
6✔
1430
                    {
1431
                        padfDst[iBand] = dfReducedRGB * padfSrc[iBand];
6✔
1432
                    }
1433
                    else
1434
                    {
1435
                        bNoDataPixel = true;
×
1436
                        break;
×
1437
                    }
1438
                }
1439
            }
1440
            else
1441
            {
1442
                for (int iBand = 0; iBand < nInBands; ++iBand)
×
1443
                {
1444
                    if (iBand != nRedBand && iBand != nGreenBand &&
×
1445
                        iBand != nBlueBand)
×
1446
                    {
1447
                        if (padfSrc[iBand] != padfInNoData[iBand])
×
1448
                        {
1449
                            padfDst[iBand] = dfReducedRGB * padfSrc[iBand];
×
1450
                        }
1451
                        else
1452
                        {
1453
                            bNoDataPixel = true;
×
1454
                            break;
×
1455
                        }
1456
                    }
1457
                }
1458
            }
6✔
1459
        }
1460
        else
1461
        {
1462
            bNoDataPixel = true;
×
1463
        }
1464
        if (bNoDataPixel)
6✔
1465
        {
1466
            for (int iBand = 0; iBand < nInBands; ++iBand)
×
1467
            {
1468
                padfDst[iBand] = padfOutNoData[iBand];
×
1469
            }
1470
        }
1471

1472
        padfSrc += nInBands;
6✔
1473
        padfDst += nInBands;
6✔
1474
    }
1475

1476
    return CE_None;
1✔
1477
}
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,
19✔
1490
                   std::string_view osDialect)
1491
        : m_nInBands(nInBands), m_nNominalBatchSize(nBatchSize),
19✔
1492
          m_nBatchCount(DIV_ROUND_UP(nInBands, nBatchSize)), m_adfResults{},
19✔
1493
          m_osExpression(std::string(osExpression)),
38✔
1494
          m_osDialect(std::string(osDialect)), m_oNominalBatchEnv{},
38✔
1495
          m_oPartialBatchEnv{}
114✔
1496
    {
1497
    }
19✔
1498

1499
    CPLErr Compile()
19✔
1500
    {
1501
        auto eErr = m_oNominalBatchEnv.Initialize(m_osExpression, m_osDialect,
38✔
1502
                                                  m_nNominalBatchSize);
19✔
1503
        if (eErr != CE_None)
19✔
1504
        {
1505
            return eErr;
2✔
1506
        }
1507

1508
        const auto nPartialBatchSize = m_nInBands % m_nNominalBatchSize;
17✔
1509
        if (nPartialBatchSize)
17✔
1510
        {
1511
            eErr = m_oPartialBatchEnv.Initialize(m_osExpression, m_osDialect,
1✔
1512
                                                 nPartialBatchSize);
1513
        }
1514

1515
        return eErr;
17✔
1516
    }
1517

1518
    CPLErr Evaluate(const double *padfInputs, size_t nExpectedOutBands)
30✔
1519
    {
1520
        m_adfResults.clear();
30✔
1521

1522
        for (int iBatch = 0; iBatch < m_nBatchCount; iBatch++)
89✔
1523
        {
1524
            const auto nBandsRemaining =
62✔
1525
                static_cast<int>(m_nInBands - (m_nNominalBatchSize * iBatch));
62✔
1526
            const auto nBatchSize =
1527
                std::min(m_nNominalBatchSize, nBandsRemaining);
62✔
1528

1529
            auto &oEnv = GetEnv(nBatchSize);
62✔
1530

1531
            const double *pdfStart = padfInputs + iBatch * m_nNominalBatchSize;
62✔
1532
            const double *pdfEnd = pdfStart + nBatchSize;
62✔
1533

1534
            std::copy(pdfStart, pdfEnd, oEnv.m_adfValuesForPixel.begin());
62✔
1535

1536
            if (auto eErr = oEnv.m_poExpression->Evaluate(); eErr != CE_None)
62✔
1537
            {
1538
                return eErr;
3✔
1539
            }
1540

1541
            const auto &adfResults = oEnv.m_poExpression->Results();
59✔
1542
            if (m_nBatchCount > 1)
59✔
1543
            {
1544
                std::copy(adfResults.begin(), adfResults.end(),
1545
                          std::back_inserter(m_adfResults));
38✔
1546
            }
1547
        }
1548

1549
        if (nExpectedOutBands > 0)
27✔
1550
        {
1551
            if (Results().size() != static_cast<std::size_t>(nExpectedOutBands))
23✔
1552
            {
1553
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1554
                         "Expression returned %d values but "
1555
                         "%d output bands were expected.",
1556
                         static_cast<int>(Results().size()),
1✔
1557
                         static_cast<int>(nExpectedOutBands));
1558
                return CE_Failure;
1✔
1559
            }
1560
        }
1561

1562
        return CE_None;
26✔
1563
    }
1564

1565
    const std::vector<double> &Results() const
50✔
1566
    {
1567
        if (m_nBatchCount == 1)
50✔
1568
        {
1569
            return m_oNominalBatchEnv.m_poExpression->Results();
41✔
1570
        }
1571
        else
1572
        {
1573
            return m_adfResults;
9✔
1574
        }
1575
    }
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,
20✔
1592
                          const CPLString &osDialect, int nBatchSize)
1593
        {
1594
            m_poExpression =
1595
                gdal::MathExpression::Create(osExpression, osDialect.c_str());
20✔
1596
            // cppcheck-suppress knownConditionTrueFalse
1597
            if (m_poExpression == nullptr)
20✔
1598
            {
1599
                return CE_Failure;
×
1600
            }
1601

1602
            m_adfValuesForPixel.resize(nBatchSize);
20✔
1603

1604
            for (int i = 0; i < nBatchSize; i++)
131✔
1605
            {
1606
                std::string osVar = "B" + std::to_string(i + 1);
222✔
1607
                m_poExpression->RegisterVariable(osVar,
222✔
1608
                                                 &m_adfValuesForPixel[i]);
111✔
1609
            }
1610

1611
            if (osExpression.ifind("BANDS") != std::string::npos)
20✔
1612
            {
1613
                m_poExpression->RegisterVector("BANDS", &m_adfValuesForPixel);
11✔
1614
            }
1615

1616
            return m_poExpression->Compile();
20✔
1617
        }
1618
    };
1619

1620
    InvocationEnv &GetEnv(int nBatchSize)
62✔
1621
    {
1622
        if (nBatchSize == m_nNominalBatchSize)
62✔
1623
        {
1624
            return m_oNominalBatchEnv;
60✔
1625
        }
1626
        else
1627
        {
1628
            return m_oPartialBatchEnv;
2✔
1629
        }
1630
    }
1631

1632
    InvocationEnv m_oNominalBatchEnv;
1633
    InvocationEnv m_oPartialBatchEnv;
1634
};
1635

1636
}  // namespace
1637

1638
static CPLErr ExpressionInit(const char * /*pszFuncName*/, void * /*pUserData*/,
19✔
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
{
1646
    CPLAssert(eInDT == GDT_Float64);
19✔
1647

1648
    *peOutDT = eInDT;
19✔
1649
    *ppWorkingData = nullptr;
19✔
1650

1651
    const char *pszBatchSize =
1652
        CSLFetchNameValue(papszFunctionArgs, "batch_size");
19✔
1653
    auto nBatchSize = nInBands;
19✔
1654

1655
    if (pszBatchSize != nullptr)
19✔
1656
    {
1657
        nBatchSize = std::min(nInBands, std::atoi(pszBatchSize));
4✔
1658
    }
1659

1660
    if (nBatchSize < 1)
19✔
1661
    {
1662
        CPLError(CE_Failure, CPLE_IllegalArg, "batch_size must be at least 1");
×
1663
        return CE_Failure;
×
1664
    }
1665

1666
    const char *pszDialect = CSLFetchNameValue(papszFunctionArgs, "dialect");
19✔
1667
    if (pszDialect == nullptr)
19✔
1668
    {
1669
        pszDialect = "muparser";
×
1670
    }
1671

1672
    const char *pszExpression =
1673
        CSLFetchNameValue(papszFunctionArgs, "expression");
19✔
1674

1675
    auto data = std::make_unique<ExpressionData>(nInBands, nBatchSize,
1676
                                                 pszExpression, pszDialect);
38✔
1677

1678
    if (auto eErr = data->Compile(); eErr != CE_None)
19✔
1679
    {
1680
        return eErr;
2✔
1681
    }
1682

1683
    if (*pnOutBands == 0)
17✔
1684
    {
1685
        std::vector<double> aDummyValues(nInBands);
4✔
1686
        if (auto eErr = data->Evaluate(aDummyValues.data(), 0); eErr != CE_None)
4✔
1687
        {
1688
            return eErr;
×
1689
        }
1690

1691
        *pnOutBands = static_cast<int>(data->Results().size());
4✔
1692
    }
1693

1694
    *ppWorkingData = data.release();
17✔
1695

1696
    return CE_None;
17✔
1697
}
1698

1699
static void ExpressionFree(const char * /* pszFuncName */,
17✔
1700
                           void * /* pUserData */,
1701
                           VRTPDWorkingDataPtr pWorkingData)
1702
{
1703
    ExpressionData *data = static_cast<ExpressionData *>(pWorkingData);
17✔
1704
    delete data;
17✔
1705
}
17✔
1706

1707
static CPLErr ExpressionProcess(
17✔
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
{
1719
    ExpressionData *expr = static_cast<ExpressionData *>(pWorkingData);
17✔
1720

1721
    const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize;
17✔
1722

1723
    CPL_IGNORE_RET_VAL(eInDT);
17✔
1724
    CPLAssert(eInDT == GDT_Float64);
17✔
1725
    const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer);
17✔
1726

1727
    CPLAssert(eOutDT == GDT_Float64);
17✔
1728
    CPL_IGNORE_RET_VAL(eOutDT);
17✔
1729
    double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer);
17✔
1730

1731
    for (size_t i = 0; i < nElts; i++)
39✔
1732
    {
1733
        if (auto eErr = expr->Evaluate(padfSrc, nOutBands); eErr != CE_None)
26✔
1734
        {
1735
            return eErr;
4✔
1736
        }
1737

1738
        const auto &adfResults = expr->Results();
22✔
1739
        std::copy(adfResults.begin(), adfResults.end(), padfDst);
22✔
1740

1741
        padfDst += nOutBands;
22✔
1742
        padfSrc += nInBands;
22✔
1743
    }
1744

1745
    return CE_None;
13✔
1746
}
1747

1748
/************************************************************************/
1749
/*              GDALVRTRegisterDefaultProcessedDatasetFuncs()           */
1750
/************************************************************************/
1751

1752
/** Register builtin functions that can be used in a VRTProcessedDataset.
1753
 */
1754
void GDALVRTRegisterDefaultProcessedDatasetFuncs()
1,429✔
1755
{
1756
    GDALVRTRegisterProcessedDatasetFunc(
1,429✔
1757
        "BandAffineCombination", nullptr,
1758
        "<ProcessedDatasetFunctionArgumentsList>"
1759
        "   <Argument name='src_nodata' type='double' "
1760
        "description='Override input nodata value'/>"
1761
        "   <Argument name='dst_nodata' type='double' "
1762
        "description='Override output nodata value'/>"
1763
        "   <Argument name='replacement_nodata' "
1764
        "description='value to substitute to a valid computed value that "
1765
        "would be nodata' type='double'/>"
1766
        "   <Argument name='dst_intended_datatype' type='string' "
1767
        "description='Intented datatype of output (which might be "
1768
        "different than the working data type)'/>"
1769
        "   <Argument name='coefficients_{band}' "
1770
        "description='Comma-separated coefficients for combining bands. "
1771
        "First one is constant term' "
1772
        "type='double_list' required='true'/>"
1773
        "   <Argument name='min' description='clamp min value' type='double'/>"
1774
        "   <Argument name='max' description='clamp max value' type='double'/>"
1775
        "</ProcessedDatasetFunctionArgumentsList>",
1776
        GDT_Float64, nullptr, 0, nullptr, 0, BandAffineCombinationInit,
1777
        BandAffineCombinationFree, BandAffineCombinationProcess, nullptr);
1778

1779
    GDALVRTRegisterProcessedDatasetFunc(
1,429✔
1780
        "LUT", nullptr,
1781
        "<ProcessedDatasetFunctionArgumentsList>"
1782
        "   <Argument name='src_nodata' type='double' "
1783
        "description='Override input nodata value'/>"
1784
        "   <Argument name='dst_nodata' type='double' "
1785
        "description='Override output nodata value'/>"
1786
        "   <Argument name='lut_{band}' "
1787
        "description='List of the form [src value 1]:[dest value 1],"
1788
        "[src value 2]:[dest value 2],...' "
1789
        "type='string' required='true'/>"
1790
        "</ProcessedDatasetFunctionArgumentsList>",
1791
        GDT_Float64, nullptr, 0, nullptr, 0, LUTInit, LUTFree, LUTProcess,
1792
        nullptr);
1793

1794
    GDALVRTRegisterProcessedDatasetFunc(
1,429✔
1795
        "LocalScaleOffset", nullptr,
1796
        "<ProcessedDatasetFunctionArgumentsList>"
1797
        "   <Argument name='relativeToVRT' "
1798
        "description='Whether gain and offset filenames are relative to "
1799
        "the VRT' type='boolean' default='false'/>"
1800
        "   <Argument name='gain_dataset_filename_{band}' "
1801
        "description='Filename to the gain dataset' "
1802
        "type='string' required='true'/>"
1803
        "   <Argument name='gain_dataset_band_{band}' "
1804
        "description='Band of the gain dataset' "
1805
        "type='integer' required='true'/>"
1806
        "   <Argument name='offset_dataset_filename_{band}' "
1807
        "description='Filename to the offset dataset' "
1808
        "type='string' required='true'/>"
1809
        "   <Argument name='offset_dataset_band_{band}' "
1810
        "description='Band of the offset dataset' "
1811
        "type='integer' required='true'/>"
1812
        "   <Argument name='min' description='clamp min value' type='double'/>"
1813
        "   <Argument name='max' description='clamp max value' type='double'/>"
1814
        "   <Argument name='nodata' type='double' "
1815
        "description='Override dataset nodata value'/>"
1816
        "   <Argument name='gain_nodata' type='double' "
1817
        "description='Override gain dataset nodata value'/>"
1818
        "   <Argument name='offset_nodata' type='double' "
1819
        "description='Override offset dataset nodata value'/>"
1820
        "</ProcessedDatasetFunctionArgumentsList>",
1821
        GDT_Float64, nullptr, 0, nullptr, 0, LocalScaleOffsetInit,
1822
        LocalScaleOffsetFree, LocalScaleOffsetProcess, nullptr);
1823

1824
    GDALVRTRegisterProcessedDatasetFunc(
1,429✔
1825
        "Trimming", nullptr,
1826
        "<ProcessedDatasetFunctionArgumentsList>"
1827
        "   <Argument name='relativeToVRT' "
1828
        "description='Whether trimming_dataset_filename is relative to the VRT'"
1829
        " type='boolean' default='false'/>"
1830
        "   <Argument name='trimming_dataset_filename' "
1831
        "description='Filename to the trimming dataset' "
1832
        "type='string' required='true'/>"
1833
        "   <Argument name='red_band' type='integer' default='1'/>"
1834
        "   <Argument name='green_band' type='integer' default='2'/>"
1835
        "   <Argument name='blue_band' type='integer' default='3'/>"
1836
        "   <Argument name='top_rgb' "
1837
        "description='Maximum saturating RGB output value' "
1838
        "type='double' required='true'/>"
1839
        "   <Argument name='tone_ceil' "
1840
        "description='Maximum threshold beyond which we give up saturation' "
1841
        "type='double' required='true'/>"
1842
        "   <Argument name='top_margin' "
1843
        "description='Margin to allow for dynamics in brighest areas "
1844
        "(between 0 and 1, should be close to 0)' "
1845
        "type='double' required='true'/>"
1846
        "   <Argument name='nodata' type='double' "
1847
        "description='Override dataset nodata value'/>"
1848
        "   <Argument name='trimming_nodata' type='double' "
1849
        "description='Override trimming dataset nodata value'/>"
1850
        "</ProcessedDatasetFunctionArgumentsList>",
1851
        GDT_Float64, nullptr, 0, nullptr, 0, TrimmingInit, TrimmingFree,
1852
        TrimmingProcess, nullptr);
1853

1854
    GDALVRTRegisterProcessedDatasetFunc(
1,429✔
1855
        "Expression", nullptr,
1856
        "<ProcessedDatasetFunctionArgumentsList>"
1857
        "    <Argument name='expression' description='the expression to "
1858
        "evaluate' type='string' required='true' />"
1859
        "    <Argument name='dialect' description='expression dialect' "
1860
        "type='string' />"
1861
        "    <Argument name='batch_size' description='batch size' "
1862
        "type='integer' />"
1863
        "</ProcessedDatasetFunctionArgumentsList>",
1864
        GDT_Float64, nullptr, 0, nullptr, 0, ExpressionInit, ExpressionFree,
1865
        ExpressionProcess, nullptr);
1866
}
1,429✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc