/src/gdal/alg/rasterfill.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: Interpolate in nodata areas. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2008, Frank Warmerdam |
9 | | * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com> |
10 | | * Copyright (c) 2015, Sean Gillies <sean@mapbox.com> |
11 | | * |
12 | | * SPDX-License-Identifier: MIT |
13 | | ***************************************************************************/ |
14 | | |
15 | | #include "cpl_port.h" |
16 | | #include "gdal_alg.h" |
17 | | |
18 | | #include <cmath> |
19 | | #include <cstring> |
20 | | |
21 | | #include <algorithm> |
22 | | #include <string> |
23 | | #include <utility> |
24 | | |
25 | | #include "cpl_conv.h" |
26 | | #include "cpl_error.h" |
27 | | #include "cpl_progress.h" |
28 | | #include "cpl_string.h" |
29 | | #include "cpl_vsi.h" |
30 | | #include "gdal.h" |
31 | | #include "gdal_priv.h" |
32 | | |
33 | | /************************************************************************/ |
34 | | /* GDALFilterLine() */ |
35 | | /* */ |
36 | | /* Apply 3x3 filtering one one scanline with masking for which */ |
37 | | /* pixels are to be interpolated (ThisFMask) and which window */ |
38 | | /* pixels are valid to include in the interpolation (TMask). */ |
39 | | /************************************************************************/ |
40 | | |
41 | | static void GDALFilterLine(const float *pafLastLine, const float *pafThisLine, |
42 | | const float *pafNextLine, float *pafOutLine, |
43 | | const GByte *pabyLastTMask, |
44 | | const GByte *pabyThisTMask, |
45 | | const GByte *pabyNextTMask, |
46 | | const GByte *pabyThisFMask, int nXSize) |
47 | | |
48 | 0 | { |
49 | 0 | for (int iX = 0; iX < nXSize; iX++) |
50 | 0 | { |
51 | 0 | if (!pabyThisFMask[iX]) |
52 | 0 | { |
53 | 0 | pafOutLine[iX] = pafThisLine[iX]; |
54 | 0 | continue; |
55 | 0 | } |
56 | | |
57 | 0 | CPLAssert(pabyThisTMask[iX]); |
58 | | |
59 | 0 | double dfValSum = 0.0; |
60 | 0 | double dfWeightSum = 0.0; |
61 | | |
62 | | // Previous line. |
63 | 0 | if (pafLastLine != nullptr) |
64 | 0 | { |
65 | 0 | if (iX > 0 && pabyLastTMask[iX - 1]) |
66 | 0 | { |
67 | 0 | dfValSum += pafLastLine[iX - 1]; |
68 | 0 | dfWeightSum += 1.0; |
69 | 0 | } |
70 | 0 | if (pabyLastTMask[iX]) |
71 | 0 | { |
72 | 0 | dfValSum += pafLastLine[iX]; |
73 | 0 | dfWeightSum += 1.0; |
74 | 0 | } |
75 | 0 | if (iX < nXSize - 1 && pabyLastTMask[iX + 1]) |
76 | 0 | { |
77 | 0 | dfValSum += pafLastLine[iX + 1]; |
78 | 0 | dfWeightSum += 1.0; |
79 | 0 | } |
80 | 0 | } |
81 | | |
82 | | // Current Line. |
83 | 0 | if (iX > 0 && pabyThisTMask[iX - 1]) |
84 | 0 | { |
85 | 0 | dfValSum += pafThisLine[iX - 1]; |
86 | 0 | dfWeightSum += 1.0; |
87 | 0 | } |
88 | 0 | if (pabyThisTMask[iX]) |
89 | 0 | { |
90 | 0 | dfValSum += pafThisLine[iX]; |
91 | 0 | dfWeightSum += 1.0; |
92 | 0 | } |
93 | 0 | if (iX < nXSize - 1 && pabyThisTMask[iX + 1]) |
94 | 0 | { |
95 | 0 | dfValSum += pafThisLine[iX + 1]; |
96 | 0 | dfWeightSum += 1.0; |
97 | 0 | } |
98 | | |
99 | | // Next line. |
100 | 0 | if (pafNextLine != nullptr) |
101 | 0 | { |
102 | 0 | if (iX > 0 && pabyNextTMask[iX - 1]) |
103 | 0 | { |
104 | 0 | dfValSum += pafNextLine[iX - 1]; |
105 | 0 | dfWeightSum += 1.0; |
106 | 0 | } |
107 | 0 | if (pabyNextTMask[iX]) |
108 | 0 | { |
109 | 0 | dfValSum += pafNextLine[iX]; |
110 | 0 | dfWeightSum += 1.0; |
111 | 0 | } |
112 | 0 | if (iX < nXSize - 1 && pabyNextTMask[iX + 1]) |
113 | 0 | { |
114 | 0 | dfValSum += pafNextLine[iX + 1]; |
115 | 0 | dfWeightSum += 1.0; |
116 | 0 | } |
117 | 0 | } |
118 | |
|
119 | 0 | pafOutLine[iX] = static_cast<float>(dfValSum / dfWeightSum); |
120 | 0 | } |
121 | 0 | } |
122 | | |
123 | | /************************************************************************/ |
124 | | /* GDALMultiFilter() */ |
125 | | /* */ |
126 | | /* Apply multiple iterations of a 3x3 smoothing filter over a */ |
127 | | /* band with masking controlling what pixels should be */ |
128 | | /* filtered (FiltMaskBand non zero) and which pixels can be */ |
129 | | /* considered valid contributors to the filter */ |
130 | | /* (TargetMaskBand non zero). */ |
131 | | /* */ |
132 | | /* This implementation attempts to apply many iterations in */ |
133 | | /* one IO pass by managing the filtering over a rolling buffer */ |
134 | | /* of nIterations+2 scanlines. While possibly clever this */ |
135 | | /* makes the algorithm implementation largely */ |
136 | | /* incomprehensible. */ |
137 | | /************************************************************************/ |
138 | | |
139 | | static CPLErr GDALMultiFilter(GDALRasterBandH hTargetBand, |
140 | | GDALRasterBandH hTargetMaskBand, |
141 | | GDALRasterBandH hFiltMaskBand, int nIterations, |
142 | | GDALProgressFunc pfnProgress, void *pProgressArg) |
143 | | |
144 | 0 | { |
145 | 0 | const int nXSize = GDALGetRasterBandXSize(hTargetBand); |
146 | 0 | const int nYSize = GDALGetRasterBandYSize(hTargetBand); |
147 | | |
148 | | /* -------------------------------------------------------------------- */ |
149 | | /* Report starting progress value. */ |
150 | | /* -------------------------------------------------------------------- */ |
151 | 0 | if (!pfnProgress(0.0, "Smoothing Filter...", pProgressArg)) |
152 | 0 | { |
153 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
154 | 0 | return CE_Failure; |
155 | 0 | } |
156 | | |
157 | | /* -------------------------------------------------------------------- */ |
158 | | /* Allocate rotating buffers. */ |
159 | | /* -------------------------------------------------------------------- */ |
160 | 0 | const int nBufLines = nIterations + 2; |
161 | |
|
162 | 0 | GByte *pabyTMaskBuf = |
163 | 0 | static_cast<GByte *>(VSI_MALLOC2_VERBOSE(nXSize, nBufLines)); |
164 | 0 | GByte *pabyFMaskBuf = |
165 | 0 | static_cast<GByte *>(VSI_MALLOC2_VERBOSE(nXSize, nBufLines)); |
166 | |
|
167 | 0 | float *paf3PassLineBuf = static_cast<float *>( |
168 | 0 | VSI_MALLOC3_VERBOSE(nXSize, nBufLines, 3 * sizeof(float))); |
169 | 0 | if (pabyTMaskBuf == nullptr || pabyFMaskBuf == nullptr || |
170 | 0 | paf3PassLineBuf == nullptr) |
171 | 0 | { |
172 | 0 | CPLFree(pabyTMaskBuf); |
173 | 0 | CPLFree(pabyFMaskBuf); |
174 | 0 | CPLFree(paf3PassLineBuf); |
175 | |
|
176 | 0 | return CE_Failure; |
177 | 0 | } |
178 | | |
179 | | /* -------------------------------------------------------------------- */ |
180 | | /* Process rotating buffers. */ |
181 | | /* -------------------------------------------------------------------- */ |
182 | | |
183 | 0 | CPLErr eErr = CE_None; |
184 | |
|
185 | 0 | int iPassCounter = 0; |
186 | |
|
187 | 0 | for (int nNewLine = 0; // Line being loaded (zero based scanline). |
188 | 0 | eErr == CE_None && nNewLine < nYSize + nIterations; nNewLine++) |
189 | 0 | { |
190 | | /* -------------------------------------------------------------------- |
191 | | */ |
192 | | /* Rotate pass buffers. */ |
193 | | /* -------------------------------------------------------------------- |
194 | | */ |
195 | 0 | iPassCounter = (iPassCounter + 1) % 3; |
196 | |
|
197 | 0 | float *const pafSLastPass = |
198 | 0 | paf3PassLineBuf + ((iPassCounter + 0) % 3) * nXSize * nBufLines; |
199 | 0 | float *const pafLastPass = |
200 | 0 | paf3PassLineBuf + ((iPassCounter + 1) % 3) * nXSize * nBufLines; |
201 | 0 | float *const pafThisPass = |
202 | 0 | paf3PassLineBuf + ((iPassCounter + 2) % 3) * nXSize * nBufLines; |
203 | | |
204 | | /* -------------------------------------------------------------------- |
205 | | */ |
206 | | /* Where does the new line go in the rotating buffer? */ |
207 | | /* -------------------------------------------------------------------- |
208 | | */ |
209 | 0 | const int iBufOffset = nNewLine % nBufLines; |
210 | | |
211 | | /* -------------------------------------------------------------------- |
212 | | */ |
213 | | /* Read the new data line if it is't off the bottom of the */ |
214 | | /* image. */ |
215 | | /* -------------------------------------------------------------------- |
216 | | */ |
217 | 0 | if (nNewLine < nYSize) |
218 | 0 | { |
219 | 0 | eErr = GDALRasterIO(hTargetMaskBand, GF_Read, 0, nNewLine, nXSize, |
220 | 0 | 1, pabyTMaskBuf + nXSize * iBufOffset, nXSize, |
221 | 0 | 1, GDT_Byte, 0, 0); |
222 | |
|
223 | 0 | if (eErr != CE_None) |
224 | 0 | break; |
225 | | |
226 | 0 | eErr = GDALRasterIO(hFiltMaskBand, GF_Read, 0, nNewLine, nXSize, 1, |
227 | 0 | pabyFMaskBuf + nXSize * iBufOffset, nXSize, 1, |
228 | 0 | GDT_Byte, 0, 0); |
229 | |
|
230 | 0 | if (eErr != CE_None) |
231 | 0 | break; |
232 | | |
233 | 0 | eErr = GDALRasterIO(hTargetBand, GF_Read, 0, nNewLine, nXSize, 1, |
234 | 0 | pafThisPass + nXSize * iBufOffset, nXSize, 1, |
235 | 0 | GDT_Float32, 0, 0); |
236 | |
|
237 | 0 | if (eErr != CE_None) |
238 | 0 | break; |
239 | 0 | } |
240 | | |
241 | | /* -------------------------------------------------------------------- |
242 | | */ |
243 | | /* Loop over the loaded data, applying the filter to all loaded */ |
244 | | /* lines with neighbours. */ |
245 | | /* -------------------------------------------------------------------- |
246 | | */ |
247 | 0 | for (int iFLine = nNewLine - 1; |
248 | 0 | eErr == CE_None && iFLine >= nNewLine - nIterations; iFLine--) |
249 | 0 | { |
250 | 0 | const int iLastOffset = (iFLine - 1) % nBufLines; |
251 | 0 | const int iThisOffset = (iFLine) % nBufLines; |
252 | 0 | const int iNextOffset = (iFLine + 1) % nBufLines; |
253 | | |
254 | | // Default to preserving the old value. |
255 | 0 | if (iFLine >= 0) |
256 | 0 | memcpy(pafThisPass + iThisOffset * nXSize, |
257 | 0 | pafLastPass + iThisOffset * nXSize, |
258 | 0 | sizeof(float) * nXSize); |
259 | | |
260 | | // TODO: Enable first and last line. |
261 | | // Skip the first and last line. |
262 | 0 | if (iFLine < 1 || iFLine >= nYSize - 1) |
263 | 0 | { |
264 | 0 | continue; |
265 | 0 | } |
266 | | |
267 | 0 | GDALFilterLine(pafSLastPass + iLastOffset * nXSize, |
268 | 0 | pafLastPass + iThisOffset * nXSize, |
269 | 0 | pafThisPass + iNextOffset * nXSize, |
270 | 0 | pafThisPass + iThisOffset * nXSize, |
271 | 0 | pabyTMaskBuf + iLastOffset * nXSize, |
272 | 0 | pabyTMaskBuf + iThisOffset * nXSize, |
273 | 0 | pabyTMaskBuf + iNextOffset * nXSize, |
274 | 0 | pabyFMaskBuf + iThisOffset * nXSize, nXSize); |
275 | 0 | } |
276 | | |
277 | | /* -------------------------------------------------------------------- |
278 | | */ |
279 | | /* Write out the top data line that will be rolling out of our */ |
280 | | /* buffer. */ |
281 | | /* -------------------------------------------------------------------- |
282 | | */ |
283 | 0 | const int iLineToSave = nNewLine - nIterations; |
284 | |
|
285 | 0 | if (iLineToSave >= 0 && eErr == CE_None) |
286 | 0 | { |
287 | 0 | const int iBufOffset2 = iLineToSave % nBufLines; |
288 | |
|
289 | 0 | eErr = GDALRasterIO(hTargetBand, GF_Write, 0, iLineToSave, nXSize, |
290 | 0 | 1, pafThisPass + nXSize * iBufOffset2, nXSize, |
291 | 0 | 1, GDT_Float32, 0, 0); |
292 | 0 | } |
293 | | |
294 | | /* -------------------------------------------------------------------- |
295 | | */ |
296 | | /* Report progress. */ |
297 | | /* -------------------------------------------------------------------- |
298 | | */ |
299 | 0 | if (eErr == CE_None && |
300 | 0 | !pfnProgress((nNewLine + 1) / |
301 | 0 | static_cast<double>(nYSize + nIterations), |
302 | 0 | "Smoothing Filter...", pProgressArg)) |
303 | 0 | { |
304 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
305 | 0 | eErr = CE_Failure; |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | /* -------------------------------------------------------------------- */ |
310 | | /* Cleanup */ |
311 | | /* -------------------------------------------------------------------- */ |
312 | 0 | CPLFree(pabyTMaskBuf); |
313 | 0 | CPLFree(pabyFMaskBuf); |
314 | 0 | CPLFree(paf3PassLineBuf); |
315 | |
|
316 | 0 | return eErr; |
317 | 0 | } |
318 | | |
319 | | /************************************************************************/ |
320 | | /* QUAD_CHECK() */ |
321 | | /* */ |
322 | | /* macro for checking whether a point is nearer than the */ |
323 | | /* existing closest point. */ |
324 | | /************************************************************************/ |
325 | | |
326 | | inline void QUAD_CHECK(double &dfQuadDist, float &fQuadValue, int target_x, |
327 | | GUInt32 target_y, int origin_x, int origin_y, |
328 | | float fTargetValue, GUInt32 nNoDataVal) |
329 | 0 | { |
330 | 0 | if (target_y != nNoDataVal) |
331 | 0 | { |
332 | 0 | const double dfDx = |
333 | 0 | static_cast<double>(target_x) - static_cast<double>(origin_x); |
334 | 0 | const double dfDy = |
335 | 0 | static_cast<double>(target_y) - static_cast<double>(origin_y); |
336 | 0 | double dfDistSq = dfDx * dfDx + dfDy * dfDy; |
337 | |
|
338 | 0 | if (dfDistSq < dfQuadDist * dfQuadDist) |
339 | 0 | { |
340 | 0 | CPLAssert(dfDistSq > 0.0); |
341 | 0 | dfQuadDist = sqrt(dfDistSq); |
342 | 0 | fQuadValue = fTargetValue; |
343 | 0 | } |
344 | 0 | } |
345 | 0 | } |
346 | | |
347 | | /************************************************************************/ |
348 | | /* GDALFillNodata() */ |
349 | | /************************************************************************/ |
350 | | |
351 | | /** |
352 | | * Fill selected raster regions by interpolation from the edges. |
353 | | * |
354 | | * This algorithm will interpolate values for all designated |
355 | | * nodata pixels (marked by zeros in hMaskBand). For each pixel |
356 | | * a four direction conic search is done to find values to interpolate |
357 | | * from (using inverse distance weighting by default). Once all values are |
358 | | * interpolated, zero or more smoothing iterations (3x3 average |
359 | | * filters on interpolated pixels) are applied to smooth out |
360 | | * artifacts. |
361 | | * |
362 | | * This algorithm is generally suitable for interpolating missing |
363 | | * regions of fairly continuously varying rasters (such as elevation |
364 | | * models for instance). It is also suitable for filling small holes |
365 | | * and cracks in more irregularly varying images (like airphotos). It |
366 | | * is generally not so great for interpolating a raster from sparse |
367 | | * point data - see the algorithms defined in gdal_grid.h for that case. |
368 | | * |
369 | | * @param hTargetBand the raster band to be modified in place. |
370 | | * @param hMaskBand a mask band indicating pixels to be interpolated |
371 | | * (zero valued). If hMaskBand is set to NULL, this method will internally use |
372 | | * the mask band returned by GDALGetMaskBand(hTargetBand). |
373 | | * @param dfMaxSearchDist the maximum number of pixels to search in all |
374 | | * directions to find values to interpolate from. |
375 | | * @param bDeprecatedOption unused argument, should be zero. |
376 | | * @param nSmoothingIterations the number of 3x3 smoothing filter passes to |
377 | | * run (0 or more). |
378 | | * @param papszOptions additional name=value options in a string list. |
379 | | * <ul> |
380 | | * <li>TEMP_FILE_DRIVER=gdal_driver_name. For example MEM.</li> |
381 | | * <li>NODATA=value (starting with GDAL 2.4). |
382 | | * Source pixels at that value will be ignored by the interpolator. Warning: |
383 | | * currently this will not be honored by smoothing passes.</li> |
384 | | * <li>INTERPOLATION=INV_DIST/NEAREST (GDAL >= 3.9). By default, pixels are |
385 | | * interpolated using an inverse distance weighting (INV_DIST). It is also |
386 | | * possible to choose a nearest neighbour (NEAREST) strategy.</li> |
387 | | * </ul> |
388 | | * @param pfnProgress the progress function to report completion. |
389 | | * @param pProgressArg callback data for progress function. |
390 | | * |
391 | | * @return CE_None on success or CE_Failure if something goes wrong. |
392 | | */ |
393 | | |
394 | | CPLErr CPL_STDCALL GDALFillNodata(GDALRasterBandH hTargetBand, |
395 | | GDALRasterBandH hMaskBand, |
396 | | double dfMaxSearchDist, |
397 | | CPL_UNUSED int bDeprecatedOption, |
398 | | int nSmoothingIterations, char **papszOptions, |
399 | | GDALProgressFunc pfnProgress, |
400 | | void *pProgressArg) |
401 | | |
402 | 0 | { |
403 | 0 | VALIDATE_POINTER1(hTargetBand, "GDALFillNodata", CE_Failure); |
404 | | |
405 | 0 | const int nXSize = GDALGetRasterBandXSize(hTargetBand); |
406 | 0 | const int nYSize = GDALGetRasterBandYSize(hTargetBand); |
407 | |
|
408 | 0 | if (dfMaxSearchDist == 0.0) |
409 | 0 | dfMaxSearchDist = std::max(nXSize, nYSize) + 1; |
410 | |
|
411 | 0 | const int nMaxSearchDist = static_cast<int>(floor(dfMaxSearchDist)); |
412 | |
|
413 | 0 | const char *pszInterpolation = |
414 | 0 | CSLFetchNameValueDef(papszOptions, "INTERPOLATION", "INV_DIST"); |
415 | 0 | const bool bNearest = EQUAL(pszInterpolation, "NEAREST"); |
416 | 0 | if (!EQUAL(pszInterpolation, "INV_DIST") && |
417 | 0 | !EQUAL(pszInterpolation, "NEAREST")) |
418 | 0 | { |
419 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
420 | 0 | "Unsupported interpolation method: %s", pszInterpolation); |
421 | 0 | return CE_Failure; |
422 | 0 | } |
423 | | |
424 | | // Special "x" pixel values identifying pixels as special. |
425 | 0 | GDALDataType eType = GDT_UInt16; |
426 | 0 | GUInt32 nNoDataVal = 65535; |
427 | |
|
428 | 0 | if (nXSize > 65533 || nYSize > 65533) |
429 | 0 | { |
430 | 0 | eType = GDT_UInt32; |
431 | 0 | nNoDataVal = 4000002; |
432 | 0 | } |
433 | | |
434 | | /* -------------------------------------------------------------------- */ |
435 | | /* Determine format driver for temp work files. */ |
436 | | /* -------------------------------------------------------------------- */ |
437 | 0 | CPLString osTmpFileDriver = |
438 | 0 | CSLFetchNameValueDef(papszOptions, "TEMP_FILE_DRIVER", "GTiff"); |
439 | 0 | GDALDriverH hDriver = GDALGetDriverByName(osTmpFileDriver.c_str()); |
440 | |
|
441 | 0 | if (hDriver == nullptr) |
442 | 0 | { |
443 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
444 | 0 | "TEMP_FILE_DRIVER=%s driver is not registered", |
445 | 0 | osTmpFileDriver.c_str()); |
446 | 0 | return CE_Failure; |
447 | 0 | } |
448 | | |
449 | 0 | if (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) == nullptr) |
450 | 0 | { |
451 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
452 | 0 | "TEMP_FILE_DRIVER=%s driver is incapable of creating " |
453 | 0 | "temp work files", |
454 | 0 | osTmpFileDriver.c_str()); |
455 | 0 | return CE_Failure; |
456 | 0 | } |
457 | | |
458 | 0 | CPLStringList aosWorkFileOptions; |
459 | 0 | if (osTmpFileDriver == "GTiff") |
460 | 0 | { |
461 | 0 | aosWorkFileOptions.SetNameValue("COMPRESS", "LZW"); |
462 | 0 | aosWorkFileOptions.SetNameValue("BIGTIFF", "IF_SAFER"); |
463 | 0 | } |
464 | |
|
465 | 0 | const CPLString osTmpFile = CPLGenerateTempFilenameSafe(""); |
466 | |
|
467 | 0 | std::unique_ptr<GDALDataset> poTmpMaskDS; |
468 | 0 | if (hMaskBand == nullptr) |
469 | 0 | { |
470 | 0 | hMaskBand = GDALGetMaskBand(hTargetBand); |
471 | 0 | } |
472 | 0 | else if (nSmoothingIterations > 0 && |
473 | 0 | hMaskBand != GDALGetMaskBand(hTargetBand)) |
474 | 0 | { |
475 | | // If doing smoothing operations and the user provided its own |
476 | | // mask band, we must make a copy of it to be able to update it |
477 | | // when we fill pixels during the initial pass. |
478 | 0 | const CPLString osMaskTmpFile = osTmpFile + "fill_mask_work.tif"; |
479 | 0 | poTmpMaskDS.reset(GDALDataset::FromHandle( |
480 | 0 | GDALCreate(hDriver, osMaskTmpFile, nXSize, nYSize, 1, GDT_Byte, |
481 | 0 | aosWorkFileOptions.List()))); |
482 | 0 | if (poTmpMaskDS == nullptr) |
483 | 0 | { |
484 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
485 | 0 | "Could not create poTmpMaskDS work file. Check driver " |
486 | 0 | "capabilities."); |
487 | 0 | return CE_Failure; |
488 | 0 | } |
489 | 0 | poTmpMaskDS->MarkSuppressOnClose(); |
490 | 0 | auto hTmpMaskBand = |
491 | 0 | GDALRasterBand::ToHandle(poTmpMaskDS->GetRasterBand(1)); |
492 | 0 | if (GDALRasterBandCopyWholeRaster(hMaskBand, hTmpMaskBand, nullptr, |
493 | 0 | nullptr, nullptr) != CE_None) |
494 | 0 | { |
495 | 0 | return CE_Failure; |
496 | 0 | } |
497 | 0 | hMaskBand = hTmpMaskBand; |
498 | 0 | } |
499 | | |
500 | | // If there are smoothing iterations, reserve 10% of the progress for them. |
501 | 0 | const double dfProgressRatio = nSmoothingIterations > 0 ? 0.9 : 1.0; |
502 | |
|
503 | 0 | const char *pszNoData = CSLFetchNameValue(papszOptions, "NODATA"); |
504 | 0 | bool bHasNoData = false; |
505 | 0 | float fNoData = 0.0f; |
506 | 0 | if (pszNoData) |
507 | 0 | { |
508 | 0 | bHasNoData = true; |
509 | 0 | fNoData = static_cast<float>(CPLAtof(pszNoData)); |
510 | 0 | } |
511 | | |
512 | | /* -------------------------------------------------------------------- */ |
513 | | /* Initialize progress counter. */ |
514 | | /* -------------------------------------------------------------------- */ |
515 | 0 | if (pfnProgress == nullptr) |
516 | 0 | pfnProgress = GDALDummyProgress; |
517 | |
|
518 | 0 | if (!pfnProgress(0.0, "Filling...", pProgressArg)) |
519 | 0 | { |
520 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
521 | 0 | return CE_Failure; |
522 | 0 | } |
523 | | |
524 | | /* -------------------------------------------------------------------- */ |
525 | | /* Create a work file to hold the Y "last value" indices. */ |
526 | | /* -------------------------------------------------------------------- */ |
527 | 0 | const CPLString osYTmpFile = osTmpFile + "fill_y_work.tif"; |
528 | |
|
529 | 0 | auto poYDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle( |
530 | 0 | GDALCreate(hDriver, osYTmpFile, nXSize, nYSize, 1, eType, |
531 | 0 | aosWorkFileOptions.List()))); |
532 | |
|
533 | 0 | if (poYDS == nullptr) |
534 | 0 | { |
535 | 0 | CPLError( |
536 | 0 | CE_Failure, CPLE_AppDefined, |
537 | 0 | "Could not create Y index work file. Check driver capabilities."); |
538 | 0 | return CE_Failure; |
539 | 0 | } |
540 | 0 | poYDS->MarkSuppressOnClose(); |
541 | |
|
542 | 0 | GDALRasterBandH hYBand = |
543 | 0 | GDALRasterBand::FromHandle(poYDS->GetRasterBand(1)); |
544 | | |
545 | | /* -------------------------------------------------------------------- */ |
546 | | /* Create a work file to hold the pixel value associated with */ |
547 | | /* the "last xy value" pixel. */ |
548 | | /* -------------------------------------------------------------------- */ |
549 | 0 | const CPLString osValTmpFile = osTmpFile + "fill_val_work.tif"; |
550 | |
|
551 | 0 | auto poValDS = |
552 | 0 | std::unique_ptr<GDALDataset>(GDALDataset::FromHandle(GDALCreate( |
553 | 0 | hDriver, osValTmpFile, nXSize, nYSize, 1, |
554 | 0 | GDALGetRasterDataType(hTargetBand), aosWorkFileOptions.List()))); |
555 | |
|
556 | 0 | if (poValDS == nullptr) |
557 | 0 | { |
558 | 0 | CPLError( |
559 | 0 | CE_Failure, CPLE_AppDefined, |
560 | 0 | "Could not create XY value work file. Check driver capabilities."); |
561 | 0 | return CE_Failure; |
562 | 0 | } |
563 | 0 | poValDS->MarkSuppressOnClose(); |
564 | |
|
565 | 0 | GDALRasterBandH hValBand = |
566 | 0 | GDALRasterBand::FromHandle(poValDS->GetRasterBand(1)); |
567 | | |
568 | | /* -------------------------------------------------------------------- */ |
569 | | /* Create a mask file to make it clear what pixels can be filtered */ |
570 | | /* on the filtering pass. */ |
571 | | /* -------------------------------------------------------------------- */ |
572 | 0 | const CPLString osFiltMaskTmpFile = osTmpFile + "fill_filtmask_work.tif"; |
573 | |
|
574 | 0 | auto poFiltMaskDS = std::unique_ptr<GDALDataset>(GDALDataset::FromHandle( |
575 | 0 | GDALCreate(hDriver, osFiltMaskTmpFile, nXSize, nYSize, 1, GDT_Byte, |
576 | 0 | aosWorkFileOptions.List()))); |
577 | |
|
578 | 0 | if (poFiltMaskDS == nullptr) |
579 | 0 | { |
580 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
581 | 0 | "Could not create mask work file. Check driver capabilities."); |
582 | 0 | return CE_Failure; |
583 | 0 | } |
584 | 0 | poFiltMaskDS->MarkSuppressOnClose(); |
585 | |
|
586 | 0 | GDALRasterBandH hFiltMaskBand = |
587 | 0 | GDALRasterBand::FromHandle(poFiltMaskDS->GetRasterBand(1)); |
588 | | |
589 | | /* -------------------------------------------------------------------- */ |
590 | | /* Allocate buffers for last scanline and this scanline. */ |
591 | | /* -------------------------------------------------------------------- */ |
592 | |
|
593 | 0 | GUInt32 *panLastY = |
594 | 0 | static_cast<GUInt32 *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(GUInt32))); |
595 | 0 | GUInt32 *panThisY = |
596 | 0 | static_cast<GUInt32 *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(GUInt32))); |
597 | 0 | GUInt32 *panTopDownY = |
598 | 0 | static_cast<GUInt32 *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(GUInt32))); |
599 | 0 | float *pafLastValue = |
600 | 0 | static_cast<float *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(float))); |
601 | 0 | float *pafThisValue = |
602 | 0 | static_cast<float *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(float))); |
603 | 0 | float *pafTopDownValue = |
604 | 0 | static_cast<float *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(float))); |
605 | 0 | float *pafScanline = |
606 | 0 | static_cast<float *>(VSI_CALLOC_VERBOSE(nXSize, sizeof(float))); |
607 | 0 | GByte *pabyMask = static_cast<GByte *>(VSI_CALLOC_VERBOSE(nXSize, 1)); |
608 | 0 | GByte *pabyFiltMask = static_cast<GByte *>(VSI_CALLOC_VERBOSE(nXSize, 1)); |
609 | |
|
610 | 0 | CPLErr eErr = CE_None; |
611 | |
|
612 | 0 | if (panLastY == nullptr || panThisY == nullptr || panTopDownY == nullptr || |
613 | 0 | pafLastValue == nullptr || pafThisValue == nullptr || |
614 | 0 | pafTopDownValue == nullptr || pafScanline == nullptr || |
615 | 0 | pabyMask == nullptr || pabyFiltMask == nullptr) |
616 | 0 | { |
617 | 0 | eErr = CE_Failure; |
618 | 0 | goto end; |
619 | 0 | } |
620 | | |
621 | 0 | for (int iX = 0; iX < nXSize; iX++) |
622 | 0 | { |
623 | 0 | panLastY[iX] = nNoDataVal; |
624 | 0 | } |
625 | | |
626 | | /* ==================================================================== */ |
627 | | /* Make first pass from top to bottom collecting the "last */ |
628 | | /* known value" for each column and writing it out to the work */ |
629 | | /* files. */ |
630 | | /* ==================================================================== */ |
631 | |
|
632 | 0 | for (int iY = 0; iY < nYSize && eErr == CE_None; iY++) |
633 | 0 | { |
634 | | /* -------------------------------------------------------------------- |
635 | | */ |
636 | | /* Read data and mask for this line. */ |
637 | | /* -------------------------------------------------------------------- |
638 | | */ |
639 | 0 | eErr = GDALRasterIO(hMaskBand, GF_Read, 0, iY, nXSize, 1, pabyMask, |
640 | 0 | nXSize, 1, GDT_Byte, 0, 0); |
641 | |
|
642 | 0 | if (eErr != CE_None) |
643 | 0 | break; |
644 | | |
645 | 0 | eErr = GDALRasterIO(hTargetBand, GF_Read, 0, iY, nXSize, 1, pafScanline, |
646 | 0 | nXSize, 1, GDT_Float32, 0, 0); |
647 | |
|
648 | 0 | if (eErr != CE_None) |
649 | 0 | break; |
650 | | |
651 | | /* -------------------------------------------------------------------- |
652 | | */ |
653 | | /* Figure out the most recent pixel for each column. */ |
654 | | /* -------------------------------------------------------------------- |
655 | | */ |
656 | | |
657 | 0 | for (int iX = 0; iX < nXSize; iX++) |
658 | 0 | { |
659 | 0 | if (pabyMask[iX]) |
660 | 0 | { |
661 | 0 | pafThisValue[iX] = pafScanline[iX]; |
662 | 0 | panThisY[iX] = iY; |
663 | 0 | } |
664 | 0 | else if (iY <= dfMaxSearchDist + panLastY[iX]) |
665 | 0 | { |
666 | 0 | pafThisValue[iX] = pafLastValue[iX]; |
667 | 0 | panThisY[iX] = panLastY[iX]; |
668 | 0 | } |
669 | 0 | else |
670 | 0 | { |
671 | 0 | panThisY[iX] = nNoDataVal; |
672 | 0 | } |
673 | 0 | } |
674 | | |
675 | | /* -------------------------------------------------------------------- |
676 | | */ |
677 | | /* Write out best index/value to working files. */ |
678 | | /* -------------------------------------------------------------------- |
679 | | */ |
680 | 0 | eErr = GDALRasterIO(hYBand, GF_Write, 0, iY, nXSize, 1, panThisY, |
681 | 0 | nXSize, 1, GDT_UInt32, 0, 0); |
682 | 0 | if (eErr != CE_None) |
683 | 0 | break; |
684 | | |
685 | 0 | eErr = GDALRasterIO(hValBand, GF_Write, 0, iY, nXSize, 1, pafThisValue, |
686 | 0 | nXSize, 1, GDT_Float32, 0, 0); |
687 | 0 | if (eErr != CE_None) |
688 | 0 | break; |
689 | | |
690 | | /* -------------------------------------------------------------------- |
691 | | */ |
692 | | /* Flip this/last buffers. */ |
693 | | /* -------------------------------------------------------------------- |
694 | | */ |
695 | 0 | std::swap(pafThisValue, pafLastValue); |
696 | 0 | std::swap(panThisY, panLastY); |
697 | | |
698 | | /* -------------------------------------------------------------------- |
699 | | */ |
700 | | /* report progress. */ |
701 | | /* -------------------------------------------------------------------- |
702 | | */ |
703 | 0 | if (!pfnProgress(dfProgressRatio * |
704 | 0 | (0.5 * (iY + 1) / static_cast<double>(nYSize)), |
705 | 0 | "Filling...", pProgressArg)) |
706 | 0 | { |
707 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
708 | 0 | eErr = CE_Failure; |
709 | 0 | } |
710 | 0 | } |
711 | |
|
712 | 0 | for (int iX = 0; iX < nXSize; iX++) |
713 | 0 | { |
714 | 0 | panLastY[iX] = nNoDataVal; |
715 | 0 | } |
716 | | |
717 | | /* ==================================================================== */ |
718 | | /* Now we will do collect similar this/last information from */ |
719 | | /* bottom to top and use it in combination with the top to */ |
720 | | /* bottom search info to interpolate. */ |
721 | | /* ==================================================================== */ |
722 | 0 | for (int iY = nYSize - 1; iY >= 0 && eErr == CE_None; iY--) |
723 | 0 | { |
724 | 0 | eErr = GDALRasterIO(hMaskBand, GF_Read, 0, iY, nXSize, 1, pabyMask, |
725 | 0 | nXSize, 1, GDT_Byte, 0, 0); |
726 | |
|
727 | 0 | if (eErr != CE_None) |
728 | 0 | break; |
729 | | |
730 | 0 | eErr = GDALRasterIO(hTargetBand, GF_Read, 0, iY, nXSize, 1, pafScanline, |
731 | 0 | nXSize, 1, GDT_Float32, 0, 0); |
732 | |
|
733 | 0 | if (eErr != CE_None) |
734 | 0 | break; |
735 | | |
736 | | /* -------------------------------------------------------------------- |
737 | | */ |
738 | | /* Figure out the most recent pixel for each column. */ |
739 | | /* -------------------------------------------------------------------- |
740 | | */ |
741 | | |
742 | 0 | for (int iX = 0; iX < nXSize; iX++) |
743 | 0 | { |
744 | 0 | if (pabyMask[iX]) |
745 | 0 | { |
746 | 0 | pafThisValue[iX] = pafScanline[iX]; |
747 | 0 | panThisY[iX] = iY; |
748 | 0 | } |
749 | 0 | else if (panLastY[iX] - iY <= dfMaxSearchDist) |
750 | 0 | { |
751 | 0 | pafThisValue[iX] = pafLastValue[iX]; |
752 | 0 | panThisY[iX] = panLastY[iX]; |
753 | 0 | } |
754 | 0 | else |
755 | 0 | { |
756 | 0 | panThisY[iX] = nNoDataVal; |
757 | 0 | } |
758 | 0 | } |
759 | | |
760 | | /* -------------------------------------------------------------------- |
761 | | */ |
762 | | /* Load the last y and corresponding value from the top down pass. |
763 | | */ |
764 | | /* -------------------------------------------------------------------- |
765 | | */ |
766 | 0 | eErr = GDALRasterIO(hYBand, GF_Read, 0, iY, nXSize, 1, panTopDownY, |
767 | 0 | nXSize, 1, GDT_UInt32, 0, 0); |
768 | |
|
769 | 0 | if (eErr != CE_None) |
770 | 0 | break; |
771 | | |
772 | 0 | eErr = GDALRasterIO(hValBand, GF_Read, 0, iY, nXSize, 1, |
773 | 0 | pafTopDownValue, nXSize, 1, GDT_Float32, 0, 0); |
774 | |
|
775 | 0 | if (eErr != CE_None) |
776 | 0 | break; |
777 | | |
778 | | /* -------------------------------------------------------------------- |
779 | | */ |
780 | | /* Attempt to interpolate any pixels that are nodata. */ |
781 | | /* -------------------------------------------------------------------- |
782 | | */ |
783 | 0 | memset(pabyFiltMask, 0, nXSize); |
784 | 0 | for (int iX = 0; iX < nXSize; iX++) |
785 | 0 | { |
786 | 0 | int nThisMaxSearchDist = nMaxSearchDist; |
787 | | |
788 | | // If this was a valid target - no change. |
789 | 0 | if (pabyMask[iX]) |
790 | 0 | continue; |
791 | | |
792 | 0 | enum Quadrants |
793 | 0 | { |
794 | 0 | QUAD_TOP_LEFT = 0, |
795 | 0 | QUAD_BOTTOM_LEFT = 1, |
796 | 0 | QUAD_TOP_RIGHT = 2, |
797 | 0 | QUAD_BOTTOM_RIGHT = 3, |
798 | 0 | }; |
799 | |
|
800 | 0 | constexpr int QUAD_COUNT = 4; |
801 | 0 | double adfQuadDist[QUAD_COUNT] = {}; |
802 | 0 | float afQuadValue[QUAD_COUNT] = {}; |
803 | |
|
804 | 0 | for (int iQuad = 0; iQuad < QUAD_COUNT; iQuad++) |
805 | 0 | { |
806 | 0 | adfQuadDist[iQuad] = dfMaxSearchDist + 1.0; |
807 | 0 | afQuadValue[iQuad] = 0.0; |
808 | 0 | } |
809 | | |
810 | | // Step left and right by one pixel searching for the closest |
811 | | // target value for each quadrant. |
812 | 0 | for (int iStep = 0; iStep <= nThisMaxSearchDist; iStep++) |
813 | 0 | { |
814 | 0 | const int iLeftX = std::max(0, iX - iStep); |
815 | 0 | const int iRightX = std::min(nXSize - 1, iX + iStep); |
816 | | |
817 | | // Top left includes current line. |
818 | 0 | QUAD_CHECK(adfQuadDist[QUAD_TOP_LEFT], |
819 | 0 | afQuadValue[QUAD_TOP_LEFT], iLeftX, |
820 | 0 | panTopDownY[iLeftX], iX, iY, pafTopDownValue[iLeftX], |
821 | 0 | nNoDataVal); |
822 | | |
823 | | // Bottom left. |
824 | 0 | QUAD_CHECK(adfQuadDist[QUAD_BOTTOM_LEFT], |
825 | 0 | afQuadValue[QUAD_BOTTOM_LEFT], iLeftX, |
826 | 0 | panLastY[iLeftX], iX, iY, pafLastValue[iLeftX], |
827 | 0 | nNoDataVal); |
828 | | |
829 | | // Top right and bottom right do no include center pixel. |
830 | 0 | if (iStep == 0) |
831 | 0 | continue; |
832 | | |
833 | | // Top right includes current line. |
834 | 0 | QUAD_CHECK(adfQuadDist[QUAD_TOP_RIGHT], |
835 | 0 | afQuadValue[QUAD_TOP_RIGHT], iRightX, |
836 | 0 | panTopDownY[iRightX], iX, iY, |
837 | 0 | pafTopDownValue[iRightX], nNoDataVal); |
838 | | |
839 | | // Bottom right. |
840 | 0 | QUAD_CHECK(adfQuadDist[QUAD_BOTTOM_RIGHT], |
841 | 0 | afQuadValue[QUAD_BOTTOM_RIGHT], iRightX, |
842 | 0 | panLastY[iRightX], iX, iY, pafLastValue[iRightX], |
843 | 0 | nNoDataVal); |
844 | | |
845 | | // Every four steps, recompute maximum distance. |
846 | 0 | if ((iStep & 0x3) == 0) |
847 | 0 | nThisMaxSearchDist = static_cast<int>(floor( |
848 | 0 | std::max(std::max(adfQuadDist[0], adfQuadDist[1]), |
849 | 0 | std::max(adfQuadDist[2], adfQuadDist[3])))); |
850 | 0 | } |
851 | |
|
852 | 0 | bool bHasSrcValues = false; |
853 | 0 | if (bNearest) |
854 | 0 | { |
855 | 0 | double dfNearestDist = dfMaxSearchDist + 1; |
856 | 0 | float fNearestValue = 0.0f; |
857 | |
|
858 | 0 | for (int iQuad = 0; iQuad < QUAD_COUNT; iQuad++) |
859 | 0 | { |
860 | 0 | if (adfQuadDist[iQuad] < dfNearestDist) |
861 | 0 | { |
862 | 0 | bHasSrcValues = true; |
863 | 0 | if (!bHasNoData || afQuadValue[iQuad] != fNoData) |
864 | 0 | { |
865 | 0 | fNearestValue = afQuadValue[iQuad]; |
866 | 0 | dfNearestDist = adfQuadDist[iQuad]; |
867 | 0 | } |
868 | 0 | } |
869 | 0 | } |
870 | |
|
871 | 0 | if (bHasSrcValues) |
872 | 0 | { |
873 | 0 | pabyFiltMask[iX] = 255; |
874 | 0 | if (dfNearestDist <= dfMaxSearchDist) |
875 | 0 | { |
876 | 0 | pabyMask[iX] = 255; |
877 | 0 | pafScanline[iX] = fNearestValue; |
878 | 0 | } |
879 | 0 | else |
880 | 0 | pafScanline[iX] = fNoData; |
881 | 0 | } |
882 | 0 | } |
883 | 0 | else |
884 | 0 | { |
885 | 0 | double dfWeightSum = 0.0; |
886 | 0 | double dfValueSum = 0.0; |
887 | |
|
888 | 0 | for (int iQuad = 0; iQuad < QUAD_COUNT; iQuad++) |
889 | 0 | { |
890 | 0 | if (adfQuadDist[iQuad] <= dfMaxSearchDist) |
891 | 0 | { |
892 | 0 | bHasSrcValues = true; |
893 | 0 | if (!bHasNoData || afQuadValue[iQuad] != fNoData) |
894 | 0 | { |
895 | 0 | const double dfWeight = 1.0 / adfQuadDist[iQuad]; |
896 | 0 | dfWeightSum += dfWeight; |
897 | 0 | dfValueSum += afQuadValue[iQuad] * dfWeight; |
898 | 0 | } |
899 | 0 | } |
900 | 0 | } |
901 | |
|
902 | 0 | if (bHasSrcValues) |
903 | 0 | { |
904 | 0 | pabyFiltMask[iX] = 255; |
905 | 0 | if (dfWeightSum > 0.0) |
906 | 0 | { |
907 | 0 | pabyMask[iX] = 255; |
908 | 0 | pafScanline[iX] = |
909 | 0 | static_cast<float>(dfValueSum / dfWeightSum); |
910 | 0 | } |
911 | 0 | else |
912 | 0 | pafScanline[iX] = fNoData; |
913 | 0 | } |
914 | 0 | } |
915 | 0 | } |
916 | | |
917 | | /* -------------------------------------------------------------------- |
918 | | */ |
919 | | /* Write out the updated data and mask information. */ |
920 | | /* -------------------------------------------------------------------- |
921 | | */ |
922 | 0 | eErr = GDALRasterIO(hTargetBand, GF_Write, 0, iY, nXSize, 1, |
923 | 0 | pafScanline, nXSize, 1, GDT_Float32, 0, 0); |
924 | |
|
925 | 0 | if (eErr != CE_None) |
926 | 0 | break; |
927 | | |
928 | 0 | if (poTmpMaskDS != nullptr) |
929 | 0 | { |
930 | | // Update (copy of) mask band when it has been provided by the |
931 | | // user |
932 | 0 | eErr = GDALRasterIO(hMaskBand, GF_Write, 0, iY, nXSize, 1, pabyMask, |
933 | 0 | nXSize, 1, GDT_Byte, 0, 0); |
934 | |
|
935 | 0 | if (eErr != CE_None) |
936 | 0 | break; |
937 | 0 | } |
938 | | |
939 | 0 | eErr = GDALRasterIO(hFiltMaskBand, GF_Write, 0, iY, nXSize, 1, |
940 | 0 | pabyFiltMask, nXSize, 1, GDT_Byte, 0, 0); |
941 | |
|
942 | 0 | if (eErr != CE_None) |
943 | 0 | break; |
944 | | |
945 | | /* -------------------------------------------------------------------- |
946 | | */ |
947 | | /* Flip this/last buffers. */ |
948 | | /* -------------------------------------------------------------------- |
949 | | */ |
950 | 0 | std::swap(pafThisValue, pafLastValue); |
951 | 0 | std::swap(panThisY, panLastY); |
952 | | |
953 | | /* -------------------------------------------------------------------- |
954 | | */ |
955 | | /* report progress. */ |
956 | | /* -------------------------------------------------------------------- |
957 | | */ |
958 | 0 | if (!pfnProgress( |
959 | 0 | dfProgressRatio * |
960 | 0 | (0.5 + 0.5 * (nYSize - iY) / static_cast<double>(nYSize)), |
961 | 0 | "Filling...", pProgressArg)) |
962 | 0 | { |
963 | 0 | CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); |
964 | 0 | eErr = CE_Failure; |
965 | 0 | } |
966 | 0 | } |
967 | | |
968 | | /* ==================================================================== */ |
969 | | /* Now we will do iterative average filters over the */ |
970 | | /* interpolated values to smooth things out and make linear */ |
971 | | /* artifacts less obvious. */ |
972 | | /* ==================================================================== */ |
973 | 0 | if (eErr == CE_None && nSmoothingIterations > 0) |
974 | 0 | { |
975 | 0 | if (poTmpMaskDS == nullptr) |
976 | 0 | { |
977 | | // Force masks to be to flushed and recomputed when the user |
978 | | // didn't pass a user-provided hMaskBand, and we assigned it |
979 | | // to be the mask band of hTargetBand. |
980 | 0 | GDALFlushRasterCache(hMaskBand); |
981 | 0 | } |
982 | |
|
983 | 0 | void *pScaledProgress = GDALCreateScaledProgress( |
984 | 0 | dfProgressRatio, 1.0, pfnProgress, pProgressArg); |
985 | |
|
986 | 0 | eErr = GDALMultiFilter(hTargetBand, hMaskBand, hFiltMaskBand, |
987 | 0 | nSmoothingIterations, GDALScaledProgress, |
988 | 0 | pScaledProgress); |
989 | |
|
990 | 0 | GDALDestroyScaledProgress(pScaledProgress); |
991 | 0 | } |
992 | | |
993 | | /* -------------------------------------------------------------------- */ |
994 | | /* Close and clean up temporary files. Free working buffers */ |
995 | | /* -------------------------------------------------------------------- */ |
996 | 0 | end: |
997 | 0 | CPLFree(panLastY); |
998 | 0 | CPLFree(panThisY); |
999 | 0 | CPLFree(panTopDownY); |
1000 | 0 | CPLFree(pafLastValue); |
1001 | 0 | CPLFree(pafThisValue); |
1002 | 0 | CPLFree(pafTopDownValue); |
1003 | 0 | CPLFree(pafScanline); |
1004 | 0 | CPLFree(pabyMask); |
1005 | 0 | CPLFree(pabyFiltMask); |
1006 | |
|
1007 | 0 | return eErr; |
1008 | 0 | } |