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 101 : GDALRasterRGBToPaletteAlgorithm::GDALRasterRGBToPaletteAlgorithm(
34 101 : bool standaloneStep)
35 : : GDALRasterPipelineNonNativelyStreamingAlgorithm(NAME, DESCRIPTION,
36 101 : HELP_URL, standaloneStep)
37 : {
38 : AddArg("color-count", 0,
39 : _("Select the number of colors in the generated color table"),
40 202 : &m_colorCount)
41 101 : .SetDefault(m_colorCount)
42 101 : .SetMinValueIncluded(2)
43 101 : .SetMaxValueIncluded(256);
44 101 : AddArg("color-map", 0, _("Color map filename"), &m_colorMap);
45 202 : AddArg("output-nodata", 0, _("Output nodata value"), &m_dstNoData)
46 202 : .AddHiddenAlias("dst-nodata")
47 101 : .SetMinValueIncluded(0)
48 101 : .SetMaxValueIncluded(255);
49 101 : 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 202 : &m_bitDepth)
54 101 : .SetDefault(m_bitDepth)
55 101 : .SetChoices("5", "8");
56 101 : }
57 :
58 : /************************************************************************/
59 : /* GDALRasterRGBToPaletteAlgorithm::RunStep() */
60 : /************************************************************************/
61 :
62 23 : bool GDALRasterRGBToPaletteAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
63 : {
64 23 : auto pfnProgress = ctxt.m_pfnProgress;
65 23 : auto pProgressData = ctxt.m_pProgressData;
66 23 : auto poSrcDS = m_inputDataset[0].GetDatasetRef();
67 23 : CPLAssert(poSrcDS);
68 :
69 23 : const int nSrcBandCount = poSrcDS->GetRasterCount();
70 23 : 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 23 : else if (nSrcBandCount == 4 &&
77 1 : 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 44 : std::map<GDALColorInterp, GDALRasterBandH> mapBands;
90 22 : int nFound = 0;
91 84 : for (int i = 1; i <= nSrcBandCount; ++i)
92 : {
93 64 : auto poSrcBand = poSrcDS->GetRasterBand(i);
94 64 : 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 63 : const auto eColorInterp = poSrcBand->GetColorInterpretation();
101 186 : for (const auto eInterestColorInterp :
102 249 : {GCI_RedBand, GCI_GreenBand, GCI_BlueBand})
103 : {
104 187 : if (eColorInterp == eInterestColorInterp)
105 : {
106 58 : 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 57 : ++nFound;
114 57 : mapBands[eColorInterp] = GDALRasterBand::ToHandle(poSrcBand);
115 : }
116 : }
117 : }
118 :
119 20 : 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 40 : /* bTiledIfPossible = */ true, poSrcDS, /* bCopyMetadata = */ true);
141 20 : if (!poTmpDS)
142 1 : return false;
143 :
144 19 : const double oneOverStep = 1.0 / ((m_colorMap.empty() ? 1 : 0) + 1);
145 :
146 19 : if (m_colorMap.empty() && m_dstNoData < 0)
147 : {
148 13 : int bSrcHasNoDataR = FALSE;
149 : const double dfSrcNoDataR =
150 13 : GDALGetRasterNoDataValue(mapBands[GCI_RedBand], &bSrcHasNoDataR);
151 13 : int bSrcHasNoDataG = FALSE;
152 : const double dfSrcNoDataG =
153 13 : GDALGetRasterNoDataValue(mapBands[GCI_GreenBand], &bSrcHasNoDataG);
154 13 : int bSrcHasNoDataB = FALSE;
155 : const double dfSrcNoDataB =
156 13 : GDALGetRasterNoDataValue(mapBands[GCI_BlueBand], &bSrcHasNoDataB);
157 13 : 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 13 : const int nMaskFlags = GDALGetMaskFlags(mapBands[GCI_RedBand]);
167 13 : if ((nMaskFlags & GMF_PER_DATASET))
168 1 : m_dstNoData = 0;
169 : }
170 : }
171 :
172 38 : GDALColorTable oCT;
173 :
174 19 : bool bOK = true;
175 19 : double dfLastProgress = 0;
176 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
177 19 : nullptr, GDALDestroyScaledProgress);
178 19 : if (m_colorMap.empty())
179 : {
180 15 : pScaledData.reset(GDALCreateScaledProgress(0, oneOverStep, pfnProgress,
181 : pProgressData));
182 15 : dfLastProgress = oneOverStep;
183 :
184 15 : const int nXSize = poSrcDS->GetRasterXSize();
185 15 : const int nYSize = poSrcDS->GetRasterYSize();
186 :
187 15 : if (m_dstNoData >= 0 && m_colorCount == 256)
188 3 : --m_colorCount;
189 15 : if (nYSize == 0)
190 : {
191 0 : bOK = false;
192 : }
193 15 : else if (static_cast<GUInt32>(nXSize) <
194 15 : std::numeric_limits<GUInt32>::max() /
195 15 : static_cast<GUInt32>(nYSize))
196 : {
197 15 : bOK =
198 30 : GDALComputeMedianCutPCTInternal(
199 15 : mapBands[GCI_RedBand], mapBands[GCI_GreenBand],
200 30 : mapBands[GCI_BlueBand], nullptr, nullptr, nullptr, nullptr,
201 : m_colorCount, m_bitDepth, static_cast<GUInt32 *>(nullptr),
202 : GDALColorTable::ToHandle(&oCT),
203 15 : 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 4 : m_colorCount = oCT.GetColorEntryCount();
258 : }
259 :
260 19 : if (m_dstNoData >= 0)
261 : {
262 513 : for (int i = std::min(255, m_colorCount); i > m_dstNoData; --i)
263 : {
264 510 : oCT.SetColorEntry(i, oCT.GetColorEntry(i - 1));
265 : }
266 :
267 3 : poTmpDS->GetRasterBand(1)->SetNoDataValue(m_dstNoData);
268 3 : GDALColorEntry sEntry = {0, 0, 0, 0};
269 3 : oCT.SetColorEntry(m_dstNoData, &sEntry);
270 : }
271 :
272 19 : if (bOK)
273 : {
274 17 : poTmpDS->GetRasterBand(1)->SetColorTable(&oCT);
275 :
276 17 : pScaledData.reset(GDALCreateScaledProgress(dfLastProgress, 1.0,
277 : pfnProgress, pProgressData));
278 :
279 51 : bOK = GDALDitherRGB2PCTInternal(
280 17 : mapBands[GCI_RedBand], mapBands[GCI_GreenBand],
281 34 : mapBands[GCI_BlueBand],
282 : GDALRasterBand::ToHandle(poTmpDS->GetRasterBand(1)),
283 : GDALColorTable::ToHandle(&oCT), m_bitDepth,
284 17 : /* pasDynamicColorMap = */ nullptr, !m_noDither,
285 17 : pScaledData ? GDALScaledProgress : nullptr,
286 : pScaledData.get()) == CE_None;
287 : }
288 :
289 19 : if (bOK)
290 : {
291 17 : m_outputDataset.Set(std::move(poTmpDS));
292 17 : if (pfnProgress)
293 3 : pfnProgress(1.0, "", pProgressData);
294 : }
295 :
296 19 : return bOK;
297 : }
298 :
299 : GDALRasterRGBToPaletteAlgorithmStandalone::
300 : ~GDALRasterRGBToPaletteAlgorithmStandalone() = default;
301 :
302 : //! @endcond
|