LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_clean_collar.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 119 140 85.0 %
Date: 2025-12-22 19:17:04 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster clean-collar" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_clean_collar.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "cpl_vsi_virtual.h"
      17             : 
      18             : #include "gdal_priv.h"
      19             : #include "gdal_utils.h"
      20             : 
      21             : //! @cond Doxygen_Suppress
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : /************************************************************************/
      28             : /*   GDALRasterCleanCollarAlgorithm::GDALRasterCleanCollarAlgorithm()   */
      29             : /************************************************************************/
      30             : 
      31          27 : GDALRasterCleanCollarAlgorithm::GDALRasterCleanCollarAlgorithm()
      32          27 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      33             : {
      34          27 :     AddProgressArg();
      35             : 
      36          27 :     AddOpenOptionsArg(&m_openOptions);
      37          27 :     AddInputFormatsArg(&m_inputFormats)
      38          54 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
      39          27 :     AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER);
      40             : 
      41             :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
      42          27 :                         /* positionalAndRequired = */ false)
      43          27 :         .SetPositional()
      44          27 :         .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
      45             :     AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
      46          27 :                        /* bGDALGAllowed = */ false)
      47             :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      48          81 :                          {GDAL_DCAP_CREATE, GDAL_DCAP_RASTER});
      49          27 :     AddCreationOptionsArg(&m_creationOptions);
      50          27 :     AddOverwriteArg(&m_overwrite);
      51          27 :     AddUpdateArg(&m_update);
      52             : 
      53             :     AddArg("color", 0,
      54             :            _("Transparent color(s): tuple of integer (like 'r,g,b'), 'black', "
      55             :              "'white'"),
      56          54 :            &m_color)
      57          27 :         .SetDefault("black")
      58          27 :         .SetPackedValuesAllowed(false)
      59             :         .AddValidationAction(
      60           8 :             [this]()
      61             :             {
      62          21 :                 for (const auto &c : m_color)
      63             :                 {
      64          15 :                     if (c != "white" && c != "black")
      65             :                     {
      66             :                         const CPLStringList aosTokens(
      67           7 :                             CSLTokenizeString2(c.c_str(), ",", 0));
      68          15 :                         for (const char *pszToken : aosTokens)
      69             :                         {
      70           9 :                             if (CPLGetValueType(pszToken) != CPL_VALUE_INTEGER)
      71             :                             {
      72           1 :                                 ReportError(CE_Failure, CPLE_IllegalArg,
      73             :                                             "Value for 'color' should be tuple "
      74             :                                             "of integer (like 'r,g,b'), "
      75             :                                             "'black' or 'white'");
      76           1 :                                 return false;
      77             :                             }
      78             :                         }
      79             :                     }
      80             :                 }
      81           6 :                 return true;
      82          27 :             });
      83             :     AddArg("color-threshold", 0,
      84             :            _("Select how far from specified transparent colors the pixel "
      85             :              "values are considered transparent."),
      86          54 :            &m_colorThreshold)
      87          27 :         .SetDefault(m_colorThreshold)
      88          27 :         .SetMinValueIncluded(0);
      89             :     AddArg("pixel-distance", 0,
      90             :            _("Number of consecutive transparent pixels that can be encountered "
      91             :              "before the giving up search inwards."),
      92          54 :            &m_pixelDistance)
      93          27 :         .SetDefault(m_pixelDistance)
      94          27 :         .SetMinValueIncluded(0);
      95             :     AddArg("add-alpha", 0, _("Adds an alpha band to the output dataset."),
      96          54 :            &m_addAlpha)
      97          27 :         .SetMutualExclusionGroup("addalpha-addmask");
      98             :     AddArg("add-mask", 0, _("Adds a mask band to the output dataset."),
      99          54 :            &m_addMask)
     100          27 :         .SetMutualExclusionGroup("addalpha-addmask");
     101          54 :     AddArg("algorithm", 0, _("Algorithm to apply"), &m_algorithm)
     102          27 :         .SetChoices("floodfill", "twopasses")
     103          27 :         .SetDefault(m_algorithm);
     104          27 : }
     105             : 
     106             : /************************************************************************/
     107             : /*               GDALRasterCleanCollarAlgorithm::RunImpl()              */
     108             : /************************************************************************/
     109             : 
     110          18 : bool GDALRasterCleanCollarAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     111             :                                              void *pProgressData)
     112             : {
     113          18 :     auto poSrcDS = m_inputDataset.GetDatasetRef();
     114          18 :     CPLAssert(poSrcDS);
     115             : 
     116          18 :     auto poDstDS = m_outputDataset.GetDatasetRef();
     117          18 :     if (poSrcDS == poDstDS && poSrcDS->GetAccess() == GA_ReadOnly)
     118             :     {
     119           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     120             :                     "Dataset should be opened in update mode");
     121           0 :         return false;
     122             :     }
     123          18 :     if (!poDstDS && !m_outputDataset.IsNameSet() && m_update)
     124             :     {
     125           7 :         m_outputDataset.Set(poSrcDS);
     126           7 :         poDstDS = poSrcDS;
     127             :     }
     128             : 
     129          18 :     const bool dstDSWasNull = poDstDS == nullptr;
     130             : 
     131          18 :     if (dstDSWasNull && !m_outputDataset.IsNameSet() && !m_update)
     132             :     {
     133           2 :         ReportError(CE_Failure, CPLE_AppDefined,
     134             :                     "Output dataset is not specified. If you intend to update "
     135             :                     "the input dataset, set the 'update' option");
     136           2 :         return false;
     137             :     }
     138             : 
     139          16 :     if (!poDstDS && !m_outputDataset.GetName().empty() && poDstDS != poSrcDS)
     140             :     {
     141             :         VSIStatBufL sStat;
     142           5 :         bool fileExists{VSIStatL(m_outputDataset.GetName().c_str(), &sStat) ==
     143           5 :                         0};
     144             : 
     145             :         {
     146          10 :             CPLErrorStateBackuper oCPLErrorHandlerPusher(CPLQuietErrorHandler);
     147           5 :             poDstDS = GDALDataset::FromHandle(GDALOpenEx(
     148           5 :                 m_outputDataset.GetName().c_str(),
     149             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_UPDATE,
     150             :                 nullptr, nullptr, nullptr));
     151           5 :             CPLErrorReset();
     152             :         }
     153             : 
     154           5 :         if ((poDstDS || fileExists) && !m_overwrite && !m_update)
     155             :         {
     156           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     157             :                      "Dataset '%s' already exists. Specify the --overwrite "
     158             :                      "option to overwrite it or the --update option to "
     159             :                      "update it.",
     160           0 :                      m_outputDataset.GetName().c_str());
     161           0 :             delete poDstDS;
     162           0 :             return false;
     163             :         }
     164             : 
     165           5 :         if (poDstDS && fileExists && m_overwrite)
     166             :         {
     167             :             // Delete the existing file
     168           0 :             delete poDstDS;
     169           0 :             poDstDS = nullptr;
     170           0 :             if (VSIUnlink(m_outputDataset.GetName().c_str()) != 0)
     171             :             {
     172           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     173             :                          "Failed to delete existing dataset '%s'.",
     174           0 :                          m_outputDataset.GetName().c_str());
     175           0 :                 return false;
     176             :             }
     177             :         }
     178             :     }
     179             : 
     180          32 :     CPLStringList aosOptions;
     181             : 
     182          16 :     if (!m_format.empty())
     183             :     {
     184           4 :         aosOptions.push_back("-of");
     185           4 :         aosOptions.push_back(m_format.c_str());
     186             :     }
     187             : 
     188          17 :     for (const auto &co : m_creationOptions)
     189             :     {
     190           1 :         aosOptions.push_back("-co");
     191           1 :         aosOptions.push_back(co.c_str());
     192             :     }
     193             : 
     194          36 :     for (const auto &color : m_color)
     195             :     {
     196          20 :         aosOptions.push_back("-color");
     197          40 :         std::string osColor;
     198          20 :         int nNonAlphaSrcBands = poSrcDS->GetRasterCount();
     199          40 :         if (nNonAlphaSrcBands &&
     200          20 :             poSrcDS->GetRasterBand(nNonAlphaSrcBands)
     201          20 :                     ->GetColorInterpretation() == GCI_AlphaBand)
     202           1 :             --nNonAlphaSrcBands;
     203          20 :         if (color == "white")
     204             :         {
     205           4 :             for (int i = 0; i < nNonAlphaSrcBands; ++i)
     206             :             {
     207           2 :                 if (i > 0)
     208           0 :                     osColor += ',';
     209           2 :                 osColor += "255";
     210             :             }
     211             :         }
     212          18 :         else if (color == "black")
     213             :         {
     214          30 :             for (int i = 0; i < nNonAlphaSrcBands; ++i)
     215             :             {
     216          15 :                 if (i > 0)
     217           0 :                     osColor += ',';
     218          15 :                 osColor += "0";
     219             :             }
     220             :         }
     221             :         else
     222             :         {
     223           3 :             osColor = color;
     224             :         }
     225          20 :         aosOptions.push_back(osColor.c_str());
     226             :     }
     227             : 
     228          16 :     aosOptions.push_back("-near");
     229          16 :     aosOptions.push_back(CPLSPrintf("%d", m_colorThreshold));
     230             : 
     231          16 :     aosOptions.push_back("-nb");
     232          16 :     aosOptions.push_back(CPLSPrintf("%d", m_pixelDistance));
     233             : 
     234          47 :     if (m_addAlpha ||
     235          15 :         (!m_addMask && poDstDS == nullptr && poSrcDS->GetRasterCount() > 0 &&
     236           3 :          poSrcDS->GetRasterBand(poSrcDS->GetRasterCount())
     237          34 :                  ->GetColorInterpretation() == GCI_AlphaBand) ||
     238          15 :         (!m_addMask && poDstDS != nullptr && poDstDS->GetRasterCount() > 0 &&
     239          10 :          poDstDS->GetRasterBand(poDstDS->GetRasterCount())
     240          10 :                  ->GetColorInterpretation() == GCI_AlphaBand))
     241             :     {
     242           2 :         aosOptions.push_back("-setalpha");
     243             :     }
     244             : 
     245          46 :     if (m_addMask ||
     246          14 :         (!m_addAlpha && poDstDS == nullptr && poSrcDS->GetRasterCount() > 0 &&
     247          33 :          poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET) ||
     248          14 :         (!m_addAlpha && poDstDS != nullptr && poDstDS->GetRasterCount() > 0 &&
     249          10 :          poDstDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET))
     250             :     {
     251           3 :         aosOptions.push_back("-setmask");
     252             :     }
     253             : 
     254          16 :     aosOptions.push_back("-alg");
     255          16 :     aosOptions.push_back(m_algorithm.c_str());
     256             : 
     257             :     std::unique_ptr<GDALNearblackOptions, decltype(&GDALNearblackOptionsFree)>
     258             :         psOptions{GDALNearblackOptionsNew(aosOptions.List(), nullptr),
     259          32 :                   GDALNearblackOptionsFree};
     260          16 :     if (!psOptions)
     261           0 :         return false;
     262             : 
     263          16 :     GDALNearblackOptionsSetProgress(psOptions.get(), pfnProgress,
     264             :                                     pProgressData);
     265             : 
     266          32 :     auto poRetDS = GDALDataset::FromHandle(GDALNearblack(
     267          16 :         m_outputDataset.GetName().c_str(), GDALDataset::ToHandle(poDstDS),
     268          16 :         GDALDataset::ToHandle(poSrcDS), psOptions.get(), nullptr));
     269          16 :     if (!poRetDS)
     270           0 :         return false;
     271             : 
     272          16 :     if (poDstDS == nullptr)
     273             :     {
     274           6 :         m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     275             :     }
     276          10 :     else if (dstDSWasNull)
     277             :     {
     278           0 :         const bool bCloseOK = poDstDS->Close() == CE_None;
     279           0 :         delete poDstDS;
     280           0 :         if (!bCloseOK)
     281             :         {
     282           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     283             :                      "Failed to close output dataset");
     284           0 :             return false;
     285             :         }
     286             :     }
     287             : 
     288          16 :     return true;
     289             : }
     290             : 
     291             : //! @endcond

Generated by: LCOV version 1.14