Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Utilities
4 : * Purpose: Convert nearly black or nearly white border to exact black/white
5 : * using the flood fill algorithm.
6 : * Author: Even Rouault <even dot rouault at spatialys.com>
7 : *
8 : * ****************************************************************************
9 : * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "gdal_priv.h"
15 : #include "nearblack_lib.h"
16 :
17 : #include <algorithm>
18 : #include <memory>
19 : #include <queue>
20 :
21 : /************************************************************************/
22 : /* GDALNearblackFloodFillAlg */
23 : /************************************************************************/
24 :
25 : // Implements the "final, combined-scan-and-fill span filler was then published
26 : // in 1990" algorithm of https://en.wikipedia.org/wiki/Flood_fill#Span_filling
27 :
28 : struct GDALNearblackFloodFillAlg
29 : {
30 : // Input arguments of the algorithm
31 : const GDALNearblackOptions *m_psOptions = nullptr;
32 : GDALDataset *m_poSrcDataset = nullptr;
33 : GDALDataset *m_poDstDS = nullptr;
34 : GDALRasterBand *m_poMaskBand = nullptr;
35 : int m_nSrcBands = 0;
36 : int m_nDstBands = 0;
37 : bool m_bSetMask = false;
38 : Colors m_oColors{};
39 : GByte m_nReplacevalue = 0;
40 :
41 : // As we (generally) do not modify the value of pixels that are "black"
42 : // we need to keep track of the pixels we visited
43 : // Cf https://en.wikipedia.org/wiki/Flood_fill#Disadvantages_2
44 : // and https://en.wikipedia.org/wiki/Flood_fill#Adding_pattern_filling_support
45 : // for the requirement to add that extra sentinel
46 : std::unique_ptr<GDALDataset> m_poVisitedDS = nullptr;
47 :
48 : // Active line for the m_abyLine, m_abyLineMustSet, m_abyMask buffers
49 : int m_nLoadedLine = -1;
50 :
51 : // Whether Set(..., m_nLoadedLine) has been called
52 : bool m_bLineModified = true;
53 :
54 : // Content of m_poSrcDataset/m_poDstDS for m_nLoadedLine
55 : // Contains m_nDstBands * nXSize values in the order (R,G,B),(R,G,B),...
56 : std::vector<GByte> m_abyLine{};
57 :
58 : static constexpr GByte MUST_FILL_UNINIT = 0; // must be 0
59 : static constexpr GByte MUST_FILL_FALSE = 1;
60 : static constexpr GByte MUST_FILL_TRUE = 2;
61 : // Content of m_poVisitedDS for m_nLoadedLine
62 : std::vector<GByte> m_abyLineMustSet{};
63 :
64 : // Only use if m_bSetMask
65 : std::vector<GByte> m_abyMask{};
66 :
67 : // Used for progress bar. Incremented the first time a line ifs loaded
68 : int m_nCountLoadedOnce = 0;
69 :
70 : // m_abLineLoadedOnce[line] is set to true after the first time the line
71 : // of m_poSrcDataset is loaded by LoadLine(line)
72 : std::vector<bool> m_abLineLoadedOnce{};
73 :
74 : // m_abLineSavedOnce[line] is set to true after the first time the line
75 : // of m_poDstDS is written by LoadLine()
76 : std::vector<bool> m_abLineSavedOnce{};
77 :
78 : #ifdef DEBUG
79 : size_t m_nMaxQueueSize = 0;
80 : #endif
81 :
82 : // Entry point
83 : bool Process();
84 :
85 : private:
86 : bool Fill(int iX, int iY);
87 : bool LoadLine(int iY);
88 : bool MustSet(int iX, int iY);
89 : void Set(int iX, int iY);
90 : };
91 :
92 : /************************************************************************/
93 : /* GDALNearblackFloodFillAlg::MustSet() */
94 : /* */
95 : /* Called Inside() in https://en.wikipedia.org/wiki/Flood_fill */
96 : /************************************************************************/
97 :
98 : // Returns true if the pixel (iX, iY) is "black" (or more generally transparent
99 : // according to m_oColors)
100 9694 : bool GDALNearblackFloodFillAlg::MustSet(int iX, int iY)
101 : {
102 9694 : CPLAssert(iX >= 0);
103 9694 : CPLAssert(iX < m_poSrcDataset->GetRasterXSize());
104 :
105 9694 : CPLAssert(iY >= 0);
106 9694 : CPLAssert(iY < m_poSrcDataset->GetRasterYSize());
107 9694 : CPLAssert(iY == m_nLoadedLine);
108 9694 : CPL_IGNORE_RET_VAL(iY);
109 :
110 9694 : if (m_abyLineMustSet[iX] != MUST_FILL_UNINIT)
111 : {
112 1367 : return m_abyLineMustSet[iX] == MUST_FILL_TRUE;
113 : }
114 :
115 : /***** loop over the colors *****/
116 :
117 11643 : for (int iColor = 0; iColor < static_cast<int>(m_oColors.size()); iColor++)
118 : {
119 9970 : const Color &oColor = m_oColors[iColor];
120 :
121 : /***** loop over the bands *****/
122 9970 : bool bIsNonBlack = false;
123 :
124 29643 : for (int iBand = 0; iBand < m_nSrcBands; iBand++)
125 : {
126 22989 : const int nPix = m_abyLine[iX * m_nDstBands + iBand];
127 :
128 45708 : if (oColor[iBand] - nPix > m_psOptions->nNearDist ||
129 22719 : nPix > m_psOptions->nNearDist + oColor[iBand])
130 : {
131 3316 : bIsNonBlack = true;
132 3316 : break;
133 : }
134 : }
135 :
136 9970 : if (!bIsNonBlack)
137 : {
138 6654 : m_abyLineMustSet[iX] = MUST_FILL_TRUE;
139 6654 : return true;
140 : }
141 : }
142 :
143 1673 : m_abyLineMustSet[iX] = MUST_FILL_FALSE;
144 1673 : return false;
145 : }
146 :
147 : /************************************************************************/
148 : /* GDALNearblackFloodFillAlg::LoadLine() */
149 : /************************************************************************/
150 :
151 : // Load the new line iY, and saves if needed buffer of the previous loaded
152 : // line (m_nLoadedLine).
153 : // Returns true if no error
154 2623 : bool GDALNearblackFloodFillAlg::LoadLine(int iY)
155 : {
156 2623 : if (iY != m_nLoadedLine)
157 : {
158 : #ifdef DEBUG
159 : // CPLDebug("GDAL", "GDALNearblackFloodFillAlg::LoadLine(%d)", iY);
160 : #endif
161 1067 : const int nXSize = m_poSrcDataset->GetRasterXSize();
162 :
163 1067 : if (m_nLoadedLine >= 0)
164 : {
165 1575 : if (m_bLineModified || (m_poDstDS != m_poSrcDataset &&
166 1575 : !m_abLineSavedOnce[m_nLoadedLine]))
167 : {
168 438 : if (m_poDstDS->RasterIO(
169 438 : GF_Write, 0, m_nLoadedLine, nXSize, 1, m_abyLine.data(),
170 438 : nXSize, 1, GDT_Byte, m_nDstBands, nullptr, m_nDstBands,
171 438 : static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
172 438 : nullptr) != CE_None)
173 : {
174 0 : return false;
175 : }
176 : }
177 :
178 1300 : if (m_bSetMask &&
179 1300 : (m_bLineModified || !m_abLineSavedOnce[m_nLoadedLine]))
180 : {
181 121 : if (m_poMaskBand->RasterIO(GF_Write, 0, m_nLoadedLine, nXSize,
182 121 : 1, m_abyMask.data(), nXSize, 1,
183 121 : GDT_Byte, 0, 0, nullptr) != CE_None)
184 : {
185 0 : return false;
186 : }
187 : }
188 :
189 1028 : m_abLineSavedOnce[m_nLoadedLine] = true;
190 : }
191 :
192 1067 : if (iY >= 0)
193 : {
194 1028 : if (m_poDstDS != m_poSrcDataset && m_abLineSavedOnce[iY])
195 : {
196 : // If the output dataset is different from the source one,
197 : // load from the output dataset if we have already written the
198 : // line of interest
199 582 : if (m_poDstDS->RasterIO(
200 582 : GF_Read, 0, iY, nXSize, 1, m_abyLine.data(), nXSize, 1,
201 582 : GDT_Byte, m_nDstBands, nullptr, m_nDstBands,
202 582 : static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
203 582 : nullptr) != CE_None)
204 : {
205 0 : return false;
206 : }
207 : }
208 : else
209 : {
210 : // Otherwise load from the source data
211 446 : if (m_poSrcDataset->RasterIO(
212 446 : GF_Read, 0, iY, nXSize, 1, m_abyLine.data(), nXSize, 1,
213 : GDT_Byte,
214 : // m_nSrcBands intended
215 : m_nSrcBands,
216 : // m_nDstBands intended
217 446 : nullptr, m_nDstBands,
218 446 : static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
219 446 : nullptr) != CE_None)
220 : {
221 0 : return false;
222 : }
223 :
224 : // Initialize the alpha component to 255 if it is the first time
225 : // we load that line.
226 446 : if (m_psOptions->bSetAlpha && !m_abLineLoadedOnce[iY])
227 : {
228 7650 : for (int iCol = 0; iCol < nXSize; iCol++)
229 : {
230 7500 : m_abyLine[iCol * m_nDstBands + m_nDstBands - 1] = 255;
231 : }
232 : }
233 : }
234 :
235 1028 : if (m_bSetMask)
236 : {
237 272 : if (!m_abLineLoadedOnce[iY])
238 : {
239 2700 : for (int iCol = 0; iCol < nXSize; iCol++)
240 : {
241 2625 : m_abyMask[iCol] = 255;
242 : }
243 : }
244 : else
245 : {
246 197 : if (m_poMaskBand->RasterIO(
247 197 : GF_Read, 0, iY, nXSize, 1, m_abyMask.data(), nXSize,
248 197 : 1, GDT_Byte, 0, 0, nullptr) != CE_None)
249 : {
250 0 : return false;
251 : }
252 : }
253 : }
254 :
255 1028 : if (!m_abLineLoadedOnce[iY])
256 : {
257 382 : m_nCountLoadedOnce++;
258 : // Very rough progression report based on the first time
259 : // we load a line...
260 : // We arbitrarily consider that it's 90% of the processing time
261 382 : const int nYSize = m_poSrcDataset->GetRasterYSize();
262 382 : if (!(m_psOptions->pfnProgress(
263 : 0.9 *
264 382 : (m_nCountLoadedOnce / static_cast<double>(nYSize)),
265 382 : nullptr, m_psOptions->pProgressData)))
266 : {
267 0 : return false;
268 : }
269 382 : m_abLineLoadedOnce[iY] = true;
270 : }
271 : }
272 :
273 1067 : if (m_nLoadedLine >= 0)
274 : {
275 2056 : if (m_poVisitedDS->GetRasterBand(1)->RasterIO(
276 : GF_Write, 0, m_nLoadedLine, nXSize, 1,
277 1028 : m_abyLineMustSet.data(), nXSize, 1, GDT_Byte, 0, 0,
278 1028 : nullptr) != CE_None)
279 : {
280 0 : return false;
281 : }
282 : }
283 :
284 1067 : if (iY >= 0)
285 : {
286 2056 : if (m_poVisitedDS->GetRasterBand(1)->RasterIO(
287 1028 : GF_Read, 0, iY, nXSize, 1, m_abyLineMustSet.data(), nXSize,
288 1028 : 1, GDT_Byte, 0, 0, nullptr) != CE_None)
289 : {
290 0 : return false;
291 : }
292 : }
293 :
294 1067 : m_bLineModified = false;
295 1067 : m_nLoadedLine = iY;
296 : }
297 2623 : return true;
298 : }
299 :
300 : /************************************************************************/
301 : /* GDALNearblackFloodFillAlg::Set() */
302 : /************************************************************************/
303 :
304 : // Mark the pixel as transparent
305 6654 : void GDALNearblackFloodFillAlg::Set(int iX, int iY)
306 : {
307 6654 : CPLAssert(iY == m_nLoadedLine);
308 6654 : CPL_IGNORE_RET_VAL(iY);
309 :
310 6654 : m_bLineModified = true;
311 6654 : m_abyLineMustSet[iX] = MUST_FILL_FALSE;
312 :
313 26225 : for (int iBand = 0; iBand < m_nSrcBands; iBand++)
314 19571 : m_abyLine[iX * m_nDstBands + iBand] = m_nReplacevalue;
315 :
316 : /***** alpha *****/
317 6654 : if (m_nDstBands > m_nSrcBands)
318 1948 : m_abyLine[iX * m_nDstBands + m_nDstBands - 1] = 0;
319 :
320 6654 : if (m_bSetMask)
321 882 : m_abyMask[iX] = 0;
322 6654 : }
323 :
324 : /************************************************************************/
325 : /* GDALNearblackFloodFillAlg::Fill() */
326 : /************************************************************************/
327 :
328 : /* Implements the "final, combined-scan-and-fill span filler was then published
329 : * in 1990" algorithm of https://en.wikipedia.org/wiki/Flood_fill#Span_filling
330 : * with the following enhancements:
331 : * - extra bound checking to avoid calling MustSet() outside the raster
332 : * - extra bound checking to avoid pushing spans outside the raster
333 : *
334 : * Returns true if no error.
335 : */
336 :
337 2020 : bool GDALNearblackFloodFillAlg::Fill(int iXInit, int iYInit)
338 : {
339 2020 : const int nXSize = m_poSrcDataset->GetRasterXSize();
340 2020 : const int nYSize = m_poSrcDataset->GetRasterYSize();
341 :
342 : struct Span
343 : {
344 : int x1;
345 : int x2;
346 : int y;
347 : int dy;
348 :
349 564 : Span(int x1In, int x2In, int yIn, int dyIn)
350 564 : : x1(x1In), x2(x2In), y(yIn), dy(dyIn)
351 : {
352 564 : }
353 : };
354 :
355 2020 : if (!LoadLine(iYInit))
356 0 : return false;
357 :
358 2020 : if (!MustSet(iXInit, iYInit))
359 : {
360 : // nothing to do
361 1987 : return true;
362 : }
363 :
364 66 : std::queue<Span> queue;
365 33 : queue.emplace(Span(iXInit, iXInit, iYInit, 1));
366 33 : if (iYInit > 0)
367 : {
368 7 : queue.emplace(Span(iXInit, iXInit, iYInit - 1, -1));
369 : }
370 :
371 597 : while (!queue.empty())
372 : {
373 : #ifdef DEBUG
374 564 : m_nMaxQueueSize = std::max(m_nMaxQueueSize, queue.size());
375 : #endif
376 :
377 564 : const Span s = queue.front();
378 564 : queue.pop();
379 :
380 564 : CPLAssert(s.x1 >= 0);
381 564 : CPLAssert(s.x1 < nXSize);
382 564 : CPLAssert(s.x2 >= 0);
383 564 : CPLAssert(s.x2 < nXSize);
384 564 : CPLAssert(s.x2 >= s.x1);
385 564 : CPLAssert(s.y >= 0);
386 564 : CPLAssert(s.y < nYSize);
387 :
388 564 : int iX = s.x1;
389 564 : const int iY = s.y;
390 :
391 564 : if (!LoadLine(iY))
392 0 : return false;
393 :
394 564 : if (iX > 0 && MustSet(iX, iY))
395 : {
396 58 : while (MustSet(iX - 1, iY))
397 : {
398 11 : Set(iX - 1, iY);
399 11 : iX--;
400 11 : if (iX == 0)
401 0 : break;
402 : }
403 : }
404 564 : if (iX >= 0 && iX <= s.x1 - 1 && iY - s.dy >= 0 && iY - s.dy < nYSize)
405 : {
406 8 : queue.emplace(Span(iX, s.x1 - 1, iY - s.dy, -s.dy));
407 : }
408 564 : int iX1 = s.x1;
409 564 : const int iX2 = s.x2;
410 1247 : while (iX1 <= iX2)
411 : {
412 7251 : while (MustSet(iX1, iY))
413 : {
414 6643 : Set(iX1, iY);
415 6643 : iX1++;
416 6643 : if (iX1 == nXSize)
417 75 : break;
418 : }
419 683 : if (iX <= iX1 - 1 && iY + s.dy >= 0 && iY + s.dy < nYSize)
420 : {
421 445 : queue.emplace(Span(iX, iX1 - 1, iY + s.dy, s.dy));
422 : }
423 683 : if (iX1 - 1 > iX2 && iY - s.dy >= 0 && iY - s.dy < nYSize)
424 : {
425 71 : queue.emplace(Span(iX2 + 1, iX1 - 1, iY - s.dy, -s.dy));
426 : }
427 683 : iX1++;
428 848 : while (iX1 < iX2 && !MustSet(iX1, iY))
429 165 : iX1++;
430 683 : iX = iX1;
431 : }
432 : }
433 :
434 33 : return true;
435 : }
436 :
437 : /************************************************************************/
438 : /* GDALNearblackFloodFillAlg::Process() */
439 : /************************************************************************/
440 :
441 : // Entry point.
442 : // Returns true if no error.
443 :
444 39 : bool GDALNearblackFloodFillAlg::Process()
445 : {
446 39 : const int nXSize = m_poSrcDataset->GetRasterXSize();
447 39 : const int nYSize = m_poSrcDataset->GetRasterYSize();
448 :
449 : /* -------------------------------------------------------------------- */
450 : /* Allocate working buffers. */
451 : /* -------------------------------------------------------------------- */
452 : try
453 : {
454 39 : m_abyLine.resize(static_cast<size_t>(nXSize) * m_nDstBands);
455 39 : m_abyLineMustSet.resize(nXSize);
456 39 : if (m_bSetMask)
457 21 : m_abyMask.resize(nXSize);
458 :
459 39 : if (m_psOptions->nMaxNonBlack > 0)
460 : {
461 23 : m_abLineLoadedOnce.resize(nYSize, true);
462 23 : m_abLineSavedOnce.resize(nYSize, true);
463 : }
464 : else
465 : {
466 16 : m_abLineLoadedOnce.resize(nYSize);
467 16 : m_abLineSavedOnce.resize(nYSize);
468 : }
469 : }
470 0 : catch (const std::exception &e)
471 : {
472 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
473 0 : "Cannot allocate working buffers: %s", e.what());
474 0 : return false;
475 : }
476 :
477 : /* -------------------------------------------------------------------- */
478 : /* Create a temporary dataset to save visited state */
479 : /* -------------------------------------------------------------------- */
480 :
481 : // For debugging / testing purposes only
482 : const char *pszTmpDriver =
483 39 : CPLGetConfigOption("GDAL_TEMP_DRIVER_NAME", nullptr);
484 39 : if (!pszTmpDriver)
485 : {
486 39 : pszTmpDriver =
487 39 : (nXSize < 100 * 1024 * 1024 / nYSize ||
488 0 : (m_poDstDS->GetDriver() &&
489 0 : strcmp(m_poDstDS->GetDriver()->GetDescription(), "MEM") == 0))
490 39 : ? "MEM"
491 : : "GTiff";
492 : }
493 39 : GDALDriverH hDriver = GDALGetDriverByName(pszTmpDriver);
494 39 : if (!hDriver)
495 : {
496 0 : CPLError(CE_Failure, CPLE_AppDefined,
497 : "Cannot find driver %s for temporary file", pszTmpDriver);
498 0 : return false;
499 : }
500 78 : std::string osVisitedDataset = m_poDstDS->GetDescription();
501 : VSIStatBuf sStat;
502 78 : if (strcmp(pszTmpDriver, "MEM") == 0 ||
503 39 : STARTS_WITH(osVisitedDataset.c_str(), "/vsimem/") ||
504 : // Regular VSIStat() (not VSIStatL()) intended to check this is
505 : // a real file
506 0 : VSIStat(osVisitedDataset.c_str(), &sStat) == 0)
507 : {
508 39 : osVisitedDataset += ".visited";
509 : }
510 : else
511 : {
512 : osVisitedDataset =
513 0 : CPLGenerateTempFilenameSafe(osVisitedDataset.c_str());
514 : }
515 78 : CPLStringList aosOptions;
516 39 : if (strcmp(pszTmpDriver, "GTiff") == 0)
517 : {
518 0 : aosOptions.SetNameValue("SPARSE_OK", "YES");
519 0 : aosOptions.SetNameValue("COMPRESS", "LZW");
520 0 : osVisitedDataset += ".tif";
521 : }
522 39 : m_poVisitedDS.reset(GDALDataset::FromHandle(
523 : GDALCreate(hDriver, osVisitedDataset.c_str(), nXSize, nYSize, 1,
524 39 : GDT_Byte, aosOptions.List())));
525 39 : if (!m_poVisitedDS)
526 0 : return false;
527 39 : if (strcmp(pszTmpDriver, "MEM") != 0)
528 : {
529 0 : VSIUnlink(osVisitedDataset.c_str());
530 : }
531 39 : m_poVisitedDS->MarkSuppressOnClose();
532 :
533 : /* -------------------------------------------------------------------- */
534 : /* Iterate over the border of the raster */
535 : /* -------------------------------------------------------------------- */
536 : // Fill from top line
537 586 : for (int iX = 0; iX < nXSize; iX++)
538 : {
539 547 : if (!Fill(iX, 0))
540 0 : return false;
541 : }
542 :
543 : // Fill from left and right side
544 502 : for (int iY = 1; iY < nYSize - 1; iY++)
545 : {
546 463 : if (!Fill(0, iY))
547 0 : return false;
548 463 : if (!Fill(nXSize - 1, iY))
549 0 : return false;
550 : }
551 :
552 : // Fill from bottom line
553 586 : for (int iX = 0; iX < nXSize; iX++)
554 : {
555 547 : if (!Fill(iX, nYSize - 1))
556 0 : return false;
557 : }
558 :
559 39 : if (!(m_psOptions->pfnProgress(1.0, nullptr, m_psOptions->pProgressData)))
560 : {
561 0 : return false;
562 : }
563 :
564 : #ifdef DEBUG
565 39 : CPLDebug("GDAL", "flood fill max queue size = %u",
566 39 : unsigned(m_nMaxQueueSize));
567 : #endif
568 :
569 : // Force update of last visited line
570 39 : return LoadLine(-1);
571 : }
572 :
573 : /************************************************************************/
574 : /* GDALNearblackFloodFill() */
575 : /************************************************************************/
576 :
577 : // Entry point.
578 : // Returns true if no error.
579 :
580 39 : bool GDALNearblackFloodFill(const GDALNearblackOptions *psOptions,
581 : GDALDatasetH hSrcDataset, GDALDatasetH hDstDS,
582 : GDALRasterBandH hMaskBand, int nSrcBands,
583 : int nDstBands, bool bSetMask, const Colors &oColors)
584 : {
585 78 : GDALNearblackFloodFillAlg alg;
586 39 : alg.m_psOptions = psOptions;
587 39 : alg.m_poSrcDataset = GDALDataset::FromHandle(hSrcDataset);
588 39 : alg.m_poDstDS = GDALDataset::FromHandle(hDstDS);
589 39 : alg.m_poMaskBand = GDALRasterBand::FromHandle(hMaskBand);
590 39 : alg.m_nSrcBands = nSrcBands;
591 39 : alg.m_nDstBands = nDstBands;
592 39 : alg.m_bSetMask = bSetMask;
593 39 : alg.m_oColors = oColors;
594 39 : alg.m_nReplacevalue = psOptions->bNearWhite ? 255 : 0;
595 :
596 39 : if (psOptions->nMaxNonBlack > 0)
597 : {
598 : // First pass: use the TwoPasses algorithm to deal with nMaxNonBlack
599 46 : GDALNearblackOptions sOptionsTmp(*psOptions);
600 46 : sOptionsTmp.pProgressData = GDALCreateScaledProgress(
601 23 : 0, 0.5, psOptions->pfnProgress, psOptions->pProgressData);
602 23 : sOptionsTmp.pfnProgress = GDALScaledProgress;
603 23 : bool bRet = GDALNearblackTwoPassesAlgorithm(
604 : &sOptionsTmp, hSrcDataset, hDstDS, hMaskBand, nSrcBands, nDstBands,
605 : bSetMask, oColors);
606 23 : GDALDestroyScaledProgress(sOptionsTmp.pProgressData);
607 23 : if (!bRet)
608 0 : return false;
609 :
610 : // Second pass: use flood fill
611 46 : sOptionsTmp.pProgressData = GDALCreateScaledProgress(
612 23 : 0.5, 1, psOptions->pfnProgress, psOptions->pProgressData);
613 23 : sOptionsTmp.pfnProgress = GDALScaledProgress;
614 23 : alg.m_psOptions = &sOptionsTmp;
615 23 : bRet = alg.Process();
616 23 : GDALDestroyScaledProgress(sOptionsTmp.pProgressData);
617 23 : return bRet;
618 : }
619 : else
620 : {
621 16 : return alg.Process();
622 : }
623 : }
|