LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_clean_collar.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 116 137 84.7 %
Date: 2025-05-15 18:21:54 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          22 : GDALRasterCleanCollarAlgorithm::GDALRasterCleanCollarAlgorithm()
      32          22 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      33             : {
      34          22 :     AddProgressArg();
      35             : 
      36          22 :     AddOpenOptionsArg(&m_openOptions);
      37          22 :     AddInputFormatsArg(&m_inputFormats)
      38          44 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
      39          22 :     AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER);
      40             : 
      41             :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
      42          22 :                         /* positionalAndRequired = */ false)
      43          22 :         .SetPositional()
      44          22 :         .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT);
      45             :     AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
      46          22 :                        /* bGDALGAllowed = */ false)
      47             :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      48          66 :                          {GDAL_DCAP_CREATE, GDAL_DCAP_RASTER});
      49          22 :     AddCreationOptionsArg(&m_creationOptions);
      50          22 :     AddOverwriteArg(&m_overwrite);
      51          22 :     AddUpdateArg(&m_update);
      52             : 
      53             :     AddArg("color", 0,
      54             :            _("Transparent color(s): tuple of integer (like 'r,g,b'), 'black', "
      55             :              "'white'"),
      56          44 :            &m_color)
      57          22 :         .SetDefault("black")
      58          22 :         .SetPackedValuesAllowed(false)
      59             :         .AddValidationAction(
      60           5 :             [this]()
      61             :             {
      62          11 :                 for (const auto &c : m_color)
      63             :                 {
      64           8 :                     if (c != "white" && c != "black")
      65             :                     {
      66             :                         const CPLStringList aosTokens(
      67           4 :                             CSLTokenizeString2(c.c_str(), ",", 0));
      68           8 :                         for (const char *pszToken : aosTokens)
      69             :                         {
      70           5 :                             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           3 :                 return true;
      82          22 :             });
      83             :     AddArg("color-threshold", 0,
      84             :            _("Select how far from specified transparent colors the pixel "
      85             :              "values are considered transparent."),
      86          44 :            &m_colorThreshold)
      87          22 :         .SetDefault(m_colorThreshold)
      88          22 :         .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          44 :            &m_pixelDistance)
      93          22 :         .SetDefault(m_pixelDistance)
      94          22 :         .SetMinValueIncluded(0);
      95             :     AddArg("add-alpha", 0, _("Adds an alpha band to the output dataset."),
      96          44 :            &m_addAlpha)
      97          22 :         .SetMutualExclusionGroup("addalpha-addmask");
      98             :     AddArg("add-mask", 0, _("Adds a mask band to the output dataset."),
      99          44 :            &m_addMask)
     100          22 :         .SetMutualExclusionGroup("addalpha-addmask");
     101          44 :     AddArg("algorithm", 0, _("Algorithm to apply"), &m_algorithm)
     102          22 :         .SetChoices("floodfill", "twopasses")
     103          22 :         .SetDefault(m_algorithm);
     104          22 : }
     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             : 
     124          18 :     const bool dstDSWasNull = poDstDS == nullptr;
     125             : 
     126          18 :     if (dstDSWasNull && !m_outputDataset.IsNameSet() && !m_update)
     127             :     {
     128           2 :         ReportError(CE_Failure, CPLE_AppDefined,
     129             :                     "Output dataset is not specified. If you intend to update "
     130             :                     "the input dataset, set the 'update' option");
     131           2 :         return false;
     132             :     }
     133             : 
     134          16 :     if (!poDstDS && !m_outputDataset.GetName().empty() && poDstDS != poSrcDS)
     135             :     {
     136             :         VSIStatBufL sStat;
     137           5 :         bool fileExists{VSIStatL(m_outputDataset.GetName().c_str(), &sStat) ==
     138           5 :                         0};
     139             : 
     140             :         {
     141          10 :             CPLErrorStateBackuper oCPLErrorHandlerPusher(CPLQuietErrorHandler);
     142           5 :             poDstDS = GDALDataset::FromHandle(GDALOpenEx(
     143           5 :                 m_outputDataset.GetName().c_str(),
     144             :                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR | GDAL_OF_UPDATE,
     145             :                 nullptr, nullptr, nullptr));
     146           5 :             CPLErrorReset();
     147             :         }
     148             : 
     149           5 :         if ((poDstDS || fileExists) && !m_overwrite && !m_update)
     150             :         {
     151           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     152             :                      "Dataset '%s' already exists. Specify the --overwrite "
     153             :                      "option to overwrite it or the --update option to "
     154             :                      "update it.",
     155           0 :                      m_outputDataset.GetName().c_str());
     156           0 :             delete poDstDS;
     157           0 :             return false;
     158             :         }
     159             : 
     160           5 :         if (poDstDS && fileExists && m_overwrite)
     161             :         {
     162             :             // Delete the existing file
     163           0 :             delete poDstDS;
     164           0 :             poDstDS = nullptr;
     165           0 :             if (VSIUnlink(m_outputDataset.GetName().c_str()) != 0)
     166             :             {
     167           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     168             :                          "Failed to delete existing dataset '%s'.",
     169           0 :                          m_outputDataset.GetName().c_str());
     170           0 :                 return false;
     171             :             }
     172             :         }
     173             :     }
     174             : 
     175          32 :     CPLStringList aosOptions;
     176             : 
     177          16 :     if (!m_format.empty())
     178             :     {
     179           4 :         aosOptions.push_back("-of");
     180           4 :         aosOptions.push_back(m_format.c_str());
     181             :     }
     182             : 
     183          17 :     for (const auto &co : m_creationOptions)
     184             :     {
     185           1 :         aosOptions.push_back("-co");
     186           1 :         aosOptions.push_back(co.c_str());
     187             :     }
     188             : 
     189          36 :     for (const auto &color : m_color)
     190             :     {
     191          20 :         aosOptions.push_back("-color");
     192          40 :         std::string osColor;
     193          20 :         int nNonAlphaSrcBands = poSrcDS->GetRasterCount();
     194          40 :         if (nNonAlphaSrcBands &&
     195          20 :             poSrcDS->GetRasterBand(nNonAlphaSrcBands)
     196          20 :                     ->GetColorInterpretation() == GCI_AlphaBand)
     197           1 :             --nNonAlphaSrcBands;
     198          20 :         if (color == "white")
     199             :         {
     200           4 :             for (int i = 0; i < nNonAlphaSrcBands; ++i)
     201             :             {
     202           2 :                 if (i > 0)
     203           0 :                     osColor += ',';
     204           2 :                 osColor += "255";
     205             :             }
     206             :         }
     207          18 :         else if (color == "black")
     208             :         {
     209          30 :             for (int i = 0; i < nNonAlphaSrcBands; ++i)
     210             :             {
     211          15 :                 if (i > 0)
     212           0 :                     osColor += ',';
     213          15 :                 osColor += "0";
     214             :             }
     215             :         }
     216             :         else
     217             :         {
     218           3 :             osColor = color;
     219             :         }
     220          20 :         aosOptions.push_back(osColor.c_str());
     221             :     }
     222             : 
     223          16 :     aosOptions.push_back("-near");
     224          16 :     aosOptions.push_back(CPLSPrintf("%d", m_colorThreshold));
     225             : 
     226          16 :     aosOptions.push_back("-nb");
     227          16 :     aosOptions.push_back(CPLSPrintf("%d", m_pixelDistance));
     228             : 
     229          47 :     if (m_addAlpha ||
     230          15 :         (!m_addMask && poDstDS == nullptr && poSrcDS->GetRasterCount() > 0 &&
     231           3 :          poSrcDS->GetRasterBand(poSrcDS->GetRasterCount())
     232          34 :                  ->GetColorInterpretation() == GCI_AlphaBand) ||
     233          15 :         (!m_addMask && poDstDS != nullptr && poDstDS->GetRasterCount() > 0 &&
     234          10 :          poDstDS->GetRasterBand(poDstDS->GetRasterCount())
     235          10 :                  ->GetColorInterpretation() == GCI_AlphaBand))
     236             :     {
     237           2 :         aosOptions.push_back("-setalpha");
     238             :     }
     239             : 
     240          46 :     if (m_addMask ||
     241          14 :         (!m_addAlpha && poDstDS == nullptr && poSrcDS->GetRasterCount() > 0 &&
     242          33 :          poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET) ||
     243          14 :         (!m_addAlpha && poDstDS != nullptr && poDstDS->GetRasterCount() > 0 &&
     244          10 :          poDstDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET))
     245             :     {
     246           3 :         aosOptions.push_back("-setmask");
     247             :     }
     248             : 
     249          16 :     aosOptions.push_back("-alg");
     250          16 :     aosOptions.push_back(m_algorithm.c_str());
     251             : 
     252             :     std::unique_ptr<GDALNearblackOptions, decltype(&GDALNearblackOptionsFree)>
     253             :         psOptions{GDALNearblackOptionsNew(aosOptions.List(), nullptr),
     254          32 :                   GDALNearblackOptionsFree};
     255          16 :     if (!psOptions)
     256           0 :         return false;
     257             : 
     258          16 :     GDALNearblackOptionsSetProgress(psOptions.get(), pfnProgress,
     259             :                                     pProgressData);
     260             : 
     261          32 :     auto poRetDS = GDALDataset::FromHandle(GDALNearblack(
     262          16 :         m_outputDataset.GetName().c_str(), GDALDataset::ToHandle(poDstDS),
     263          16 :         GDALDataset::ToHandle(poSrcDS), psOptions.get(), nullptr));
     264          16 :     if (!poRetDS)
     265           0 :         return false;
     266             : 
     267          16 :     if (poDstDS == nullptr)
     268             :     {
     269           6 :         m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     270             :     }
     271          10 :     else if (dstDSWasNull)
     272             :     {
     273           0 :         const bool bCloseOK = poDstDS->Close() == CE_None;
     274           0 :         delete poDstDS;
     275           0 :         if (!bCloseOK)
     276             :         {
     277           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     278             :                      "Failed to close output dataset");
     279           0 :             return false;
     280             :         }
     281             :     }
     282             : 
     283          16 :     return true;
     284             : }
     285             : 
     286             : //! @endcond

Generated by: LCOV version 1.14