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
|