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