Coverage Report

Created: 2026-07-25 06:50

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