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