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

Generated by: LCOV version 1.14