1 : /******************************************************************************
2 : * $Id: gdalsievefilter.cpp 18523 2010-01-11 18:12:25Z mloskot $
3 : *
4 : * Project: GDAL
5 : * Purpose: Raster to Polygon Converter
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2008, Frank Warmerdam
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "gdal_alg_priv.h"
31 : #include "cpl_conv.h"
32 : #include <vector>
33 :
34 : CPL_CVSID("$Id: gdalsievefilter.cpp 18523 2010-01-11 18:12:25Z mloskot $");
35 :
36 : #define GP_NODATA_MARKER -51502112
37 :
38 : /*
39 : * General Plan
40 : *
41 : * 1) make a pass with the polygon enumerator to build up the
42 : * polygon map array. Also accumulate polygon size information.
43 : *
44 : * 2) Identify the polygons that need to be merged.
45 : *
46 : * 3) Make a pass with the polygon enumerator. For each "to be merged"
47 : * polygon keep track of it's largest neighbour.
48 : *
49 : * 4) Fix up remappings that would go to polygons smaller than the seive
50 : * size. Ensure these in term map to the largest neighbour of the
51 : * "to be seieved" polygons.
52 : *
53 : * 5) Make another pass with the polygon enumerator. This time we remap
54 : * the actual pixel values of all polygons to be merged.
55 : *
56 : */
57 :
58 : /************************************************************************/
59 : /* GPMaskImageData() */
60 : /* */
61 : /* Mask out image pixels to a special nodata value if the mask */
62 : /* band is zero. */
63 : /************************************************************************/
64 :
65 : static CPLErr
66 : GPMaskImageData( GDALRasterBandH hMaskBand, GByte *pabyMaskLine, int iY, int nXSize,
67 21 : GInt32 *panImageLine )
68 :
69 : {
70 : CPLErr eErr;
71 :
72 : eErr = GDALRasterIO( hMaskBand, GF_Read, 0, iY, nXSize, 1,
73 21 : pabyMaskLine, nXSize, 1, GDT_Byte, 0, 0 );
74 21 : if( eErr == CE_None )
75 : {
76 : int i;
77 126 : for( i = 0; i < nXSize; i++ )
78 : {
79 105 : if( pabyMaskLine[i] == 0 )
80 0 : panImageLine[i] = GP_NODATA_MARKER;
81 : }
82 : }
83 :
84 21 : return eErr;
85 : }
86 :
87 : /************************************************************************/
88 : /* CompareNeighbour() */
89 : /* */
90 : /* Compare two neighbouring polygons, and update eaches */
91 : /* "biggest neighbour" if the other is larger than it's current */
92 : /* largest neighbour. */
93 : /* */
94 : /* Note that this should end up with each polygon knowing the */
95 : /* id of it's largest neighbour. No attempt is made to */
96 : /* restrict things to small polygons that we will be merging, */
97 : /* nor to exclude assigning "biggest neighbours" that are still */
98 : /* smaller than our sieve threshold. */
99 : /************************************************************************/
100 :
101 : static inline void CompareNeighbour( int nPolyId1, int nPolyId2,
102 : int *panPolyIdMap,
103 : int *panPolyValue,
104 : std::vector<int> &anPolySizes,
105 528 : std::vector<int> &anBigNeighbour )
106 :
107 : {
108 : // make sure we are working with the final merged polygon ids.
109 528 : nPolyId1 = panPolyIdMap[nPolyId1];
110 528 : nPolyId2 = panPolyIdMap[nPolyId2];
111 :
112 528 : if( nPolyId1 == nPolyId2 )
113 204 : return;
114 :
115 : // nodata polygon do not need neighbours, and cannot be neighbours
116 : // to valid polygons.
117 324 : if( panPolyValue[nPolyId1] == GP_NODATA_MARKER
118 : || panPolyValue[nPolyId2] == GP_NODATA_MARKER )
119 0 : return;
120 :
121 324 : if( anBigNeighbour[nPolyId1] == -1
122 : || anPolySizes[anBigNeighbour[nPolyId1]] < anPolySizes[nPolyId2] )
123 127 : anBigNeighbour[nPolyId1] = nPolyId2;
124 :
125 324 : if( anBigNeighbour[nPolyId2] == -1
126 : || anPolySizes[anBigNeighbour[nPolyId2]] < anPolySizes[nPolyId1] )
127 55 : anBigNeighbour[nPolyId2] = nPolyId1;
128 : }
129 :
130 : /************************************************************************/
131 : /* GDALSieveFilter() */
132 : /************************************************************************/
133 :
134 : /**
135 : * Removes small raster polygons.
136 : *
137 : * The function removes raster polygons smaller than a provided
138 : * threshold size (in pixels) and replaces replaces them with the pixel value
139 : * of the largest neighbour polygon.
140 : *
141 : * Polygon are determined (per GDALRasterPolygonEnumerator) as regions of
142 : * the raster where the pixels all have the same value, and that are contiguous
143 : * (connected).
144 : *
145 : * Pixels determined to be "nodata" per hMaskBand will not be treated as part
146 : * of a polygon regardless of their pixel values. Nodata areas will never be
147 : * changed nor affect polygon sizes.
148 : *
149 : * Polygons smaller than the threshold with no neighbours that are as large
150 : * as the threshold will not be altered. Polygons surrounded by nodata areas
151 : * will therefore not be altered.
152 : *
153 : * The algorithm makes three passes over the input file to enumerate the
154 : * polygons and collect limited information about them. Memory use is
155 : * proportional to the number of polygons (roughly 24 bytes per polygon), but
156 : * is not directly related to the size of the raster. So very large raster
157 : * files can be processed effectively if there aren't too many polygons. But
158 : * extremely noisy rasters with many one pixel polygons will end up being
159 : * expensive (in memory) to process.
160 : *
161 : * @param hSrcBand the source raster band to be processed.
162 : * @param hMaskBand an optional mask band. All pixels in the mask band with a
163 : * value other than zero will be considered suitable for inclusion in polygons.
164 : * @param hDstBand the output raster band. It may be the same as hSrcBand
165 : * to update the source in place.
166 : * @param nSizeThreshold raster polygons with sizes smaller than this will
167 : * be merged into their largest neighbour.
168 : * @param nConnectedness either 4 indicating that diagonal pixels are not
169 : * considered directly adjacent for polygon membership purposes or 8
170 : * indicating they are.
171 : * @param papszOption algorithm options in name=value list form. None currently
172 : * supported.
173 : * @param pfnProgress callback for reporting algorithm progress matching the
174 : * GDALProgressFunc() semantics. May be NULL.
175 : * @param pProgressArg callback argument passed to pfnProgress.
176 : *
177 : * @return CE_None on success or CE_Failure if an error occurs.
178 : */
179 :
180 : CPLErr CPL_STDCALL
181 : GDALSieveFilter( GDALRasterBandH hSrcBand, GDALRasterBandH hMaskBand,
182 : GDALRasterBandH hDstBand,
183 : int nSizeThreshold, int nConnectedness,
184 : char **papszOptions,
185 : GDALProgressFunc pfnProgress,
186 6 : void * pProgressArg )
187 :
188 : {
189 6 : VALIDATE_POINTER1( hSrcBand, "GDALSieveFilter", CE_Failure );
190 6 : VALIDATE_POINTER1( hDstBand, "GDALSieveFilter", CE_Failure );
191 :
192 6 : if( pfnProgress == NULL )
193 5 : pfnProgress = GDALDummyProgress;
194 :
195 : /* -------------------------------------------------------------------- */
196 : /* Allocate working buffers. */
197 : /* -------------------------------------------------------------------- */
198 6 : CPLErr eErr = CE_None;
199 6 : int nXSize = GDALGetRasterBandXSize( hSrcBand );
200 6 : int nYSize = GDALGetRasterBandYSize( hSrcBand );
201 6 : GInt32 *panLastLineVal = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
202 6 : GInt32 *panThisLineVal = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
203 6 : GInt32 *panLastLineId = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
204 6 : GInt32 *panThisLineId = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
205 6 : GInt32 *panThisLineWriteVal = (GInt32 *) VSIMalloc2(sizeof(GInt32), nXSize);
206 6 : GByte *pabyMaskLine = (hMaskBand != NULL) ? (GByte *) VSIMalloc(nXSize) : NULL;
207 6 : if (panLastLineVal == NULL || panThisLineVal == NULL ||
208 : panLastLineId == NULL || panThisLineId == NULL ||
209 : panThisLineWriteVal == NULL ||
210 : (hMaskBand != NULL && pabyMaskLine == NULL))
211 : {
212 : CPLError(CE_Failure, CPLE_OutOfMemory,
213 0 : "Could not allocate enough memory for temporary buffers");
214 0 : CPLFree( panThisLineId );
215 0 : CPLFree( panLastLineId );
216 0 : CPLFree( panThisLineVal );
217 0 : CPLFree( panLastLineVal );
218 0 : CPLFree( panThisLineWriteVal );
219 0 : CPLFree( pabyMaskLine );
220 0 : return CE_Failure;
221 : }
222 :
223 : /* -------------------------------------------------------------------- */
224 : /* The first pass over the raster is only used to build up the */
225 : /* polygon id map so we will know in advance what polygons are */
226 : /* what on the second pass. */
227 : /* -------------------------------------------------------------------- */
228 : int iY, iX, iPoly;
229 6 : GDALRasterPolygonEnumerator oFirstEnum( nConnectedness );
230 6 : std::vector<int> anPolySizes;
231 :
232 49 : for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
233 : {
234 : eErr = GDALRasterIO(
235 : hSrcBand,
236 : GF_Read, 0, iY, nXSize, 1,
237 43 : panThisLineVal, nXSize, 1, GDT_Int32, 0, 0 );
238 :
239 43 : if( eErr == CE_None && hMaskBand != NULL )
240 7 : eErr = GPMaskImageData( hMaskBand, pabyMaskLine, iY, nXSize, panThisLineVal );
241 :
242 43 : if( iY == 0 )
243 : oFirstEnum.ProcessLine(
244 6 : NULL, panThisLineVal, NULL, panThisLineId, nXSize );
245 : else
246 : oFirstEnum.ProcessLine(
247 : panLastLineVal, panThisLineVal,
248 : panLastLineId, panThisLineId,
249 37 : nXSize );
250 :
251 : /* -------------------------------------------------------------------- */
252 : /* Accumulate polygon sizes. */
253 : /* -------------------------------------------------------------------- */
254 43 : if( oFirstEnum.nNextPolygonId > (int) anPolySizes.size() )
255 39 : anPolySizes.resize( oFirstEnum.nNextPolygonId );
256 :
257 298 : for( iX = 0; iX < nXSize; iX++ )
258 : {
259 255 : iPoly = panThisLineId[iX];
260 :
261 255 : CPLAssert( iPoly >= 0 );
262 255 : anPolySizes[iPoly] += 1;
263 : }
264 :
265 : /* -------------------------------------------------------------------- */
266 : /* swap this/last lines. */
267 : /* -------------------------------------------------------------------- */
268 43 : GInt32 *panTmp = panLastLineVal;
269 43 : panLastLineVal = panThisLineVal;
270 43 : panThisLineVal = panTmp;
271 :
272 43 : panTmp = panThisLineId;
273 43 : panThisLineId = panLastLineId;
274 43 : panLastLineId = panTmp;
275 :
276 : /* -------------------------------------------------------------------- */
277 : /* Report progress, and support interrupts. */
278 : /* -------------------------------------------------------------------- */
279 43 : if( eErr == CE_None
280 : && !pfnProgress( 0.25 * ((iY+1) / (double) nYSize),
281 : "", pProgressArg ) )
282 : {
283 0 : CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
284 0 : eErr = CE_Failure;
285 : }
286 : }
287 :
288 : /* -------------------------------------------------------------------- */
289 : /* Make a pass through the maps, ensuring every polygon id */
290 : /* points to the final id it should use, not an intermediate */
291 : /* value. */
292 : /* -------------------------------------------------------------------- */
293 6 : oFirstEnum.CompleteMerges();
294 :
295 : /* -------------------------------------------------------------------- */
296 : /* Push the sizes of merged polygon fragments into the the */
297 : /* merged polygon id's count. */
298 : /* -------------------------------------------------------------------- */
299 120 : for( iPoly = 0; iPoly < oFirstEnum.nNextPolygonId; iPoly++ )
300 : {
301 114 : if( oFirstEnum.panPolyIdMap[iPoly] != iPoly )
302 : {
303 11 : anPolySizes[oFirstEnum.panPolyIdMap[iPoly]] += anPolySizes[iPoly];
304 11 : anPolySizes[iPoly] = 0;
305 : }
306 : }
307 :
308 : /* -------------------------------------------------------------------- */
309 : /* We will use a new enumerator for the second pass primariliy */
310 : /* so we can preserve the first pass map. */
311 : /* -------------------------------------------------------------------- */
312 6 : GDALRasterPolygonEnumerator oSecondEnum( nConnectedness );
313 :
314 6 : std::vector<int> anBigNeighbour;
315 12 : anBigNeighbour.resize( anPolySizes.size() );
316 :
317 120 : for( iPoly = 0; iPoly < (int) anPolySizes.size(); iPoly++ )
318 114 : anBigNeighbour[iPoly] = -1;
319 :
320 : /* ==================================================================== */
321 : /* Second pass ... identify the largest neighbour for each */
322 : /* polygon. */
323 : /* ==================================================================== */
324 49 : for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
325 : {
326 : /* -------------------------------------------------------------------- */
327 : /* Read the image data. */
328 : /* -------------------------------------------------------------------- */
329 : eErr = GDALRasterIO( hSrcBand, GF_Read, 0, iY, nXSize, 1,
330 43 : panThisLineVal, nXSize, 1, GDT_Int32, 0, 0 );
331 :
332 43 : if( eErr == CE_None && hMaskBand != NULL )
333 7 : eErr = GPMaskImageData( hMaskBand, pabyMaskLine, iY, nXSize, panThisLineVal );
334 :
335 43 : if( eErr != CE_None )
336 0 : continue;
337 :
338 : /* -------------------------------------------------------------------- */
339 : /* Determine what polygon the various pixels belong to (redoing */
340 : /* the same thing done in the first pass above). */
341 : /* -------------------------------------------------------------------- */
342 43 : if( iY == 0 )
343 : oSecondEnum.ProcessLine(
344 6 : NULL, panThisLineVal, NULL, panThisLineId, nXSize );
345 : else
346 : oSecondEnum.ProcessLine(
347 : panLastLineVal, panThisLineVal,
348 : panLastLineId, panThisLineId,
349 37 : nXSize );
350 :
351 : /* -------------------------------------------------------------------- */
352 : /* Check our neighbours, and update our biggest neighbour map */
353 : /* as appropriate. */
354 : /* -------------------------------------------------------------------- */
355 298 : for( iX = 0; iX < nXSize; iX++ )
356 : {
357 255 : if( iY > 0 )
358 : {
359 : CompareNeighbour( panThisLineId[iX],
360 : panLastLineId[iX],
361 : oFirstEnum.panPolyIdMap,
362 : oFirstEnum.panPolyValue,
363 220 : anPolySizes, anBigNeighbour );
364 :
365 220 : if( iX > 0 && nConnectedness == 8 )
366 : CompareNeighbour( panThisLineId[iX],
367 : panLastLineId[iX-1],
368 : oFirstEnum.panPolyIdMap,
369 : oFirstEnum.panPolyValue,
370 48 : anPolySizes, anBigNeighbour );
371 :
372 220 : if( iX < nXSize-1 && nConnectedness == 8 )
373 : CompareNeighbour( panThisLineId[iX],
374 : panLastLineId[iX+1],
375 : oFirstEnum.panPolyIdMap,
376 : oFirstEnum.panPolyValue,
377 48 : anPolySizes, anBigNeighbour );
378 :
379 : }
380 :
381 255 : if( iX > 0 )
382 : CompareNeighbour( panThisLineId[iX],
383 : panThisLineId[iX-1],
384 : oFirstEnum.panPolyIdMap,
385 : oFirstEnum.panPolyValue,
386 212 : anPolySizes, anBigNeighbour );
387 :
388 : // We don't need to compare to next pixel or next line
389 : // since they will be compared to us.
390 : }
391 :
392 : /* -------------------------------------------------------------------- */
393 : /* Swap pixel value, and polygon id lines to be ready for the */
394 : /* next line. */
395 : /* -------------------------------------------------------------------- */
396 43 : GInt32 *panTmp = panLastLineVal;
397 43 : panLastLineVal = panThisLineVal;
398 43 : panThisLineVal = panTmp;
399 :
400 43 : panTmp = panThisLineId;
401 43 : panThisLineId = panLastLineId;
402 43 : panLastLineId = panTmp;
403 :
404 : /* -------------------------------------------------------------------- */
405 : /* Report progress, and support interrupts. */
406 : /* -------------------------------------------------------------------- */
407 43 : if( eErr == CE_None
408 : && !pfnProgress( 0.25 + 0.25 * ((iY+1) / (double) nYSize),
409 : "", pProgressArg ) )
410 : {
411 0 : CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
412 0 : eErr = CE_Failure;
413 : }
414 : }
415 :
416 : /* -------------------------------------------------------------------- */
417 : /* If our biggest neighbour is still smaller than the */
418 : /* threshold, then try tracking to that polygons biggest */
419 : /* neighbour, and so forth. */
420 : /* -------------------------------------------------------------------- */
421 6 : int nFailedMerges = 0;
422 6 : int nIsolatedSmall = 0;
423 6 : int nSieveTargets = 0;
424 :
425 120 : for( iPoly = 0; iPoly < (int) anPolySizes.size(); iPoly++ )
426 : {
427 114 : if( oFirstEnum.panPolyIdMap[iPoly] != iPoly )
428 11 : continue;
429 :
430 : // Ignore nodata polygons.
431 103 : if( oFirstEnum.panPolyValue[iPoly] == GP_NODATA_MARKER )
432 0 : continue;
433 :
434 : // Don't try to merge polygons larger than the threshold.
435 103 : if( anPolySizes[iPoly] >= nSizeThreshold )
436 : {
437 23 : anBigNeighbour[iPoly] = -1;
438 23 : continue;
439 : }
440 :
441 80 : nSieveTargets++;
442 :
443 : // if we have no neighbours but we are small, what shall we do?
444 80 : if( anBigNeighbour[iPoly] == -1 )
445 : {
446 0 : nIsolatedSmall++;
447 0 : continue;
448 : }
449 :
450 : // If our biggest neighbour is larger than the threshold
451 : // then we are golden.
452 80 : if( anPolySizes[anBigNeighbour[iPoly]] >= nSizeThreshold )
453 54 : continue;
454 :
455 : #ifdef notdef
456 : // Will our neighbours biggest neighbour do?
457 : // Eventually we need something sort of recursive here with
458 : // loop detection.
459 : if( anPolySizes[anBigNeighbour[anBigNeighbour[iPoly]]]
460 : >= nSizeThreshold )
461 : {
462 : anBigNeighbour[iPoly] = anBigNeighbour[anBigNeighbour[iPoly]];
463 : continue;
464 : }
465 : #endif
466 :
467 26 : nFailedMerges++;
468 26 : anBigNeighbour[iPoly] = -1;
469 : }
470 :
471 : CPLDebug( "GDALSieveFilter",
472 : "Small Polygons: %d, Isolated: %d, Unmergable: %d",
473 6 : nSieveTargets, nIsolatedSmall, nFailedMerges );
474 :
475 : /* ==================================================================== */
476 : /* Make a third pass over the image, actually applying the */
477 : /* merges. We reuse the second enumerator but preserve the */
478 : /* "final maps" from the first. */
479 : /* ==================================================================== */
480 6 : oSecondEnum.Clear();
481 :
482 :
483 49 : for( iY = 0; eErr == CE_None && iY < nYSize; iY++ )
484 : {
485 : /* -------------------------------------------------------------------- */
486 : /* Read the image data. */
487 : /* -------------------------------------------------------------------- */
488 : eErr = GDALRasterIO( hSrcBand, GF_Read, 0, iY, nXSize, 1,
489 43 : panThisLineVal, nXSize, 1, GDT_Int32, 0, 0 );
490 :
491 43 : memcpy( panThisLineWriteVal, panThisLineVal, 4 * nXSize );
492 :
493 43 : if( eErr == CE_None && hMaskBand != NULL )
494 7 : eErr = GPMaskImageData( hMaskBand, pabyMaskLine, iY, nXSize, panThisLineVal );
495 :
496 43 : if( eErr != CE_None )
497 0 : continue;
498 :
499 : /* -------------------------------------------------------------------- */
500 : /* Determine what polygon the various pixels belong to (redoing */
501 : /* the same thing done in the first pass above). */
502 : /* -------------------------------------------------------------------- */
503 43 : if( iY == 0 )
504 : oSecondEnum.ProcessLine(
505 6 : NULL, panThisLineVal, NULL, panThisLineId, nXSize );
506 : else
507 : oSecondEnum.ProcessLine(
508 : panLastLineVal, panThisLineVal,
509 : panLastLineId, panThisLineId,
510 37 : nXSize );
511 :
512 : /* -------------------------------------------------------------------- */
513 : /* Reprocess the actual pixel values according to the polygon */
514 : /* merging, and write out this line of image data. */
515 : /* -------------------------------------------------------------------- */
516 298 : for( iX = 0; iX < nXSize; iX++ )
517 : {
518 255 : int iThisPoly = oFirstEnum.panPolyIdMap[panThisLineId[iX]];
519 :
520 255 : if( anBigNeighbour[iThisPoly] != -1 )
521 : {
522 : panThisLineWriteVal[iX] =
523 : oFirstEnum.panPolyValue[
524 54 : anBigNeighbour[iThisPoly]];
525 : }
526 : }
527 :
528 : /* -------------------------------------------------------------------- */
529 : /* Write the update data out. */
530 : /* -------------------------------------------------------------------- */
531 : eErr = GDALRasterIO( hDstBand, GF_Write, 0, iY, nXSize, 1,
532 43 : panThisLineWriteVal, nXSize, 1, GDT_Int32, 0, 0 );
533 :
534 : /* -------------------------------------------------------------------- */
535 : /* Swap pixel value, and polygon id lines to be ready for the */
536 : /* next line. */
537 : /* -------------------------------------------------------------------- */
538 43 : GInt32 *panTmp = panLastLineVal;
539 43 : panLastLineVal = panThisLineVal;
540 43 : panThisLineVal = panTmp;
541 :
542 43 : panTmp = panThisLineId;
543 43 : panThisLineId = panLastLineId;
544 43 : panLastLineId = panTmp;
545 :
546 : /* -------------------------------------------------------------------- */
547 : /* Report progress, and support interrupts. */
548 : /* -------------------------------------------------------------------- */
549 43 : if( eErr == CE_None
550 : && !pfnProgress( 0.5 + 0.5 * ((iY+1) / (double) nYSize),
551 : "", pProgressArg ) )
552 : {
553 0 : CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
554 0 : eErr = CE_Failure;
555 : }
556 : }
557 :
558 : /* -------------------------------------------------------------------- */
559 : /* Cleanup */
560 : /* -------------------------------------------------------------------- */
561 6 : CPLFree( panThisLineId );
562 6 : CPLFree( panLastLineId );
563 6 : CPLFree( panThisLineVal );
564 6 : CPLFree( panLastLineVal );
565 6 : CPLFree( panThisLineWriteVal );
566 6 : CPLFree( pabyMaskLine );
567 :
568 6 : return eErr;
569 : }
570 :
|