Coverage Report

Created: 2026-02-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/nearblack_lib_floodfill.cpp
Line
Count
Source
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
bool GDALNearblackFloodFillAlg::MustSet(int iX, int iY)
101
0
{
102
0
    CPLAssert(iX >= 0);
103
0
    CPLAssert(iX < m_poSrcDataset->GetRasterXSize());
104
105
0
    CPLAssert(iY >= 0);
106
0
    CPLAssert(iY < m_poSrcDataset->GetRasterYSize());
107
0
    CPLAssert(iY == m_nLoadedLine);
108
0
    CPL_IGNORE_RET_VAL(iY);
109
110
0
    if (m_abyLineMustSet[iX] != MUST_FILL_UNINIT)
111
0
    {
112
0
        return m_abyLineMustSet[iX] == MUST_FILL_TRUE;
113
0
    }
114
115
    /***** loop over the colors *****/
116
117
0
    for (int iColor = 0; iColor < static_cast<int>(m_oColors.size()); iColor++)
118
0
    {
119
0
        const Color &oColor = m_oColors[iColor];
120
121
        /***** loop over the bands *****/
122
0
        bool bIsNonBlack = false;
123
124
0
        for (int iBand = 0; iBand < m_nSrcBands; iBand++)
125
0
        {
126
0
            const int nPix = m_abyLine[iX * m_nDstBands + iBand];
127
128
0
            if (oColor[iBand] - nPix > m_psOptions->nNearDist ||
129
0
                nPix > m_psOptions->nNearDist + oColor[iBand])
130
0
            {
131
0
                bIsNonBlack = true;
132
0
                break;
133
0
            }
134
0
        }
135
136
0
        if (!bIsNonBlack)
137
0
        {
138
0
            m_abyLineMustSet[iX] = MUST_FILL_TRUE;
139
0
            return true;
140
0
        }
141
0
    }
142
143
0
    m_abyLineMustSet[iX] = MUST_FILL_FALSE;
144
0
    return false;
145
0
}
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
bool GDALNearblackFloodFillAlg::LoadLine(int iY)
155
0
{
156
0
    if (iY != m_nLoadedLine)
157
0
    {
158
0
#ifdef DEBUG
159
        // CPLDebug("GDAL", "GDALNearblackFloodFillAlg::LoadLine(%d)", iY);
160
0
#endif
161
0
        const int nXSize = m_poSrcDataset->GetRasterXSize();
162
163
0
        if (m_nLoadedLine >= 0)
164
0
        {
165
0
            if (m_bLineModified || (m_poDstDS != m_poSrcDataset &&
166
0
                                    !m_abLineSavedOnce[m_nLoadedLine]))
167
0
            {
168
0
                if (m_poDstDS->RasterIO(
169
0
                        GF_Write, 0, m_nLoadedLine, nXSize, 1, m_abyLine.data(),
170
0
                        nXSize, 1, GDT_UInt8, m_nDstBands, nullptr, m_nDstBands,
171
0
                        static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
172
0
                        nullptr) != CE_None)
173
0
                {
174
0
                    return false;
175
0
                }
176
0
            }
177
178
0
            if (m_bSetMask &&
179
0
                (m_bLineModified || !m_abLineSavedOnce[m_nLoadedLine]))
180
0
            {
181
0
                if (m_poMaskBand->RasterIO(GF_Write, 0, m_nLoadedLine, nXSize,
182
0
                                           1, m_abyMask.data(), nXSize, 1,
183
0
                                           GDT_UInt8, 0, 0, nullptr) != CE_None)
184
0
                {
185
0
                    return false;
186
0
                }
187
0
            }
188
189
0
            m_abLineSavedOnce[m_nLoadedLine] = true;
190
0
        }
191
192
0
        if (iY >= 0)
193
0
        {
194
0
            if (m_poDstDS != m_poSrcDataset && m_abLineSavedOnce[iY])
195
0
            {
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
0
                if (m_poDstDS->RasterIO(
200
0
                        GF_Read, 0, iY, nXSize, 1, m_abyLine.data(), nXSize, 1,
201
0
                        GDT_UInt8, m_nDstBands, nullptr, m_nDstBands,
202
0
                        static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
203
0
                        nullptr) != CE_None)
204
0
                {
205
0
                    return false;
206
0
                }
207
0
            }
208
0
            else
209
0
            {
210
                // Otherwise load from the source data
211
0
                if (m_poSrcDataset->RasterIO(
212
0
                        GF_Read, 0, iY, nXSize, 1, m_abyLine.data(), nXSize, 1,
213
0
                        GDT_UInt8,
214
                        // m_nSrcBands intended
215
0
                        m_nSrcBands,
216
                        // m_nDstBands intended
217
0
                        nullptr, m_nDstBands,
218
0
                        static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
219
0
                        nullptr) != CE_None)
220
0
                {
221
0
                    return false;
222
0
                }
223
224
                // Initialize the alpha component to 255 if it is the first time
225
                // we load that line.
226
0
                if (m_psOptions->bSetAlpha && !m_abLineLoadedOnce[iY])
227
0
                {
228
0
                    for (int iCol = 0; iCol < nXSize; iCol++)
229
0
                    {
230
0
                        m_abyLine[iCol * m_nDstBands + m_nDstBands - 1] = 255;
231
0
                    }
232
0
                }
233
0
            }
234
235
0
            if (m_bSetMask)
236
0
            {
237
0
                if (!m_abLineLoadedOnce[iY])
238
0
                {
239
0
                    for (int iCol = 0; iCol < nXSize; iCol++)
240
0
                    {
241
0
                        m_abyMask[iCol] = 255;
242
0
                    }
243
0
                }
244
0
                else
245
0
                {
246
0
                    if (m_poMaskBand->RasterIO(
247
0
                            GF_Read, 0, iY, nXSize, 1, m_abyMask.data(), nXSize,
248
0
                            1, GDT_UInt8, 0, 0, nullptr) != CE_None)
249
0
                    {
250
0
                        return false;
251
0
                    }
252
0
                }
253
0
            }
254
255
0
            if (!m_abLineLoadedOnce[iY])
256
0
            {
257
0
                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
0
                const int nYSize = m_poSrcDataset->GetRasterYSize();
262
0
                if (!(m_psOptions->pfnProgress(
263
0
                        0.9 *
264
0
                            (m_nCountLoadedOnce / static_cast<double>(nYSize)),
265
0
                        nullptr, m_psOptions->pProgressData)))
266
0
                {
267
0
                    return false;
268
0
                }
269
0
                m_abLineLoadedOnce[iY] = true;
270
0
            }
271
0
        }
272
273
0
        if (m_nLoadedLine >= 0)
274
0
        {
275
0
            if (m_poVisitedDS->GetRasterBand(1)->RasterIO(
276
0
                    GF_Write, 0, m_nLoadedLine, nXSize, 1,
277
0
                    m_abyLineMustSet.data(), nXSize, 1, GDT_UInt8, 0, 0,
278
0
                    nullptr) != CE_None)
279
0
            {
280
0
                return false;
281
0
            }
282
0
        }
283
284
0
        if (iY >= 0)
285
0
        {
286
0
            if (m_poVisitedDS->GetRasterBand(1)->RasterIO(
287
0
                    GF_Read, 0, iY, nXSize, 1, m_abyLineMustSet.data(), nXSize,
288
0
                    1, GDT_UInt8, 0, 0, nullptr) != CE_None)
289
0
            {
290
0
                return false;
291
0
            }
292
0
        }
293
294
0
        m_bLineModified = false;
295
0
        m_nLoadedLine = iY;
296
0
    }
297
0
    return true;
298
0
}
299
300
/************************************************************************/
301
/*                   GDALNearblackFloodFillAlg::Set()                   */
302
/************************************************************************/
303
304
// Mark the pixel as transparent
305
void GDALNearblackFloodFillAlg::Set(int iX, int iY)
306
0
{
307
0
    CPLAssert(iY == m_nLoadedLine);
308
0
    CPL_IGNORE_RET_VAL(iY);
309
310
0
    m_bLineModified = true;
311
0
    m_abyLineMustSet[iX] = MUST_FILL_FALSE;
312
313
0
    for (int iBand = 0; iBand < m_nSrcBands; iBand++)
314
0
        m_abyLine[iX * m_nDstBands + iBand] = m_nReplacevalue;
315
316
    /***** alpha *****/
317
0
    if (m_nDstBands > m_nSrcBands)
318
0
        m_abyLine[iX * m_nDstBands + m_nDstBands - 1] = 0;
319
320
0
    if (m_bSetMask)
321
0
        m_abyMask[iX] = 0;
322
0
}
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
bool GDALNearblackFloodFillAlg::Fill(int iXInit, int iYInit)
338
0
{
339
0
    const int nXSize = m_poSrcDataset->GetRasterXSize();
340
0
    const int nYSize = m_poSrcDataset->GetRasterYSize();
341
342
0
    struct Span
343
0
    {
344
0
        int x1;
345
0
        int x2;
346
0
        int y;
347
0
        int dy;
348
349
0
        Span(int x1In, int x2In, int yIn, int dyIn)
350
0
            : x1(x1In), x2(x2In), y(yIn), dy(dyIn)
351
0
        {
352
0
        }
353
0
    };
354
355
0
    if (!LoadLine(iYInit))
356
0
        return false;
357
358
0
    if (!MustSet(iXInit, iYInit))
359
0
    {
360
        // nothing to do
361
0
        return true;
362
0
    }
363
364
0
    std::queue<Span> queue;
365
0
    queue.emplace(Span(iXInit, iXInit, iYInit, 1));
366
0
    if (iYInit > 0)
367
0
    {
368
0
        queue.emplace(Span(iXInit, iXInit, iYInit - 1, -1));
369
0
    }
370
371
0
    while (!queue.empty())
372
0
    {
373
0
#ifdef DEBUG
374
0
        m_nMaxQueueSize = std::max(m_nMaxQueueSize, queue.size());
375
0
#endif
376
377
0
        const Span s = queue.front();
378
0
        queue.pop();
379
380
0
        CPLAssert(s.x1 >= 0);
381
0
        CPLAssert(s.x1 < nXSize);
382
0
        CPLAssert(s.x2 >= 0);
383
0
        CPLAssert(s.x2 < nXSize);
384
0
        CPLAssert(s.x2 >= s.x1);
385
0
        CPLAssert(s.y >= 0);
386
0
        CPLAssert(s.y < nYSize);
387
388
0
        int iX = s.x1;
389
0
        const int iY = s.y;
390
391
0
        if (!LoadLine(iY))
392
0
            return false;
393
394
0
        if (iX > 0 && MustSet(iX, iY))
395
0
        {
396
0
            while (MustSet(iX - 1, iY))
397
0
            {
398
0
                Set(iX - 1, iY);
399
0
                iX--;
400
0
                if (iX == 0)
401
0
                    break;
402
0
            }
403
0
        }
404
0
        if (iX >= 0 && iX <= s.x1 - 1 && iY - s.dy >= 0 && iY - s.dy < nYSize)
405
0
        {
406
0
            queue.emplace(Span(iX, s.x1 - 1, iY - s.dy, -s.dy));
407
0
        }
408
0
        int iX1 = s.x1;
409
0
        const int iX2 = s.x2;
410
0
        while (iX1 <= iX2)
411
0
        {
412
0
            while (MustSet(iX1, iY))
413
0
            {
414
0
                Set(iX1, iY);
415
0
                iX1++;
416
0
                if (iX1 == nXSize)
417
0
                    break;
418
0
            }
419
0
            if (iX <= iX1 - 1 && iY + s.dy >= 0 && iY + s.dy < nYSize)
420
0
            {
421
0
                queue.emplace(Span(iX, iX1 - 1, iY + s.dy, s.dy));
422
0
            }
423
0
            if (iX1 - 1 > iX2 && iY - s.dy >= 0 && iY - s.dy < nYSize)
424
0
            {
425
0
                queue.emplace(Span(iX2 + 1, iX1 - 1, iY - s.dy, -s.dy));
426
0
            }
427
0
            iX1++;
428
0
            while (iX1 < iX2 && !MustSet(iX1, iY))
429
0
                iX1++;
430
0
            iX = iX1;
431
0
        }
432
0
    }
433
434
0
    return true;
435
0
}
436
437
/************************************************************************/
438
/*                 GDALNearblackFloodFillAlg::Process()                 */
439
/************************************************************************/
440
441
// Entry point.
442
// Returns true if no error.
443
444
bool GDALNearblackFloodFillAlg::Process()
445
0
{
446
0
    const int nXSize = m_poSrcDataset->GetRasterXSize();
447
0
    const int nYSize = m_poSrcDataset->GetRasterYSize();
448
449
    /* -------------------------------------------------------------------- */
450
    /*      Allocate working buffers.                                       */
451
    /* -------------------------------------------------------------------- */
452
0
    try
453
0
    {
454
0
        m_abyLine.resize(static_cast<size_t>(nXSize) * m_nDstBands);
455
0
        m_abyLineMustSet.resize(nXSize);
456
0
        if (m_bSetMask)
457
0
            m_abyMask.resize(nXSize);
458
459
0
        if (m_psOptions->nMaxNonBlack > 0)
460
0
        {
461
0
            m_abLineLoadedOnce.resize(nYSize, true);
462
0
            m_abLineSavedOnce.resize(nYSize, true);
463
0
        }
464
0
        else
465
0
        {
466
0
            m_abLineLoadedOnce.resize(nYSize);
467
0
            m_abLineSavedOnce.resize(nYSize);
468
0
        }
469
0
    }
470
0
    catch (const std::exception &e)
471
0
    {
472
0
        CPLError(CE_Failure, CPLE_OutOfMemory,
473
0
                 "Cannot allocate working buffers: %s", e.what());
474
0
        return false;
475
0
    }
476
477
    /* -------------------------------------------------------------------- */
478
    /*      Create a temporary dataset to save visited state                */
479
    /* -------------------------------------------------------------------- */
480
481
    // For debugging / testing purposes only
482
0
    const char *pszTmpDriver =
483
0
        CPLGetConfigOption("GDAL_TEMP_DRIVER_NAME", nullptr);
484
0
    if (!pszTmpDriver)
485
0
    {
486
0
        pszTmpDriver =
487
0
            (nXSize < 100 * 1024 * 1024 / nYSize ||
488
0
             (m_poDstDS->GetDriver() &&
489
0
              strcmp(m_poDstDS->GetDriver()->GetDescription(), "MEM") == 0))
490
0
                ? "MEM"
491
0
                : "GTiff";
492
0
    }
493
0
    GDALDriverH hDriver = GDALGetDriverByName(pszTmpDriver);
494
0
    if (!hDriver)
495
0
    {
496
0
        CPLError(CE_Failure, CPLE_AppDefined,
497
0
                 "Cannot find driver %s for temporary file", pszTmpDriver);
498
0
        return false;
499
0
    }
500
0
    std::string osVisitedDataset = m_poDstDS->GetDescription();
501
0
    VSIStatBuf sStat;
502
0
    if (strcmp(pszTmpDriver, "MEM") == 0 ||
503
0
        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
0
    {
508
0
        osVisitedDataset += ".visited";
509
0
    }
510
0
    else
511
0
    {
512
0
        osVisitedDataset =
513
0
            CPLGenerateTempFilenameSafe(osVisitedDataset.c_str());
514
0
    }
515
0
    CPLStringList aosOptions;
516
0
    if (strcmp(pszTmpDriver, "GTiff") == 0)
517
0
    {
518
0
        aosOptions.SetNameValue("SPARSE_OK", "YES");
519
0
        aosOptions.SetNameValue("COMPRESS", "LZW");
520
0
        osVisitedDataset += ".tif";
521
0
    }
522
0
    m_poVisitedDS.reset(GDALDataset::FromHandle(
523
0
        GDALCreate(hDriver, osVisitedDataset.c_str(), nXSize, nYSize, 1,
524
0
                   GDT_UInt8, aosOptions.List())));
525
0
    if (!m_poVisitedDS)
526
0
        return false;
527
0
    if (strcmp(pszTmpDriver, "MEM") != 0)
528
0
    {
529
0
        VSIUnlink(osVisitedDataset.c_str());
530
0
    }
531
0
    m_poVisitedDS->MarkSuppressOnClose();
532
533
    /* -------------------------------------------------------------------- */
534
    /*      Iterate over the border of the raster                           */
535
    /* -------------------------------------------------------------------- */
536
    // Fill from top line
537
0
    for (int iX = 0; iX < nXSize; iX++)
538
0
    {
539
0
        if (!Fill(iX, 0))
540
0
            return false;
541
0
    }
542
543
    // Fill from left and right side
544
0
    for (int iY = 1; iY < nYSize - 1; iY++)
545
0
    {
546
0
        if (!Fill(0, iY))
547
0
            return false;
548
0
        if (!Fill(nXSize - 1, iY))
549
0
            return false;
550
0
    }
551
552
    // Fill from bottom line
553
0
    for (int iX = 0; iX < nXSize; iX++)
554
0
    {
555
0
        if (!Fill(iX, nYSize - 1))
556
0
            return false;
557
0
    }
558
559
0
    if (!(m_psOptions->pfnProgress(1.0, nullptr, m_psOptions->pProgressData)))
560
0
    {
561
0
        return false;
562
0
    }
563
564
0
#ifdef DEBUG
565
0
    CPLDebug("GDAL", "flood fill max queue size = %u",
566
0
             unsigned(m_nMaxQueueSize));
567
0
#endif
568
569
    // Force update of last visited line
570
0
    return LoadLine(-1);
571
0
}
572
573
/************************************************************************/
574
/*                       GDALNearblackFloodFill()                       */
575
/************************************************************************/
576
577
// Entry point.
578
// Returns true if no error.
579
580
bool GDALNearblackFloodFill(const GDALNearblackOptions *psOptions,
581
                            GDALDatasetH hSrcDataset, GDALDatasetH hDstDS,
582
                            GDALRasterBandH hMaskBand, int nSrcBands,
583
                            int nDstBands, bool bSetMask, const Colors &oColors)
584
0
{
585
0
    GDALNearblackFloodFillAlg alg;
586
0
    alg.m_psOptions = psOptions;
587
0
    alg.m_poSrcDataset = GDALDataset::FromHandle(hSrcDataset);
588
0
    alg.m_poDstDS = GDALDataset::FromHandle(hDstDS);
589
0
    alg.m_poMaskBand = GDALRasterBand::FromHandle(hMaskBand);
590
0
    alg.m_nSrcBands = nSrcBands;
591
0
    alg.m_nDstBands = nDstBands;
592
0
    alg.m_bSetMask = bSetMask;
593
0
    alg.m_oColors = oColors;
594
0
    alg.m_nReplacevalue = psOptions->bNearWhite ? 255 : 0;
595
596
0
    if (psOptions->nMaxNonBlack > 0)
597
0
    {
598
        // First pass: use the TwoPasses algorithm to deal with nMaxNonBlack
599
0
        GDALNearblackOptions sOptionsTmp(*psOptions);
600
0
        sOptionsTmp.pProgressData = GDALCreateScaledProgress(
601
0
            0, 0.5, psOptions->pfnProgress, psOptions->pProgressData);
602
0
        sOptionsTmp.pfnProgress = GDALScaledProgress;
603
0
        bool bRet = GDALNearblackTwoPassesAlgorithm(
604
0
            &sOptionsTmp, hSrcDataset, hDstDS, hMaskBand, nSrcBands, nDstBands,
605
0
            bSetMask, oColors);
606
0
        GDALDestroyScaledProgress(sOptionsTmp.pProgressData);
607
0
        if (!bRet)
608
0
            return false;
609
610
        // Second pass: use flood fill
611
0
        sOptionsTmp.pProgressData = GDALCreateScaledProgress(
612
0
            0.5, 1, psOptions->pfnProgress, psOptions->pProgressData);
613
0
        sOptionsTmp.pfnProgress = GDALScaledProgress;
614
0
        alg.m_psOptions = &sOptionsTmp;
615
0
        bRet = alg.Process();
616
0
        GDALDestroyScaledProgress(sOptionsTmp.pProgressData);
617
0
        return bRet;
618
0
    }
619
0
    else
620
0
    {
621
0
        return alg.Process();
622
0
    }
623
0
}