LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_clean_collar.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 124 133 93.2 %
Date: 2026-06-20 20:44:25 Functions: 4 4 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         102 : GDALRasterCleanCollarAlgorithm::GDALRasterCleanCollarAlgorithm(
      32         102 :     bool standaloneStep)
      33             :     : GDALRasterPipelineNonNativelyStreamingAlgorithm(
      34             :           NAME, DESCRIPTION, HELP_URL,
      35           0 :           ConstructorOptions()
      36         102 :               .SetStandaloneStep(standaloneStep)
      37         102 :               .SetAddDefaultArguments(false)
      38         204 :               .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE))
      39             : {
      40         102 :     if (standaloneStep)
      41             :     {
      42          64 :         AddProgressArg();
      43          64 :         AddRasterInputArgs(false, false);
      44             : 
      45             :         AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER,
      46          64 :                             /* positionalAndRequired = */ false)
      47          64 :             .SetDatasetInputFlags(GADV_NAME | GADV_OBJECT)
      48          64 :             .SetAvailableInPipelineStep(false)
      49          64 :             .SetPositional();
      50             :         AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
      51          64 :                            /* bGDALGAllowed = */ false)
      52             :             .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      53         256 :                              {GDAL_DCAP_CREATE, GDAL_DCAP_RASTER})
      54          64 :             .SetAvailableInPipelineStep(false);
      55          64 :         AddCreationOptionsArg(&m_creationOptions)
      56          64 :             .SetAvailableInPipelineStep(false);
      57          64 :         AddOverwriteArg(&m_overwrite).SetAvailableInPipelineStep(false);
      58          64 :         AddUpdateArg(&m_update).SetAvailableInPipelineStep(false);
      59             :     }
      60             :     else
      61             :     {
      62          38 :         AddRasterHiddenInputDatasetArg();
      63             :     }
      64             : 
      65             :     AddArg("color", 0,
      66             :            _("Transparent color(s): tuple of integer (like 'r,g,b'), 'black', "
      67             :              "'white'"),
      68         204 :            &m_color)
      69         102 :         .SetDefault("black")
      70         102 :         .SetPackedValuesAllowed(false)
      71             :         .AddValidationAction(
      72           8 :             [this]()
      73             :             {
      74          21 :                 for (const auto &c : m_color)
      75             :                 {
      76          15 :                     if (c != "white" && c != "black")
      77             :                     {
      78             :                         const CPLStringList aosTokens(
      79           7 :                             CSLTokenizeString2(c.c_str(), ",", 0));
      80          15 :                         for (const char *pszToken : aosTokens)
      81             :                         {
      82           9 :                             if (CPLGetValueType(pszToken) != CPL_VALUE_INTEGER)
      83             :                             {
      84           1 :                                 ReportError(CE_Failure, CPLE_IllegalArg,
      85             :                                             "Value for 'color' should be tuple "
      86             :                                             "of integer (like 'r,g,b'), "
      87             :                                             "'black' or 'white'");
      88           1 :                                 return false;
      89             :                             }
      90             :                         }
      91             :                     }
      92             :                 }
      93           6 :                 return true;
      94         102 :             });
      95             :     AddArg("color-threshold", 0,
      96             :            _("Select how far from specified transparent colors the pixel "
      97             :              "values are considered transparent."),
      98         204 :            &m_colorThreshold)
      99         102 :         .SetDefault(m_colorThreshold)
     100         102 :         .SetMinValueIncluded(0);
     101             :     AddArg("pixel-distance", 0,
     102             :            _("Number of consecutive transparent pixels that can be encountered "
     103             :              "before the giving up search inwards."),
     104         204 :            &m_pixelDistance)
     105         102 :         .SetDefault(m_pixelDistance)
     106         102 :         .SetMinValueIncluded(0);
     107             :     AddArg("add-alpha", 0, _("Adds an alpha band to the output dataset."),
     108         204 :            &m_addAlpha)
     109         102 :         .SetMutualExclusionGroup("addalpha-addmask");
     110             :     AddArg("add-mask", 0, _("Adds a mask band to the output dataset."),
     111         204 :            &m_addMask)
     112         102 :         .SetMutualExclusionGroup("addalpha-addmask");
     113         204 :     AddArg("algorithm", 0, _("Algorithm to apply"), &m_algorithm)
     114         102 :         .SetChoices("floodfill", "twopasses")
     115         102 :         .SetDefault(m_algorithm);
     116         102 : }
     117             : 
     118             : /************************************************************************/
     119             : /*              GDALRasterCleanCollarAlgorithm::RunImpl()               */
     120             : /************************************************************************/
     121             : 
     122          18 : bool GDALRasterCleanCollarAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
     123             :                                              void *pProgressData)
     124             : {
     125          18 :     GDALPipelineStepRunContext stepCtxt;
     126          18 :     stepCtxt.m_pfnProgress = pfnProgress;
     127          18 :     stepCtxt.m_pProgressData = pProgressData;
     128          18 :     return RunPreStepPipelineValidations() && RunStep(stepCtxt);
     129             : }
     130             : 
     131             : /************************************************************************/
     132             : /*              GDALRasterCleanCollarAlgorithm::RunStep()               */
     133             : /************************************************************************/
     134             : 
     135          19 : bool GDALRasterCleanCollarAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     136             : {
     137          19 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     138          19 :     CPLAssert(poSrcDS);
     139             : 
     140          19 :     auto poDstDS = m_outputDataset.GetDatasetRef();
     141          19 :     if (!m_standaloneStep)
     142             :     {
     143           1 :         m_outputDataset.Set(CPLGenerateTempFilenameSafe("_clean_collar") +
     144             :                             ".tif");
     145           1 :         m_creationOptions.push_back("TILED=YES");
     146             :     }
     147             :     else
     148             :     {
     149          18 :         if (poSrcDS == poDstDS && poSrcDS->GetAccess() == GA_ReadOnly)
     150             :         {
     151           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     152             :                         "Dataset should be opened in update mode");
     153           0 :             return false;
     154             :         }
     155             : 
     156          18 :         if (!poDstDS && !m_outputDataset.IsNameSet())
     157             :         {
     158           9 :             if (m_update)
     159             :             {
     160           7 :                 m_outputDataset.Set(poSrcDS);
     161           7 :                 poDstDS = poSrcDS;
     162             :             }
     163             :             else
     164             :             {
     165           2 :                 ReportError(
     166             :                     CE_Failure, CPLE_AppDefined,
     167             :                     "Output dataset is not specified. If you intend to update "
     168             :                     "the input dataset, set the 'update' option");
     169           2 :                 return false;
     170             :             }
     171             :         }
     172             :     }
     173             : 
     174          34 :     CPLStringList aosOptions;
     175             : 
     176          17 :     if (!m_format.empty())
     177             :     {
     178           4 :         aosOptions.push_back("-of");
     179           4 :         aosOptions.push_back(m_format.c_str());
     180             :     }
     181             : 
     182          19 :     for (const auto &co : m_creationOptions)
     183             :     {
     184           2 :         aosOptions.push_back("-co");
     185           2 :         aosOptions.push_back(co.c_str());
     186             :     }
     187             : 
     188          38 :     for (const auto &color : m_color)
     189             :     {
     190          21 :         aosOptions.push_back("-color");
     191          42 :         std::string osColor;
     192          21 :         int nNonAlphaSrcBands = poSrcDS->GetRasterCount();
     193          42 :         if (nNonAlphaSrcBands &&
     194          21 :             poSrcDS->GetRasterBand(nNonAlphaSrcBands)
     195          21 :                     ->GetColorInterpretation() == GCI_AlphaBand)
     196           1 :             --nNonAlphaSrcBands;
     197          21 :         if (color == "white")
     198             :         {
     199           4 :             for (int i = 0; i < nNonAlphaSrcBands; ++i)
     200             :             {
     201           2 :                 if (i > 0)
     202           0 :                     osColor += ',';
     203           2 :                 osColor += "255";
     204             :             }
     205             :         }
     206          19 :         else if (color == "black")
     207             :         {
     208          32 :             for (int i = 0; i < nNonAlphaSrcBands; ++i)
     209             :             {
     210          16 :                 if (i > 0)
     211           0 :                     osColor += ',';
     212          16 :                 osColor += "0";
     213             :             }
     214             :         }
     215             :         else
     216             :         {
     217           3 :             osColor = color;
     218             :         }
     219          21 :         aosOptions.push_back(osColor.c_str());
     220             :     }
     221             : 
     222          17 :     aosOptions.push_back("-near");
     223          17 :     aosOptions.push_back(CPLSPrintf("%d", m_colorThreshold));
     224             : 
     225          17 :     aosOptions.push_back("-nb");
     226          17 :     aosOptions.push_back(CPLSPrintf("%d", m_pixelDistance));
     227             : 
     228          50 :     if (m_addAlpha ||
     229          16 :         (!m_addMask && poDstDS == nullptr && poSrcDS->GetRasterCount() > 0 &&
     230           4 :          poSrcDS->GetRasterBand(poSrcDS->GetRasterCount())
     231          37 :                  ->GetColorInterpretation() == GCI_AlphaBand) ||
     232          16 :         (!m_addMask && poDstDS != nullptr && poDstDS->GetRasterCount() > 0 &&
     233          10 :          poDstDS->GetRasterBand(poDstDS->GetRasterCount())
     234          10 :                  ->GetColorInterpretation() == GCI_AlphaBand))
     235             :     {
     236           2 :         aosOptions.push_back("-setalpha");
     237             :     }
     238             : 
     239          49 :     if (m_addMask ||
     240          15 :         (!m_addAlpha && poDstDS == nullptr && poSrcDS->GetRasterCount() > 0 &&
     241          36 :          poSrcDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET) ||
     242          15 :         (!m_addAlpha && poDstDS != nullptr && poDstDS->GetRasterCount() > 0 &&
     243          10 :          poDstDS->GetRasterBand(1)->GetMaskFlags() == GMF_PER_DATASET))
     244             :     {
     245           3 :         aosOptions.push_back("-setmask");
     246             :     }
     247             : 
     248          17 :     aosOptions.push_back("-alg");
     249          17 :     aosOptions.push_back(m_algorithm.c_str());
     250             : 
     251             :     std::unique_ptr<GDALNearblackOptions, decltype(&GDALNearblackOptionsFree)>
     252             :         psOptions{GDALNearblackOptionsNew(aosOptions.List(), nullptr),
     253          34 :                   GDALNearblackOptionsFree};
     254          17 :     if (!psOptions)
     255           0 :         return false;
     256             : 
     257          17 :     GDALNearblackOptionsSetProgress(psOptions.get(), ctxt.m_pfnProgress,
     258             :                                     ctxt.m_pProgressData);
     259             : 
     260          34 :     auto poRetDS = GDALDataset::FromHandle(GDALNearblack(
     261          17 :         m_outputDataset.GetName().c_str(), GDALDataset::ToHandle(poDstDS),
     262          17 :         GDALDataset::ToHandle(poSrcDS), psOptions.get(), nullptr));
     263          17 :     if (!poRetDS)
     264             :     {
     265           0 :         if (!m_standaloneStep)
     266           0 :             VSIUnlink(m_outputDataset.GetName().c_str());
     267           0 :         return false;
     268             :     }
     269             : 
     270          17 :     if (poDstDS == nullptr)
     271             :     {
     272           7 :         if (!m_standaloneStep)
     273           1 :             poRetDS->MarkSuppressOnClose();
     274           7 :         m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     275             :     }
     276             :     else
     277             :     {
     278          10 :         CPLAssert(poRetDS == poDstDS);
     279             :     }
     280             : 
     281          17 :     return true;
     282             : }
     283             : 
     284             : GDALRasterCleanCollarAlgorithmStandalone::
     285             :     ~GDALRasterCleanCollarAlgorithmStandalone() = default;
     286             : 
     287             : //! @endcond

Generated by: LCOV version 1.14