LCOV - code coverage report
Current view: top level - apps - nearblack_lib_floodfill.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 211 243 86.8 %
Date: 2024-11-21 22:18:42 Functions: 7 7 100.0 %

          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        9281 : bool GDALNearblackFloodFillAlg::MustSet(int iX, int iY)
     101             : {
     102        9281 :     CPLAssert(iX >= 0);
     103        9281 :     CPLAssert(iX < m_poSrcDataset->GetRasterXSize());
     104             : 
     105        9281 :     CPLAssert(iY >= 0);
     106        9281 :     CPLAssert(iY < m_poSrcDataset->GetRasterYSize());
     107        9281 :     CPLAssert(iY == m_nLoadedLine);
     108        9281 :     CPL_IGNORE_RET_VAL(iY);
     109             : 
     110        9281 :     if (m_abyLineMustSet[iX] != MUST_FILL_UNINIT)
     111             :     {
     112        1300 :         return m_abyLineMustSet[iX] == MUST_FILL_TRUE;
     113             :     }
     114             : 
     115             :     /***** loop over the colors *****/
     116             : 
     117       10965 :     for (int iColor = 0; iColor < static_cast<int>(m_oColors.size()); iColor++)
     118             :     {
     119        9617 :         const Color &oColor = m_oColors[iColor];
     120             : 
     121             :         /***** loop over the bands *****/
     122        9617 :         bool bIsNonBlack = false;
     123             : 
     124       29267 :         for (int iBand = 0; iBand < m_nSrcBands; iBand++)
     125             :         {
     126       22634 :             const int nPix = m_abyLine[iX * m_nDstBands + iBand];
     127             : 
     128       45004 :             if (oColor[iBand] - nPix > m_psOptions->nNearDist ||
     129       22370 :                 nPix > m_psOptions->nNearDist + oColor[iBand])
     130             :             {
     131        2984 :                 bIsNonBlack = true;
     132        2984 :                 break;
     133             :             }
     134             :         }
     135             : 
     136        9617 :         if (!bIsNonBlack)
     137             :         {
     138        6633 :             m_abyLineMustSet[iX] = MUST_FILL_TRUE;
     139        6633 :             return true;
     140             :         }
     141             :     }
     142             : 
     143        1348 :     m_abyLineMustSet[iX] = MUST_FILL_FALSE;
     144        1348 :     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        2222 : bool GDALNearblackFloodFillAlg::LoadLine(int iY)
     155             : {
     156        2222 :     if (iY != m_nLoadedLine)
     157             :     {
     158             : #ifdef DEBUG
     159             :         // CPLDebug("GDAL", "GDALNearblackFloodFillAlg::LoadLine(%d)", iY);
     160             : #endif
     161         953 :         const int nXSize = m_poSrcDataset->GetRasterXSize();
     162             : 
     163         953 :         if (m_nLoadedLine >= 0)
     164             :         {
     165        1396 :             if (m_bLineModified || (m_poDstDS != m_poSrcDataset &&
     166        1396 :                                     !m_abLineSavedOnce[m_nLoadedLine]))
     167             :             {
     168         423 :                 if (m_poDstDS->RasterIO(
     169         423 :                         GF_Write, 0, m_nLoadedLine, nXSize, 1, m_abyLine.data(),
     170         423 :                         nXSize, 1, GDT_Byte, m_nDstBands, nullptr, m_nDstBands,
     171         423 :                         static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
     172         423 :                         nullptr) != CE_None)
     173             :                 {
     174           0 :                     return false;
     175             :                 }
     176             :             }
     177             : 
     178        1198 :             if (m_bSetMask &&
     179        1198 :                 (m_bLineModified || !m_abLineSavedOnce[m_nLoadedLine]))
     180             :             {
     181         118 :                 if (m_poMaskBand->RasterIO(GF_Write, 0, m_nLoadedLine, nXSize,
     182         118 :                                            1, m_abyMask.data(), nXSize, 1,
     183         118 :                                            GDT_Byte, 0, 0, nullptr) != CE_None)
     184             :                 {
     185           0 :                     return false;
     186             :                 }
     187             :             }
     188             : 
     189         929 :             m_abLineSavedOnce[m_nLoadedLine] = true;
     190             :         }
     191             : 
     192         953 :         if (iY >= 0)
     193             :         {
     194         929 :             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         497 :                 if (m_poDstDS->RasterIO(
     200         497 :                         GF_Read, 0, iY, nXSize, 1, m_abyLine.data(), nXSize, 1,
     201         497 :                         GDT_Byte, m_nDstBands, nullptr, m_nDstBands,
     202         497 :                         static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
     203         497 :                         nullptr) != CE_None)
     204             :                 {
     205           0 :                     return false;
     206             :                 }
     207             :             }
     208             :             else
     209             :             {
     210             :                 // Otherwise load from the source data
     211         432 :                 if (m_poSrcDataset->RasterIO(
     212         432 :                         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         432 :                         nullptr, m_nDstBands,
     218         432 :                         static_cast<GSpacing>(nXSize) * m_nDstBands, 1,
     219         432 :                         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         432 :                 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         929 :             if (m_bSetMask)
     236             :             {
     237         269 :                 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         194 :                     if (m_poMaskBand->RasterIO(
     247         194 :                             GF_Read, 0, iY, nXSize, 1, m_abyMask.data(), nXSize,
     248         194 :                             1, GDT_Byte, 0, 0, nullptr) != CE_None)
     249             :                     {
     250           0 :                         return false;
     251             :                     }
     252             :                 }
     253             :             }
     254             : 
     255         929 :             if (!m_abLineLoadedOnce[iY])
     256             :             {
     257         375 :                 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         375 :                 const int nYSize = m_poSrcDataset->GetRasterYSize();
     262         375 :                 if (!(m_psOptions->pfnProgress(
     263             :                         0.9 *
     264         375 :                             (m_nCountLoadedOnce / static_cast<double>(nYSize)),
     265         375 :                         nullptr, m_psOptions->pProgressData)))
     266             :                 {
     267           0 :                     return false;
     268             :                 }
     269         375 :                 m_abLineLoadedOnce[iY] = true;
     270             :             }
     271             :         }
     272             : 
     273         953 :         if (m_nLoadedLine >= 0)
     274             :         {
     275        1858 :             if (m_poVisitedDS->GetRasterBand(1)->RasterIO(
     276             :                     GF_Write, 0, m_nLoadedLine, nXSize, 1,
     277         929 :                     m_abyLineMustSet.data(), nXSize, 1, GDT_Byte, 0, 0,
     278         929 :                     nullptr) != CE_None)
     279             :             {
     280           0 :                 return false;
     281             :             }
     282             :         }
     283             : 
     284         953 :         if (iY >= 0)
     285             :         {
     286        1858 :             if (m_poVisitedDS->GetRasterBand(1)->RasterIO(
     287         929 :                     GF_Read, 0, iY, nXSize, 1, m_abyLineMustSet.data(), nXSize,
     288         929 :                     1, GDT_Byte, 0, 0, nullptr) != CE_None)
     289             :             {
     290           0 :                 return false;
     291             :             }
     292             :         }
     293             : 
     294         953 :         m_bLineModified = false;
     295         953 :         m_nLoadedLine = iY;
     296             :     }
     297        2222 :     return true;
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*              GDALNearblackFloodFillAlg::Set()                        */
     302             : /************************************************************************/
     303             : 
     304             : // Mark the pixel as transparent
     305        6633 : void GDALNearblackFloodFillAlg::Set(int iX, int iY)
     306             : {
     307        6633 :     CPLAssert(iY == m_nLoadedLine);
     308        6633 :     CPL_IGNORE_RET_VAL(iY);
     309             : 
     310        6633 :     m_bLineModified = true;
     311        6633 :     m_abyLineMustSet[iX] = MUST_FILL_FALSE;
     312             : 
     313       26182 :     for (int iBand = 0; iBand < m_nSrcBands; iBand++)
     314       19549 :         m_abyLine[iX * m_nDstBands + iBand] = m_nReplacevalue;
     315             : 
     316             :     /***** alpha *****/
     317        6633 :     if (m_nDstBands > m_nSrcBands)
     318        1946 :         m_abyLine[iX * m_nDstBands + m_nDstBands - 1] = 0;
     319             : 
     320        6633 :     if (m_bSetMask)
     321         879 :         m_abyMask[iX] = 0;
     322        6633 : }
     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        1652 : bool GDALNearblackFloodFillAlg::Fill(int iXInit, int iYInit)
     338             : {
     339        1652 :     const int nXSize = m_poSrcDataset->GetRasterXSize();
     340        1652 :     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         546 :         Span(int x1In, int x2In, int yIn, int dyIn)
     350         546 :             : x1(x1In), x2(x2In), y(yIn), dy(dyIn)
     351             :         {
     352         546 :         }
     353             :     };
     354             : 
     355        1652 :     if (!LoadLine(iYInit))
     356           0 :         return false;
     357             : 
     358        1652 :     if (!MustSet(iXInit, iYInit))
     359             :     {
     360             :         // nothing to do
     361        1632 :         return true;
     362             :     }
     363             : 
     364          40 :     std::queue<Span> queue;
     365          20 :     queue.emplace(Span(iXInit, iXInit, iYInit, 1));
     366          20 :     if (iYInit > 0)
     367             :     {
     368           7 :         queue.emplace(Span(iXInit, iXInit, iYInit - 1, -1));
     369             :     }
     370             : 
     371         566 :     while (!queue.empty())
     372             :     {
     373             : #ifdef DEBUG
     374         546 :         m_nMaxQueueSize = std::max(m_nMaxQueueSize, queue.size());
     375             : #endif
     376             : 
     377         546 :         const Span s = queue.front();
     378         546 :         queue.pop();
     379             : 
     380         546 :         CPLAssert(s.x1 >= 0);
     381         546 :         CPLAssert(s.x1 < nXSize);
     382         546 :         CPLAssert(s.x2 >= 0);
     383         546 :         CPLAssert(s.x2 < nXSize);
     384         546 :         CPLAssert(s.x2 >= s.x1);
     385         546 :         CPLAssert(s.y >= 0);
     386         546 :         CPLAssert(s.y < nYSize);
     387             : 
     388         546 :         int iX = s.x1;
     389         546 :         const int iY = s.y;
     390             : 
     391         546 :         if (!LoadLine(iY))
     392           0 :             return false;
     393             : 
     394         546 :         if (iX > 0 && MustSet(iX, iY))
     395             :         {
     396          56 :             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         546 :         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         546 :         int iX1 = s.x1;
     409         546 :         const int iX2 = s.x2;
     410        1209 :         while (iX1 <= iX2)
     411             :         {
     412        7212 :             while (MustSet(iX1, iY))
     413             :             {
     414        6622 :                 Set(iX1, iY);
     415        6622 :                 iX1++;
     416        6622 :                 if (iX1 == nXSize)
     417          73 :                     break;
     418             :             }
     419         663 :             if (iX <= iX1 - 1 && iY + s.dy >= 0 && iY + s.dy < nYSize)
     420             :             {
     421         441 :                 queue.emplace(Span(iX, iX1 - 1, iY + s.dy, s.dy));
     422             :             }
     423         663 :             if (iX1 - 1 > iX2 && iY - s.dy >= 0 && iY - s.dy < nYSize)
     424             :             {
     425          70 :                 queue.emplace(Span(iX2 + 1, iX1 - 1, iY - s.dy, -s.dy));
     426             :             }
     427         663 :             iX1++;
     428         828 :             while (iX1 < iX2 && !MustSet(iX1, iY))
     429         165 :                 iX1++;
     430         663 :             iX = iX1;
     431             :         }
     432             :     }
     433             : 
     434          20 :     return true;
     435             : }
     436             : 
     437             : /************************************************************************/
     438             : /*              GDALNearblackFloodFillAlg::Process()                    */
     439             : /************************************************************************/
     440             : 
     441             : // Entry point.
     442             : // Returns true if no error.
     443             : 
     444          24 : bool GDALNearblackFloodFillAlg::Process()
     445             : {
     446          24 :     const int nXSize = m_poSrcDataset->GetRasterXSize();
     447          24 :     const int nYSize = m_poSrcDataset->GetRasterYSize();
     448             : 
     449             :     /* -------------------------------------------------------------------- */
     450             :     /*      Allocate working buffers.                                       */
     451             :     /* -------------------------------------------------------------------- */
     452             :     try
     453             :     {
     454          24 :         m_abyLine.resize(static_cast<size_t>(nXSize) * m_nDstBands);
     455          24 :         m_abyLineMustSet.resize(nXSize);
     456          24 :         if (m_bSetMask)
     457          18 :             m_abyMask.resize(nXSize);
     458             : 
     459          24 :         if (m_psOptions->nMaxNonBlack > 0)
     460             :         {
     461          12 :             m_abLineLoadedOnce.resize(nYSize, true);
     462          12 :             m_abLineSavedOnce.resize(nYSize, true);
     463             :         }
     464             :         else
     465             :         {
     466          12 :             m_abLineLoadedOnce.resize(nYSize);
     467          12 :             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          24 :         CPLGetConfigOption("GDAL_TEMP_DRIVER_NAME", nullptr);
     484          24 :     if (!pszTmpDriver)
     485             :     {
     486          24 :         pszTmpDriver =
     487          24 :             (nXSize < 100 * 1024 * 1024 / nYSize ||
     488           0 :              (m_poDstDS->GetDriver() &&
     489           0 :               strcmp(m_poDstDS->GetDriver()->GetDescription(), "MEM") == 0))
     490          24 :                 ? "MEM"
     491             :                 : "GTiff";
     492             :     }
     493          24 :     GDALDriverH hDriver = GDALGetDriverByName(pszTmpDriver);
     494          24 :     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          48 :     std::string osVisitedDataset = m_poDstDS->GetDescription();
     501             :     VSIStatBuf sStat;
     502          48 :     if (strcmp(pszTmpDriver, "MEM") == 0 ||
     503          24 :         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          24 :         osVisitedDataset += ".visited";
     509             :     }
     510             :     else
     511             :     {
     512           0 :         osVisitedDataset = CPLGenerateTempFilename(osVisitedDataset.c_str());
     513             :     }
     514          48 :     CPLStringList aosOptions;
     515          24 :     if (strcmp(pszTmpDriver, "GTiff") == 0)
     516             :     {
     517           0 :         aosOptions.SetNameValue("SPARSE_OK", "YES");
     518           0 :         aosOptions.SetNameValue("COMPRESS", "LZW");
     519           0 :         osVisitedDataset += ".tif";
     520             :     }
     521          24 :     m_poVisitedDS.reset(GDALDataset::FromHandle(
     522             :         GDALCreate(hDriver, osVisitedDataset.c_str(), nXSize, nYSize, 1,
     523          24 :                    GDT_Byte, aosOptions.List())));
     524          24 :     if (!m_poVisitedDS)
     525           0 :         return false;
     526          24 :     if (strcmp(pszTmpDriver, "MEM") != 0)
     527             :     {
     528           0 :         VSIUnlink(osVisitedDataset.c_str());
     529             :     }
     530          24 :     m_poVisitedDS->MarkSuppressOnClose();
     531             : 
     532             :     /* -------------------------------------------------------------------- */
     533             :     /*      Iterate over the border of the raster                           */
     534             :     /* -------------------------------------------------------------------- */
     535             :     // Fill from top line
     536         461 :     for (int iX = 0; iX < nXSize; iX++)
     537             :     {
     538         437 :         if (!Fill(iX, 0))
     539           0 :             return false;
     540             :     }
     541             : 
     542             :     // Fill from left and right side
     543         413 :     for (int iY = 1; iY < nYSize - 1; iY++)
     544             :     {
     545         389 :         if (!Fill(0, iY))
     546           0 :             return false;
     547         389 :         if (!Fill(nXSize - 1, iY))
     548           0 :             return false;
     549             :     }
     550             : 
     551             :     // Fill from bottom line
     552         461 :     for (int iX = 0; iX < nXSize; iX++)
     553             :     {
     554         437 :         if (!Fill(iX, nYSize - 1))
     555           0 :             return false;
     556             :     }
     557             : 
     558          24 :     if (!(m_psOptions->pfnProgress(1.0, nullptr, m_psOptions->pProgressData)))
     559             :     {
     560           0 :         return false;
     561             :     }
     562             : 
     563             : #ifdef DEBUG
     564          24 :     CPLDebug("GDAL", "flood fill max queue size = %u",
     565          24 :              unsigned(m_nMaxQueueSize));
     566             : #endif
     567             : 
     568             :     // Force update of last visited line
     569          24 :     return LoadLine(-1);
     570             : }
     571             : 
     572             : /************************************************************************/
     573             : /*                    GDALNearblackFloodFill()                          */
     574             : /************************************************************************/
     575             : 
     576             : // Entry point.
     577             : // Returns true if no error.
     578             : 
     579          24 : bool GDALNearblackFloodFill(const GDALNearblackOptions *psOptions,
     580             :                             GDALDatasetH hSrcDataset, GDALDatasetH hDstDS,
     581             :                             GDALRasterBandH hMaskBand, int nSrcBands,
     582             :                             int nDstBands, bool bSetMask, const Colors &oColors)
     583             : {
     584          48 :     GDALNearblackFloodFillAlg alg;
     585          24 :     alg.m_psOptions = psOptions;
     586          24 :     alg.m_poSrcDataset = GDALDataset::FromHandle(hSrcDataset);
     587          24 :     alg.m_poDstDS = GDALDataset::FromHandle(hDstDS);
     588          24 :     alg.m_poMaskBand = GDALRasterBand::FromHandle(hMaskBand);
     589          24 :     alg.m_nSrcBands = nSrcBands;
     590          24 :     alg.m_nDstBands = nDstBands;
     591          24 :     alg.m_bSetMask = bSetMask;
     592          24 :     alg.m_oColors = oColors;
     593          24 :     alg.m_nReplacevalue = psOptions->bNearWhite ? 255 : 0;
     594             : 
     595          24 :     if (psOptions->nMaxNonBlack > 0)
     596             :     {
     597             :         // First pass: use the TwoPasses algorithm to deal with nMaxNonBlack
     598          24 :         GDALNearblackOptions sOptionsTmp(*psOptions);
     599          24 :         sOptionsTmp.pProgressData = GDALCreateScaledProgress(
     600          12 :             0, 0.5, psOptions->pfnProgress, psOptions->pProgressData);
     601          12 :         sOptionsTmp.pfnProgress = GDALScaledProgress;
     602          12 :         bool bRet = GDALNearblackTwoPassesAlgorithm(
     603             :             &sOptionsTmp, hSrcDataset, hDstDS, hMaskBand, nSrcBands, nDstBands,
     604             :             bSetMask, oColors);
     605          12 :         GDALDestroyScaledProgress(sOptionsTmp.pProgressData);
     606          12 :         if (!bRet)
     607           0 :             return false;
     608             : 
     609             :         // Second pass: use flood fill
     610          24 :         sOptionsTmp.pProgressData = GDALCreateScaledProgress(
     611          12 :             0.5, 1, psOptions->pfnProgress, psOptions->pProgressData);
     612          12 :         sOptionsTmp.pfnProgress = GDALScaledProgress;
     613          12 :         alg.m_psOptions = &sOptionsTmp;
     614          12 :         bRet = alg.Process();
     615          12 :         GDALDestroyScaledProgress(sOptionsTmp.pProgressData);
     616          12 :         return bRet;
     617             :     }
     618             :     else
     619             :     {
     620          12 :         return alg.Process();
     621             :     }
     622             : }

Generated by: LCOV version 1.14