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