Coverage Report

Created: 2026-07-14 06:21

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