Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster rgb-to-palette" 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_rgb_to_palette.h"
14 :
15 : #include "cpl_string.h"
16 : #include "gdal_alg.h"
17 : #include "gdal_alg_priv.h"
18 : #include "gdal_priv.h"
19 :
20 : #include <algorithm>
21 : #include <limits>
22 :
23 : //! @cond Doxygen_Suppress
24 :
25 : #ifndef _
26 : #define _(x) (x)
27 : #endif
28 :
29 : /************************************************************************/
30 : /* GDALRasterRGBToPaletteAlgorithm() */
31 : /************************************************************************/
32 :
33 104 : GDALRasterRGBToPaletteAlgorithm::GDALRasterRGBToPaletteAlgorithm(
34 104 : bool standaloneStep)
35 : : GDALRasterPipelineNonNativelyStreamingAlgorithm(NAME, DESCRIPTION,
36 104 : HELP_URL, standaloneStep)
37 : {
38 : AddArg("color-count", 0,
39 : _("Select the number of colors in the generated color table"),
40 208 : &m_colorCount)
41 104 : .SetDefault(m_colorCount)
42 104 : .SetMinValueIncluded(2)
43 104 : .SetMaxValueIncluded(256);
44 104 : AddArg("color-map", 0, _("Color map filename"), &m_colorMap);
45 208 : AddArg("output-nodata", 0, _("Output nodata value"), &m_dstNoData)
46 208 : .AddHiddenAlias("dst-nodata")
47 104 : .SetMinValueIncluded(0)
48 104 : .SetMaxValueIncluded(255);
49 104 : AddArg("no-dither", 0, _("Disable Floyd-Steinberg dithering"), &m_noDither);
50 : AddArg("bit-depth", 0,
51 : _("Bit depth of color palette component (8 bit causes longer "
52 : "computation time)"),
53 208 : &m_bitDepth)
54 104 : .SetDefault(m_bitDepth)
55 104 : .SetChoices("5", "8");
56 104 : }
57 :
58 : /************************************************************************/
59 : /* GDALRasterRGBToPaletteAlgorithm::RunStep() */
60 : /************************************************************************/
61 :
62 25 : bool GDALRasterRGBToPaletteAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
63 : {
64 25 : auto pfnProgress = ctxt.m_pfnProgress;
65 25 : auto pProgressData = ctxt.m_pProgressData;
66 25 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
67 25 : CPLAssert(poSrcDS);
68 :
69 25 : const int nSrcBandCount = poSrcDS->GetRasterCount();
70 25 : if (nSrcBandCount < 3)
71 : {
72 1 : ReportError(CE_Failure, CPLE_NotSupported,
73 : "Input dataset must have at least 3 bands");
74 1 : return false;
75 : }
76 27 : else if (nSrcBandCount == 4 &&
77 3 : poSrcDS->GetRasterBand(4)->GetColorInterpretation() ==
78 : GCI_AlphaBand)
79 : {
80 : // nothing to do
81 : }
82 21 : else if (nSrcBandCount >= 4)
83 : {
84 0 : ReportError(
85 : CE_Warning, CPLE_AppDefined,
86 : "Only R,G,B bands of input dataset will be taken into account");
87 : }
88 :
89 48 : std::map<GDALColorInterp, GDALRasterBandH> mapBands;
90 24 : int nFound = 0;
91 94 : for (int i = 1; i <= nSrcBandCount; ++i)
92 : {
93 72 : auto poSrcBand = poSrcDS->GetRasterBand(i);
94 72 : if (poSrcBand->GetRasterDataType() != GDT_UInt8)
95 : {
96 1 : ReportError(CE_Failure, CPLE_NotSupported,
97 : "Non-byte band found and not supported");
98 2 : return false;
99 : }
100 71 : const auto eColorInterp = poSrcBand->GetColorInterpretation();
101 210 : for (const auto eInterestColorInterp :
102 281 : {GCI_RedBand, GCI_GreenBand, GCI_BlueBand})
103 : {
104 211 : if (eColorInterp == eInterestColorInterp)
105 : {
106 64 : if (mapBands.find(eColorInterp) != mapBands.end())
107 : {
108 1 : ReportError(CE_Failure, CPLE_NotSupported,
109 : "Several %s bands found",
110 : GDALGetColorInterpretationName(eColorInterp));
111 1 : return false;
112 : }
113 63 : ++nFound;
114 63 : mapBands[eColorInterp] = GDALRasterBand::ToHandle(poSrcBand);
115 : }
116 : }
117 : }
118 :
119 22 : if (nFound < 3)
120 : {
121 2 : if (nFound > 0)
122 : {
123 1 : ReportError(
124 : CE_Warning, CPLE_AppDefined,
125 : "Assuming first band is red, second green and third blue, "
126 : "despite at least one band with one of those color "
127 : "interpretation "
128 : "found");
129 : }
130 2 : mapBands[GCI_RedBand] =
131 2 : GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(1));
132 2 : mapBands[GCI_GreenBand] =
133 2 : GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(2));
134 2 : mapBands[GCI_BlueBand] =
135 2 : GDALRasterBand::ToHandle(poSrcDS->GetRasterBand(3));
136 : }
137 :
138 : auto poTmpDS = CreateTemporaryDataset(
139 : poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), 1, GDT_UInt8,
140 44 : /* bTiledIfPossible = */ true, poSrcDS, /* bCopyMetadata = */ true);
141 22 : if (!poTmpDS)
142 1 : return false;
143 :
144 21 : const double oneOverStep = 1.0 / ((m_colorMap.empty() ? 1 : 0) + 1);
145 :
146 21 : if (m_colorMap.empty() && m_dstNoData < 0)
147 : {
148 15 : int bSrcHasNoDataR = FALSE;
149 : const double dfSrcNoDataR =
150 15 : GDALGetRasterNoDataValue(mapBands[GCI_RedBand], &bSrcHasNoDataR);
151 15 : int bSrcHasNoDataG = FALSE;
152 : const double dfSrcNoDataG =
153 15 : GDALGetRasterNoDataValue(mapBands[GCI_GreenBand], &bSrcHasNoDataG);
154 15 : int bSrcHasNoDataB = FALSE;
155 : const double dfSrcNoDataB =
156 15 : GDALGetRasterNoDataValue(mapBands[GCI_BlueBand], &bSrcHasNoDataB);
157 15 : if (bSrcHasNoDataR && bSrcHasNoDataG && bSrcHasNoDataB &&
158 0 : dfSrcNoDataR == dfSrcNoDataG && dfSrcNoDataR == dfSrcNoDataB &&
159 0 : dfSrcNoDataR >= 0 && dfSrcNoDataR <= 255 &&
160 0 : std::round(dfSrcNoDataR) == dfSrcNoDataR)
161 : {
162 0 : m_dstNoData = 0;
163 : }
164 : else
165 : {
166 15 : const int nMaskFlags = GDALGetMaskFlags(mapBands[GCI_RedBand]);
167 15 : if ((nMaskFlags & GMF_PER_DATASET))
168 3 : m_dstNoData = 0;
169 : }
170 : }
171 :
172 42 : GDALColorTable oCT;
173 :
174 21 : bool bOK = true;
175 21 : double dfLastProgress = 0;
176 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
177 21 : nullptr, GDALDestroyScaledProgress);
178 21 : if (m_colorMap.empty())
179 : {
180 17 : pScaledData.reset(GDALCreateScaledProgress(0, oneOverStep, pfnProgress,
181 : pProgressData));
182 17 : dfLastProgress = oneOverStep;
183 :
184 17 : const int nXSize = poSrcDS->GetRasterXSize();
185 17 : const int nYSize = poSrcDS->GetRasterYSize();
186 :
187 17 : if (m_dstNoData >= 0 && m_colorCount == 256)
188 5 : --m_colorCount;
189 17 : if (nYSize == 0)
190 : {
191 0 : bOK = false;
192 : }
193 17 : else if (static_cast<GUInt32>(nXSize) <
194 17 : std::numeric_limits<GUInt32>::max() /
195 17 : static_cast<GUInt32>(nYSize))
196 : {
197 17 : bOK =
198 34 : GDALComputeMedianCutPCTInternal(
199 17 : mapBands[GCI_RedBand], mapBands[GCI_GreenBand],
200 34 : mapBands[GCI_BlueBand], nullptr, nullptr, nullptr, nullptr,
201 : m_colorCount, m_bitDepth, static_cast<GUInt32 *>(nullptr),
202 : GDALColorTable::ToHandle(&oCT),
203 17 : pScaledData ? GDALScaledProgress : nullptr,
204 : pScaledData.get()) == CE_None;
205 : }
206 : else
207 : {
208 0 : bOK =
209 0 : GDALComputeMedianCutPCTInternal(
210 0 : mapBands[GCI_RedBand], mapBands[GCI_GreenBand],
211 0 : mapBands[GCI_BlueBand], nullptr, nullptr, nullptr, nullptr,
212 : m_colorCount, m_bitDepth, static_cast<GUIntBig *>(nullptr),
213 : GDALColorTable::ToHandle(&oCT),
214 0 : pScaledData ? GDALScaledProgress : nullptr,
215 : pScaledData.get()) == CE_None;
216 : }
217 : }
218 : else
219 : {
220 : GDALDriverH hDriver;
221 4 : if ((hDriver = GDALIdentifyDriver(m_colorMap.c_str(), nullptr)) !=
222 7 : nullptr &&
223 : // Palette .txt files may be misidentified by the XYZ driver
224 3 : !EQUAL(GDALGetDescription(hDriver), "XYZ"))
225 : {
226 : auto poPaletteDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
227 : m_colorMap.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
228 4 : nullptr, nullptr, nullptr));
229 2 : bOK = poPaletteDS != nullptr && poPaletteDS->GetRasterCount() > 0;
230 2 : if (bOK)
231 : {
232 : const auto poCT =
233 2 : poPaletteDS->GetRasterBand(1)->GetColorTable();
234 2 : if (poCT)
235 : {
236 1 : oCT = *poCT;
237 : }
238 : else
239 : {
240 1 : bOK = false;
241 1 : ReportError(CE_Failure, CPLE_AppDefined,
242 : "Dataset '%s' does not contain a color table",
243 : m_colorMap.c_str());
244 : }
245 : }
246 : }
247 : else
248 : {
249 4 : auto poCT = GDALColorTable::LoadFromFile(m_colorMap.c_str());
250 2 : bOK = poCT != nullptr;
251 2 : if (bOK)
252 : {
253 1 : oCT = std::move(*(poCT.get()));
254 : }
255 : }
256 : }
257 :
258 21 : m_colorCount = oCT.GetColorEntryCount();
259 :
260 21 : if (m_dstNoData >= 0)
261 : {
262 516 : for (int i = std::min(255, m_colorCount); i > m_dstNoData; --i)
263 : {
264 : // Create a temporary copy to avoid use after free when SetColorEntry()
265 : // resizes the underlying vector.
266 511 : const GDALColorEntry sEntry = *(oCT.GetColorEntry(i - 1));
267 511 : oCT.SetColorEntry(i, &sEntry);
268 : }
269 :
270 5 : poTmpDS->GetRasterBand(1)->SetNoDataValue(m_dstNoData);
271 5 : GDALColorEntry sEntry = {0, 0, 0, 0};
272 5 : oCT.SetColorEntry(m_dstNoData, &sEntry);
273 : }
274 :
275 21 : if (bOK)
276 : {
277 19 : poTmpDS->GetRasterBand(1)->SetColorTable(&oCT);
278 :
279 19 : pScaledData.reset(GDALCreateScaledProgress(dfLastProgress, 1.0,
280 : pfnProgress, pProgressData));
281 :
282 57 : bOK = GDALDitherRGB2PCTInternal(
283 19 : mapBands[GCI_RedBand], mapBands[GCI_GreenBand],
284 38 : mapBands[GCI_BlueBand],
285 : GDALRasterBand::ToHandle(poTmpDS->GetRasterBand(1)),
286 : GDALColorTable::ToHandle(&oCT), m_bitDepth,
287 19 : /* pasDynamicColorMap = */ nullptr, !m_noDither,
288 19 : pScaledData ? GDALScaledProgress : nullptr,
289 : pScaledData.get()) == CE_None;
290 : }
291 :
292 21 : if (bOK)
293 : {
294 19 : m_outputDataset.Set(std::move(poTmpDS));
295 19 : if (pfnProgress)
296 3 : pfnProgress(1.0, "", pProgressData);
297 : }
298 :
299 21 : return bOK;
300 : }
301 :
302 : GDALRasterRGBToPaletteAlgorithmStandalone::
303 : ~GDALRasterRGBToPaletteAlgorithmStandalone() = default;
304 :
305 : //! @endcond
|