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
|