LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_rgb_to_palette.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 124 135 91.9 %
Date: 2026-04-23 19:47:11 Functions: 2 2 100.0 %

          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

Generated by: LCOV version 1.14