Coverage Report

Created: 2026-02-14 06:52

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