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

Generated by: LCOV version 1.14