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