LCOV - code coverage report
Current view: top level - apps - nearblack_lib.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 272 337 80.7 %
Date: 2024-11-21 22:18:42 Functions: 10 11 90.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL Utilities
       4             :  * Purpose:  Convert nearly black or nearly white border to exact black/white.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  * ****************************************************************************
       8             :  * Copyright (c) 2006, MapShots Inc (www.mapshots.com)
       9             :  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "gdal_utils.h"
      16             : #include "gdal_utils_priv.h"
      17             : #include "commonutils.h"
      18             : #include "gdalargumentparser.h"
      19             : 
      20             : #include <cassert>
      21             : #include <cstdlib>
      22             : #include <cstring>
      23             : 
      24             : #include <algorithm>
      25             : #include <memory>
      26             : #include <vector>
      27             : 
      28             : #include "cpl_conv.h"
      29             : #include "cpl_error.h"
      30             : #include "cpl_progress.h"
      31             : #include "cpl_string.h"
      32             : #include "gdal.h"
      33             : #include "gdal_priv.h"
      34             : 
      35             : #include "nearblack_lib.h"
      36             : 
      37             : static void ProcessLine(GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd,
      38             :                         int nSrcBands, int nDstBands, int nNearDist,
      39             :                         int nMaxNonBlack, bool bNearWhite,
      40             :                         const Colors &oColors, int *panLastLineCounts,
      41             :                         bool bDoHorizontalCheck, bool bDoVerticalCheck,
      42             :                         bool bBottomUp, int iLineFromTopOrBottom);
      43             : 
      44             : /************************************************************************/
      45             : /*                            GDALNearblack()                           */
      46             : /************************************************************************/
      47             : 
      48             : /* clang-format off */
      49             : /**
      50             :  * Convert nearly black/white borders to exact value.
      51             :  *
      52             :  * This is the equivalent of the
      53             :  * <a href="/programs/nearblack.html">nearblack</a> utility.
      54             :  *
      55             :  * GDALNearblackOptions* must be allocated and freed with
      56             :  * GDALNearblackOptionsNew() and GDALNearblackOptionsFree() respectively.
      57             :  * pszDest and hDstDS cannot be used at the same time.
      58             :  *
      59             :  * In-place update (i.e. hDstDS == hSrcDataset) is possible for formats that
      60             :  * support it, and if the dataset is opened in update mode.
      61             :  *
      62             :  * @param pszDest the destination dataset path or NULL.
      63             :  * @param hDstDS the destination dataset or NULL. Might be equal to hSrcDataset.
      64             :  * @param hSrcDataset the source dataset handle.
      65             :  * @param psOptionsIn the options struct returned by GDALNearblackOptionsNew()
      66             :  * or NULL.
      67             :  * @param pbUsageError pointer to a integer output variable to store if any
      68             :  * usage error has occurred or NULL.
      69             :  * @return the output dataset (new dataset that must be closed using
      70             :  * GDALClose(), or hDstDS when it is not NULL) or NULL in case of error.
      71             :  *
      72             :  * @since GDAL 2.1
      73             :  */
      74             : /* clang-format on */
      75             : 
      76          53 : GDALDatasetH CPL_DLL GDALNearblack(const char *pszDest, GDALDatasetH hDstDS,
      77             :                                    GDALDatasetH hSrcDataset,
      78             :                                    const GDALNearblackOptions *psOptionsIn,
      79             :                                    int *pbUsageError)
      80             : 
      81             : {
      82          53 :     if (pszDest == nullptr && hDstDS == nullptr)
      83             :     {
      84           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      85             :                  "pszDest == NULL && hDstDS == NULL");
      86             : 
      87           0 :         if (pbUsageError)
      88           0 :             *pbUsageError = TRUE;
      89           0 :         return nullptr;
      90             :     }
      91          53 :     if (hSrcDataset == nullptr)
      92             :     {
      93           0 :         CPLError(CE_Failure, CPLE_AppDefined, "hSrcDataset== NULL");
      94             : 
      95           0 :         if (pbUsageError)
      96           0 :             *pbUsageError = TRUE;
      97           0 :         return nullptr;
      98             :     }
      99             : 
     100             :     // to keep in that scope
     101          53 :     std::unique_ptr<GDALNearblackOptions> psTmpOptions;
     102          53 :     const GDALNearblackOptions *psOptions = psOptionsIn;
     103          53 :     if (!psOptionsIn)
     104             :     {
     105           0 :         psTmpOptions = std::make_unique<GDALNearblackOptions>();
     106           0 :         psOptions = psTmpOptions.get();
     107             :     }
     108             : 
     109          53 :     const bool bCloseOutDSOnError = hDstDS == nullptr;
     110          53 :     if (pszDest == nullptr)
     111           2 :         pszDest = GDALGetDescription(hDstDS);
     112             : 
     113          53 :     const int nXSize = GDALGetRasterXSize(hSrcDataset);
     114          53 :     const int nYSize = GDALGetRasterYSize(hSrcDataset);
     115          53 :     int nBands = GDALGetRasterCount(hSrcDataset);
     116          53 :     int nDstBands = nBands;
     117             : 
     118          53 :     const bool bNearWhite = psOptions->bNearWhite;
     119          53 :     const bool bSetAlpha = psOptions->bSetAlpha;
     120          53 :     bool bSetMask = psOptions->bSetMask;
     121         106 :     Colors oColors = psOptions->oColors;
     122             : 
     123             :     /* -------------------------------------------------------------------- */
     124             :     /*      Do we need to create output file?                               */
     125             :     /* -------------------------------------------------------------------- */
     126             : 
     127          53 :     if (hDstDS == nullptr)
     128             :     {
     129          48 :         CPLString osFormat;
     130          48 :         if (psOptions->osFormat.empty())
     131             :         {
     132           0 :             osFormat = GetOutputDriverForRaster(pszDest);
     133           0 :             if (osFormat.empty())
     134             :             {
     135           0 :                 return nullptr;
     136             :             }
     137             :         }
     138             :         else
     139             :         {
     140          48 :             osFormat = psOptions->osFormat;
     141             :         }
     142             : 
     143          48 :         GDALDriverH hDriver = GDALGetDriverByName(osFormat);
     144          48 :         if (hDriver == nullptr)
     145             :         {
     146           0 :             return nullptr;
     147             :         }
     148             : 
     149          48 :         if (bSetAlpha)
     150             :         {
     151             :             // TODO(winkey): There should be a way to preserve alpha
     152             :             // band data not in the collar.
     153          10 :             if (nBands == 4)
     154           2 :                 nBands--;
     155             :             else
     156           8 :                 nDstBands++;
     157             :         }
     158             : 
     159          48 :         if (bSetMask)
     160             :         {
     161          32 :             if (nBands == 4)
     162             :             {
     163           0 :                 nDstBands = 3;
     164           0 :                 nBands = 3;
     165             :             }
     166             :         }
     167             : 
     168          48 :         hDstDS = GDALCreate(hDriver, pszDest, nXSize, nYSize, nDstBands,
     169             :                             GDT_Byte, psOptions->aosCreationOptions.List());
     170          48 :         if (hDstDS == nullptr)
     171             :         {
     172           0 :             return nullptr;
     173             :         }
     174             : 
     175          48 :         double adfGeoTransform[6] = {};
     176             : 
     177          48 :         if (GDALGetGeoTransform(hSrcDataset, adfGeoTransform) == CE_None)
     178             :         {
     179          16 :             GDALSetGeoTransform(hDstDS, adfGeoTransform);
     180          16 :             GDALSetProjection(hDstDS, GDALGetProjectionRef(hSrcDataset));
     181             :         }
     182             :     }
     183             :     else
     184             :     {
     185           5 :         if (!psOptions->aosCreationOptions.empty())
     186             :         {
     187           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     188             :                      "Warning: creation options are ignored when writing to "
     189             :                      "an existing file.");
     190             :         }
     191             : 
     192             :         /***** check the input and output datasets are the same size *****/
     193          10 :         if (GDALGetRasterXSize(hDstDS) != nXSize ||
     194           5 :             GDALGetRasterYSize(hDstDS) != nYSize)
     195             :         {
     196           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     197             :                      "The dimensions of the output dataset don't match "
     198             :                      "the dimensions of the input dataset.");
     199           0 :             return nullptr;
     200             :         }
     201             : 
     202           5 :         if (bSetAlpha)
     203             :         {
     204           1 :             if (nBands != 4 &&
     205           0 :                 (nBands < 2 ||
     206           0 :                  GDALGetRasterColorInterpretation(
     207             :                      GDALGetRasterBand(hDstDS, nBands)) != GCI_AlphaBand))
     208             :             {
     209           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     210             :                          "Last band is not an alpha band.");
     211           0 :                 return nullptr;
     212             :             }
     213             : 
     214           1 :             nBands--;
     215             :         }
     216             : 
     217           5 :         if (bSetMask)
     218             :         {
     219           1 :             if (nBands == 4)
     220             :             {
     221           0 :                 nDstBands = 3;
     222           0 :                 nBands = 3;
     223             :             }
     224             :         }
     225             :     }
     226             : 
     227             :     /***** set a color if there are no colors set? *****/
     228             : 
     229          53 :     if (oColors.empty())
     230             :     {
     231         100 :         Color oColor;
     232             : 
     233             :         /***** loop over the bands to get the right number of values *****/
     234         142 :         for (int iBand = 0; iBand < nBands; iBand++)
     235             :         {
     236             :             // black or white?
     237          92 :             oColor.push_back(bNearWhite ? 255 : 0);
     238             :         }
     239             : 
     240             :         /***** add the color to the colors *****/
     241          50 :         oColors.push_back(oColor);
     242          50 :         assert(!oColors.empty());
     243             :     }
     244             : 
     245             :     /***** does the number of bands match the number of color values? *****/
     246             : 
     247          53 :     if (static_cast<int>(oColors.front().size()) != nBands)
     248             :     {
     249           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     250             :                  "-color args must have the same number of values as "
     251             :                  "the non alpha input band count.\n");
     252           0 :         if (bCloseOutDSOnError)
     253           0 :             GDALClose(hDstDS);
     254           0 :         return nullptr;
     255             :     }
     256             : 
     257         154 :     for (int iBand = 0; iBand < nBands; iBand++)
     258             :     {
     259         101 :         GDALRasterBandH hBand = GDALGetRasterBand(hSrcDataset, iBand + 1);
     260         101 :         if (GDALGetRasterDataType(hBand) != GDT_Byte)
     261             :         {
     262           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     263             :                      "Band %d is not of type GDT_Byte. "
     264             :                      "It can lead to unexpected results.",
     265             :                      iBand + 1);
     266             :         }
     267         101 :         if (GDALGetRasterColorTable(hBand) != nullptr)
     268             :         {
     269           0 :             CPLError(
     270             :                 CE_Warning, CPLE_AppDefined,
     271             :                 "Band %d has a color table, which is ignored by nearblack. "
     272             :                 "It can lead to unexpected results.",
     273             :                 iBand + 1);
     274             :         }
     275             :     }
     276             : 
     277          53 :     GDALRasterBandH hMaskBand = nullptr;
     278             : 
     279          53 :     if (bSetMask)
     280             :     {
     281             :         // If there isn't already a mask band on the output file create one.
     282          33 :         if (GMF_PER_DATASET != GDALGetMaskFlags(GDALGetRasterBand(hDstDS, 1)))
     283             :         {
     284             : 
     285          32 :             if (CE_None != GDALCreateDatasetMaskBand(hDstDS, GMF_PER_DATASET))
     286             :             {
     287           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     288             :                          "Failed to create mask band on output DS");
     289           0 :                 bSetMask = false;
     290             :             }
     291             :         }
     292             : 
     293          33 :         if (bSetMask)
     294             :         {
     295          33 :             hMaskBand = GDALGetMaskBand(GDALGetRasterBand(hDstDS, 1));
     296             :         }
     297             :     }
     298             : 
     299             :     bool bRet;
     300          53 :     if (psOptions->bFloodFill)
     301             :     {
     302          24 :         bRet = GDALNearblackFloodFill(psOptions, hSrcDataset, hDstDS, hMaskBand,
     303             :                                       nBands, nDstBands, bSetMask, oColors);
     304             :     }
     305             :     else
     306             :     {
     307          29 :         bRet = GDALNearblackTwoPassesAlgorithm(psOptions, hSrcDataset, hDstDS,
     308             :                                                hMaskBand, nBands, nDstBands,
     309             :                                                bSetMask, oColors);
     310             :     }
     311          53 :     if (!bRet)
     312             :     {
     313           0 :         if (bCloseOutDSOnError)
     314           0 :             GDALClose(hDstDS);
     315           0 :         hDstDS = nullptr;
     316             :     }
     317             : 
     318          53 :     return hDstDS;
     319             : }
     320             : 
     321             : /************************************************************************/
     322             : /*                   GDALNearblackTwoPassesAlgorithm()                  */
     323             : /*                                                                      */
     324             : /* Do a top-to-bottom pass, followed by a bottom-to-top one.            */
     325             : /************************************************************************/
     326             : 
     327          41 : bool GDALNearblackTwoPassesAlgorithm(const GDALNearblackOptions *psOptions,
     328             :                                      GDALDatasetH hSrcDataset,
     329             :                                      GDALDatasetH hDstDS,
     330             :                                      GDALRasterBandH hMaskBand, int nBands,
     331             :                                      int nDstBands, bool bSetMask,
     332             :                                      const Colors &oColors)
     333             : {
     334          41 :     const int nXSize = GDALGetRasterXSize(hSrcDataset);
     335          41 :     const int nYSize = GDALGetRasterYSize(hSrcDataset);
     336             : 
     337          41 :     const int nMaxNonBlack = psOptions->nMaxNonBlack;
     338          41 :     const int nNearDist = psOptions->nNearDist;
     339          41 :     const bool bNearWhite = psOptions->bNearWhite;
     340          41 :     const bool bSetAlpha = psOptions->bSetAlpha;
     341             : 
     342             :     /* -------------------------------------------------------------------- */
     343             :     /*      Allocate a line buffer.                                         */
     344             :     /* -------------------------------------------------------------------- */
     345             : 
     346          82 :     std::vector<GByte> abyLine(static_cast<size_t>(nXSize) * nDstBands);
     347          41 :     GByte *pabyLine = abyLine.data();
     348             : 
     349          82 :     std::vector<GByte> abyMask;
     350          41 :     GByte *pabyMask = nullptr;
     351          41 :     if (bSetMask)
     352             :     {
     353          27 :         abyMask.resize(nXSize);
     354          27 :         pabyMask = abyMask.data();
     355             :     }
     356             : 
     357          82 :     std::vector<int> anLastLineCounts(nXSize);
     358          41 :     int *panLastLineCounts = anLastLineCounts.data();
     359             : 
     360             :     /* -------------------------------------------------------------------- */
     361             :     /*      Processing data one line at a time.                             */
     362             :     /* -------------------------------------------------------------------- */
     363             :     int iLine;
     364             : 
     365        1015 :     for (iLine = 0; iLine < nYSize; iLine++)
     366             :     {
     367         974 :         CPLErr eErr = GDALDatasetRasterIO(
     368             :             hSrcDataset, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1,
     369             :             GDT_Byte, nBands, nullptr, nDstBands, nXSize * nDstBands, 1);
     370         974 :         if (eErr != CE_None)
     371             :         {
     372           0 :             return false;
     373             :         }
     374             : 
     375         974 :         if (bSetAlpha)
     376             :         {
     377       20400 :             for (int iCol = 0; iCol < nXSize; iCol++)
     378             :             {
     379       20000 :                 pabyLine[iCol * nDstBands + nDstBands - 1] = 255;
     380             :             }
     381             :         }
     382             : 
     383         974 :         if (bSetMask)
     384             :         {
     385        8422 :             for (int iCol = 0; iCol < nXSize; iCol++)
     386             :             {
     387        8148 :                 pabyMask[iCol] = 255;
     388             :             }
     389             :         }
     390             : 
     391         974 :         ProcessLine(pabyLine, pabyMask, 0, nXSize - 1, nBands, nDstBands,
     392             :                     nNearDist, nMaxNonBlack, bNearWhite, oColors,
     393             :                     panLastLineCounts,
     394             :                     true,   // bDoHorizontalCheck
     395             :                     true,   // bDoVerticalCheck
     396             :                     false,  // bBottomUp
     397             :                     iLine);
     398         974 :         ProcessLine(pabyLine, pabyMask, nXSize - 1, 0, nBands, nDstBands,
     399             :                     nNearDist, nMaxNonBlack, bNearWhite, oColors,
     400             :                     panLastLineCounts,
     401             :                     true,   // bDoHorizontalCheck
     402             :                     false,  // bDoVerticalCheck
     403             :                     false,  // bBottomUp
     404             :                     iLine);
     405             : 
     406         974 :         eErr = GDALDatasetRasterIO(hDstDS, GF_Write, 0, iLine, nXSize, 1,
     407             :                                    pabyLine, nXSize, 1, GDT_Byte, nDstBands,
     408             :                                    nullptr, nDstBands, nXSize * nDstBands, 1);
     409             : 
     410         974 :         if (eErr != CE_None)
     411             :         {
     412           0 :             return false;
     413             :         }
     414             : 
     415             :         /***** write out the mask band line *****/
     416             : 
     417         974 :         if (bSetMask)
     418             :         {
     419         274 :             eErr = GDALRasterIO(hMaskBand, GF_Write, 0, iLine, nXSize, 1,
     420             :                                 pabyMask, nXSize, 1, GDT_Byte, 0, 0);
     421         274 :             if (eErr != CE_None)
     422             :             {
     423           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     424             :                          "ERROR writing out line to mask band.");
     425           0 :                 return false;
     426             :             }
     427             :         }
     428             : 
     429         974 :         if (!(psOptions->pfnProgress(
     430         974 :                 0.5 * ((iLine + 1) / static_cast<double>(nYSize)), nullptr,
     431         974 :                 psOptions->pProgressData)))
     432             :         {
     433           0 :             return false;
     434             :         }
     435             :     }
     436             : 
     437             :     /* -------------------------------------------------------------------- */
     438             :     /*      Now process from the bottom back up                            .*/
     439             :     /* -------------------------------------------------------------------- */
     440          41 :     memset(panLastLineCounts, 0, sizeof(int) * nXSize);
     441             : 
     442        1015 :     for (iLine = nYSize - 1; hDstDS != nullptr && iLine >= 0; iLine--)
     443             :     {
     444         974 :         CPLErr eErr = GDALDatasetRasterIO(
     445             :             hDstDS, GF_Read, 0, iLine, nXSize, 1, pabyLine, nXSize, 1, GDT_Byte,
     446             :             nDstBands, nullptr, nDstBands, nXSize * nDstBands, 1);
     447         974 :         if (eErr != CE_None)
     448             :         {
     449           0 :             return false;
     450             :         }
     451             : 
     452             :         /***** read the mask band line back in *****/
     453             : 
     454         974 :         if (bSetMask)
     455             :         {
     456         274 :             eErr = GDALRasterIO(hMaskBand, GF_Read, 0, iLine, nXSize, 1,
     457             :                                 pabyMask, nXSize, 1, GDT_Byte, 0, 0);
     458         274 :             if (eErr != CE_None)
     459             :             {
     460           0 :                 return false;
     461             :             }
     462             :         }
     463             : 
     464         974 :         ProcessLine(pabyLine, pabyMask, 0, nXSize - 1, nBands, nDstBands,
     465             :                     nNearDist, nMaxNonBlack, bNearWhite, oColors,
     466             :                     panLastLineCounts,
     467             :                     true,  // bDoHorizontalCheck
     468             :                     true,  // bDoVerticalCheck
     469             :                     true,  // bBottomUp
     470         974 :                     nYSize - 1 - iLine);
     471         974 :         ProcessLine(pabyLine, pabyMask, nXSize - 1, 0, nBands, nDstBands,
     472             :                     nNearDist, nMaxNonBlack, bNearWhite, oColors,
     473             :                     panLastLineCounts,
     474             :                     true,   // bDoHorizontalCheck
     475             :                     false,  // bDoVerticalCheck
     476             :                     true,   // bBottomUp
     477         974 :                     nYSize - 1 - iLine);
     478             : 
     479         974 :         eErr = GDALDatasetRasterIO(hDstDS, GF_Write, 0, iLine, nXSize, 1,
     480             :                                    pabyLine, nXSize, 1, GDT_Byte, nDstBands,
     481             :                                    nullptr, nDstBands, nXSize * nDstBands, 1);
     482         974 :         if (eErr != CE_None)
     483             :         {
     484           0 :             return false;
     485             :         }
     486             : 
     487             :         /***** write out the mask band line *****/
     488             : 
     489         974 :         if (bSetMask)
     490             :         {
     491         274 :             eErr = GDALRasterIO(hMaskBand, GF_Write, 0, iLine, nXSize, 1,
     492             :                                 pabyMask, nXSize, 1, GDT_Byte, 0, 0);
     493         274 :             if (eErr != CE_None)
     494             :             {
     495           0 :                 return false;
     496             :             }
     497             :         }
     498             : 
     499         974 :         if (!(psOptions->pfnProgress(0.5 + 0.5 * (nYSize - iLine) /
     500         974 :                                                static_cast<double>(nYSize),
     501         974 :                                      nullptr, psOptions->pProgressData)))
     502             :         {
     503           0 :             return false;
     504             :         }
     505             :     }
     506             : 
     507          41 :     return true;
     508             : }
     509             : 
     510             : /************************************************************************/
     511             : /*                            ProcessLine()                             */
     512             : /*                                                                      */
     513             : /*      Process a single scanline of image data.                        */
     514             : /************************************************************************/
     515             : 
     516        3896 : static void ProcessLine(GByte *pabyLine, GByte *pabyMask, int iStart, int iEnd,
     517             :                         int nSrcBands, int nDstBands, int nNearDist,
     518             :                         int nMaxNonBlack, bool bNearWhite,
     519             :                         const Colors &oColors, int *panLastLineCounts,
     520             :                         bool bDoHorizontalCheck, bool bDoVerticalCheck,
     521             :                         bool bBottomUp, int iLineFromTopOrBottom)
     522             : {
     523        3896 :     const GByte nReplacevalue = bNearWhite ? 255 : 0;
     524             : 
     525             :     /* -------------------------------------------------------------------- */
     526             :     /*      Vertical checking.                                              */
     527             :     /* -------------------------------------------------------------------- */
     528             : 
     529        3896 :     if (bDoVerticalCheck)
     530             :     {
     531        1948 :         const int nXSize = std::max(iStart + 1, iEnd + 1);
     532             : 
     533       88244 :         for (int i = 0; i < nXSize; i++)
     534             :         {
     535             :             // are we already terminated for this column?
     536       86296 :             if (panLastLineCounts[i] > nMaxNonBlack)
     537       61570 :                 continue;
     538             : 
     539             :             /***** is the pixel valid data? ****/
     540             : 
     541       24726 :             bool bIsNonBlack = false;
     542             : 
     543             :             /***** loop over the colors *****/
     544             : 
     545       29421 :             for (int iColor = 0; iColor < static_cast<int>(oColors.size());
     546             :                  iColor++)
     547             :             {
     548             : 
     549       27794 :                 const Color &oColor = oColors[iColor];
     550             : 
     551       27794 :                 bIsNonBlack = false;
     552             : 
     553             :                 /***** loop over the bands *****/
     554             : 
     555       96432 :                 for (int iBand = 0; iBand < nSrcBands; iBand++)
     556             :                 {
     557       73333 :                     const int nPix = pabyLine[i * nDstBands + iBand];
     558             : 
     559      146406 :                     if (oColor[iBand] - nPix > nNearDist ||
     560       73073 :                         nPix > nNearDist + oColor[iBand])
     561             :                     {
     562        4695 :                         bIsNonBlack = true;
     563        4695 :                         break;
     564             :                     }
     565             :                 }
     566             : 
     567       27794 :                 if (!bIsNonBlack)
     568       23099 :                     break;
     569             :             }
     570             : 
     571       24726 :             if (bIsNonBlack)
     572             :             {
     573        1627 :                 panLastLineCounts[i]++;
     574             : 
     575        1627 :                 if (panLastLineCounts[i] > nMaxNonBlack)
     576        1415 :                     continue;
     577             : 
     578         212 :                 if (iLineFromTopOrBottom == 0 && nMaxNonBlack > 0)
     579             :                 {
     580             :                     // if there's a valid value just at the top or bottom
     581             :                     // of the raster, then ignore the nMaxNonBlack setting
     582         139 :                     panLastLineCounts[i] = nMaxNonBlack + 1;
     583         139 :                     continue;
     584             :                 }
     585             :             }
     586             :             // else
     587             :             //   panLastLineCounts[i] = 0; // not sure this even makes sense
     588             : 
     589             :             /***** replace the pixel values *****/
     590       91788 :             for (int iBand = 0; iBand < nSrcBands; iBand++)
     591       68616 :                 pabyLine[i * nDstBands + iBand] = nReplacevalue;
     592             : 
     593             :             /***** alpha *****/
     594       23172 :             if (nDstBands > nSrcBands)
     595        6882 :                 pabyLine[i * nDstBands + nDstBands - 1] = 0;
     596             : 
     597             :             /***** mask *****/
     598       23172 :             if (pabyMask != nullptr)
     599        3402 :                 pabyMask[i] = 0;
     600             :         }
     601             :     }
     602             : 
     603             :     /* -------------------------------------------------------------------- */
     604             :     /*      Horizontal Checking.                                            */
     605             :     /* -------------------------------------------------------------------- */
     606             : 
     607        3896 :     if (bDoHorizontalCheck)
     608             :     {
     609        3896 :         int nNonBlackPixels = 0;
     610             : 
     611             :         /***** on a bottom up pass assume nMaxNonBlack is 0 *****/
     612             : 
     613        3896 :         if (bBottomUp)
     614        1948 :             nMaxNonBlack = 0;
     615             : 
     616        3896 :         const int iDir = iStart < iEnd ? 1 : -1;
     617             : 
     618        3896 :         bool bDoTest = TRUE;
     619             : 
     620      172592 :         for (int i = iStart; i != iEnd; i += iDir)
     621             :         {
     622             :             /***** not seen any valid data? *****/
     623             : 
     624      168696 :             if (bDoTest)
     625             :             {
     626             :                 /***** is the pixel valid data? ****/
     627             : 
     628       54075 :                 bool bIsNonBlack = false;
     629             : 
     630             :                 /***** loop over the colors *****/
     631             : 
     632       58023 :                 for (int iColor = 0; iColor < static_cast<int>(oColors.size());
     633             :                      iColor++)
     634             :                 {
     635             : 
     636       54375 :                     const Color &oColor = oColors[iColor];
     637             : 
     638       54375 :                     bIsNonBlack = false;
     639             : 
     640             :                     /***** loop over the bands *****/
     641             : 
     642      204577 :                     for (int iBand = 0; iBand < nSrcBands; iBand++)
     643             :                     {
     644      154150 :                         const int nPix = pabyLine[i * nDstBands + iBand];
     645             : 
     646      307696 :                         if (oColor[iBand] - nPix > nNearDist ||
     647      153546 :                             nPix > nNearDist + oColor[iBand])
     648             :                         {
     649        3948 :                             bIsNonBlack = true;
     650        3948 :                             break;
     651             :                         }
     652             :                     }
     653             : 
     654       54375 :                     if (bIsNonBlack == false)
     655       50427 :                         break;
     656             :                 }
     657             : 
     658       54075 :                 if (bIsNonBlack)
     659             :                 {
     660             :                     /***** use nNonBlackPixels in grey areas  *****/
     661             :                     /***** from the vertical pass's grey areas ****/
     662             : 
     663        3648 :                     if (panLastLineCounts[i] <= nMaxNonBlack)
     664           0 :                         nNonBlackPixels = panLastLineCounts[i];
     665             :                     else
     666        3648 :                         nNonBlackPixels++;
     667             :                 }
     668             : 
     669       54075 :                 if (nNonBlackPixels > nMaxNonBlack)
     670             :                 {
     671        3438 :                     bDoTest = false;
     672        3438 :                     continue;
     673             :                 }
     674             : 
     675       50637 :                 if (bIsNonBlack && nMaxNonBlack > 0 && i == iStart)
     676             :                 {
     677             :                     // if there's a valid value just at the left or right
     678             :                     // of the raster, then ignore the nMaxNonBlack setting
     679         152 :                     bDoTest = false;
     680         152 :                     continue;
     681             :                 }
     682             : 
     683             :                 /***** replace the pixel values *****/
     684             : 
     685      200436 :                 for (int iBand = 0; iBand < nSrcBands; iBand++)
     686      149951 :                     pabyLine[i * nDstBands + iBand] = nReplacevalue;
     687             : 
     688             :                 /***** alpha *****/
     689             : 
     690       50485 :                 if (nDstBands > nSrcBands)
     691       15534 :                     pabyLine[i * nDstBands + nDstBands - 1] = 0;
     692             : 
     693             :                 /***** mask *****/
     694             : 
     695       50485 :                 if (pabyMask != nullptr)
     696        7295 :                     pabyMask[i] = 0;
     697             :             }
     698             : 
     699             :             /***** seen valid data but test if the *****/
     700             :             /***** vertical pass saw any non valid data *****/
     701             : 
     702      114621 :             else if (panLastLineCounts[i] == 0)
     703             :             {
     704        1768 :                 bDoTest = true;
     705        1768 :                 nNonBlackPixels = 0;
     706             :             }
     707             :         }
     708             :     }
     709        3896 : }
     710             : 
     711             : /************************************************************************/
     712             : /*                            IsInt()                                   */
     713             : /************************************************************************/
     714             : 
     715          18 : static bool IsInt(const char *pszArg)
     716             : {
     717          18 :     if (pszArg[0] == '-')
     718           0 :         pszArg++;
     719             : 
     720          18 :     if (*pszArg == '\0')
     721           0 :         return false;
     722             : 
     723          54 :     while (*pszArg != '\0')
     724             :     {
     725          36 :         if (*pszArg < '0' || *pszArg > '9')
     726           0 :             return false;
     727          36 :         pszArg++;
     728             :     }
     729             : 
     730          18 :     return true;
     731             : }
     732             : 
     733             : /************************************************************************/
     734             : /*                    GDALNearblackOptionsGetParser()                   */
     735             : /************************************************************************/
     736             : 
     737             : static std::unique_ptr<GDALArgumentParser>
     738          54 : GDALNearblackOptionsGetParser(GDALNearblackOptions *psOptions,
     739             :                               GDALNearblackOptionsForBinary *psOptionsForBinary)
     740             : {
     741             :     auto argParser = std::make_unique<GDALArgumentParser>(
     742          54 :         "nearblack", /* bForBinary=*/psOptionsForBinary != nullptr);
     743             : 
     744          54 :     argParser->add_description(
     745          54 :         _("Convert nearly black/white borders to black."));
     746             : 
     747          54 :     argParser->add_epilog(_(
     748          54 :         "For more details, consult https://gdal.org/programs/nearblack.html"));
     749             : 
     750          54 :     argParser->add_output_format_argument(psOptions->osFormat);
     751             : 
     752             :     // Written that way so that in library mode, users can still use the -q
     753             :     // switch, even if it has no effect
     754             :     argParser->add_quiet_argument(
     755          54 :         psOptionsForBinary ? &(psOptionsForBinary->bQuiet) : nullptr);
     756             : 
     757          54 :     argParser->add_creation_options_argument(psOptions->aosCreationOptions);
     758             : 
     759             :     auto &oOutputFileArg =
     760          54 :         argParser->add_argument("-o")
     761         108 :             .metavar("<output_file>")
     762          54 :             .help(_("The name of the output file to be created."));
     763          54 :     if (psOptionsForBinary)
     764           9 :         oOutputFileArg.store_into(psOptionsForBinary->osOutFile);
     765             : 
     766             :     {
     767          54 :         auto &group = argParser->add_mutually_exclusive_group();
     768          54 :         group.add_argument("-white")
     769          54 :             .store_into(psOptions->bNearWhite)
     770             :             .help(_("Search for nearly white (255) pixels instead of nearly "
     771          54 :                     "black pixels."));
     772             : 
     773          54 :         group.add_argument("-color")
     774          54 :             .append()
     775         108 :             .metavar("<c1,c2,c3...cn>")
     776             :             .action(
     777          21 :                 [psOptions](const std::string &s)
     778             :                 {
     779          12 :                     Color oColor;
     780             : 
     781             :                     /***** tokenize the arg on , *****/
     782             : 
     783             :                     const CPLStringList aosTokens(
     784          12 :                         CSLTokenizeString2(s.c_str(), ",", 0));
     785             : 
     786             :                     /***** loop over the tokens *****/
     787             : 
     788          24 :                     for (int iToken = 0; iToken < aosTokens.size(); iToken++)
     789             :                     {
     790             : 
     791             :                         /***** ensure the token is an int and add it to the color *****/
     792             : 
     793          18 :                         if (IsInt(aosTokens[iToken]))
     794             :                         {
     795          18 :                             oColor.push_back(atoi(aosTokens[iToken]));
     796             :                         }
     797             :                         else
     798             :                         {
     799             :                             throw std::invalid_argument(
     800           0 :                                 "Colors must be valid integers.");
     801             :                         }
     802             :                     }
     803             : 
     804             :                     /***** check if the number of bands is consistent *****/
     805             : 
     806           9 :                     if (!psOptions->oColors.empty() &&
     807           3 :                         psOptions->oColors.front().size() != oColor.size())
     808             :                     {
     809             :                         throw std::invalid_argument(
     810             :                             "all -color args must have the same number of "
     811           0 :                             "values.\n");
     812             :                     }
     813             : 
     814             :                     /***** add the color to the colors *****/
     815             : 
     816           6 :                     psOptions->oColors.push_back(oColor);
     817          60 :                 })
     818          54 :             .help(_("Search for pixels near the specified color."));
     819             :     }
     820             : 
     821          54 :     argParser->add_argument("-nb")
     822         108 :         .metavar("<non_black_pixels>")
     823          54 :         .nargs(1)
     824          54 :         .default_value(psOptions->nMaxNonBlack)
     825          54 :         .store_into(psOptions->nMaxNonBlack)
     826          54 :         .help(_("Number of consecutive non-black pixels."));
     827             : 
     828          54 :     argParser->add_argument("-near")
     829         108 :         .metavar("<dist>")
     830          54 :         .nargs(1)
     831          54 :         .default_value(psOptions->nNearDist)
     832          54 :         .store_into(psOptions->nNearDist)
     833             :         .help(_("Select how far from black, white or custom colors the pixel "
     834          54 :                 "values can be and still considered."));
     835             : 
     836          54 :     argParser->add_argument("-setalpha")
     837          54 :         .store_into(psOptions->bSetAlpha)
     838          54 :         .help(_("Adds an alpha band if needed."));
     839             : 
     840          54 :     argParser->add_argument("-setmask")
     841          54 :         .store_into(psOptions->bSetMask)
     842             :         .help(_("Adds a mask band to the output file if -o is used, or to the "
     843          54 :                 "input file otherwise."));
     844             : 
     845          54 :     argParser->add_argument("-alg")
     846          54 :         .choices("floodfill", "twopasses")
     847         108 :         .metavar("floodfill|twopasses")
     848          86 :         .action([psOptions](const std::string &s)
     849          97 :                 { psOptions->bFloodFill = EQUAL(s.c_str(), "floodfill"); })
     850          54 :         .help(_("Selects the algorithm to apply."));
     851             : 
     852          54 :     if (psOptionsForBinary)
     853             :     {
     854           9 :         argParser->add_argument("input_file")
     855          18 :             .metavar("<input_file>")
     856           9 :             .store_into(psOptionsForBinary->osInFile)
     857             :             .help(_("The input file. Any GDAL supported format, any number of "
     858           9 :                     "bands, normally 8bit Byte bands."));
     859             :     }
     860             : 
     861          54 :     return argParser;
     862             : }
     863             : 
     864             : /************************************************************************/
     865             : /*                      GDALNearblackGetParserUsage()                   */
     866             : /************************************************************************/
     867             : 
     868           0 : std::string GDALNearblackGetParserUsage()
     869             : {
     870             :     try
     871             :     {
     872           0 :         GDALNearblackOptions sOptions;
     873           0 :         GDALNearblackOptionsForBinary sOptionsForBinary;
     874             :         auto argParser =
     875           0 :             GDALNearblackOptionsGetParser(&sOptions, &sOptionsForBinary);
     876           0 :         return argParser->usage();
     877             :     }
     878           0 :     catch (const std::exception &err)
     879             :     {
     880           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
     881           0 :                  err.what());
     882           0 :         return std::string();
     883             :     }
     884             : }
     885             : 
     886             : /************************************************************************/
     887             : /*                           GDALNearblackOptionsNew()                  */
     888             : /************************************************************************/
     889             : 
     890             : /**
     891             :  * Allocates a GDALNearblackOptions struct.
     892             :  *
     893             :  * @param papszArgv NULL terminated list of options (potentially including
     894             :  * filename and open options too), or NULL. The accepted options are the ones of
     895             :  * the <a href="/programs/nearblack.html">nearblack</a> utility.
     896             :  * @param psOptionsForBinary (output) may be NULL (and should generally be
     897             :  * NULL), otherwise (gdal_translate_bin.cpp use case) must be allocated with
     898             :  *                           GDALNearblackOptionsForBinaryNew() prior to this
     899             :  * function. Will be filled with potentially present filename, open options,...
     900             :  * @return pointer to the allocated GDALNearblackOptions struct. Must be freed
     901             :  * with GDALNearblackOptionsFree().
     902             :  *
     903             :  * @since GDAL 2.1
     904             :  */
     905             : 
     906             : GDALNearblackOptions *
     907          54 : GDALNearblackOptionsNew(char **papszArgv,
     908             :                         GDALNearblackOptionsForBinary *psOptionsForBinary)
     909             : {
     910         107 :     auto psOptions = std::make_unique<GDALNearblackOptions>();
     911             : 
     912             :     try
     913             :     {
     914             : 
     915             :         auto argParser =
     916         107 :             GDALNearblackOptionsGetParser(psOptions.get(), psOptionsForBinary);
     917             : 
     918          54 :         argParser->parse_args_without_binary_name(papszArgv);
     919             : 
     920          53 :         return psOptions.release();
     921             :     }
     922           0 :     catch (const std::exception &err)
     923             :     {
     924           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
     925           0 :         return nullptr;
     926             :     }
     927             : }
     928             : 
     929             : /************************************************************************/
     930             : /*                       GDALNearblackOptionsFree()                     */
     931             : /************************************************************************/
     932             : 
     933             : /**
     934             :  * Frees the GDALNearblackOptions struct.
     935             :  *
     936             :  * @param psOptions the options struct for GDALNearblack().
     937             :  *
     938             :  * @since GDAL 2.1
     939             :  */
     940             : 
     941          53 : void GDALNearblackOptionsFree(GDALNearblackOptions *psOptions)
     942             : {
     943          53 :     delete psOptions;
     944          53 : }
     945             : 
     946             : /************************************************************************/
     947             : /*                  GDALNearblackOptionsSetProgress()                   */
     948             : /************************************************************************/
     949             : 
     950             : /**
     951             :  * Set a progress function.
     952             :  *
     953             :  * @param psOptions the options struct for GDALNearblack().
     954             :  * @param pfnProgress the progress callback.
     955             :  * @param pProgressData the user data for the progress callback.
     956             :  *
     957             :  * @since GDAL 2.1
     958             :  */
     959             : 
     960           7 : void GDALNearblackOptionsSetProgress(GDALNearblackOptions *psOptions,
     961             :                                      GDALProgressFunc pfnProgress,
     962             :                                      void *pProgressData)
     963             : {
     964           7 :     psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
     965           7 :     psOptions->pProgressData = pProgressData;
     966           7 : }

Generated by: LCOV version 1.14