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: 123 134 91.8 %
Date: 2026-02-01 11:59:10 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          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

Generated by: LCOV version 1.14