LCOV - code coverage report
Current view: top level - gcore - gdalcolortable.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 214 284 75.4 %
Date: 2025-10-14 18:17:50 Functions: 23 25 92.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL Core
       4             :  * Purpose:  Color table implementation.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  **********************************************************************
       8             :  * Copyright (c) 2000, Frank Warmerdam
       9             :  * Copyright (c) 2009, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "gdal_priv.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <cstring>
      19             : #include <exception>
      20             : #include <memory>
      21             : #include <vector>
      22             : 
      23             : #include "cpl_error.h"
      24             : #include "cpl_minixml.h"
      25             : #include "cpl_vsi_virtual.h"
      26             : #include "gdal.h"
      27             : 
      28             : /************************************************************************/
      29             : /*                           GDALColorTable()                           */
      30             : /************************************************************************/
      31             : 
      32             : /**
      33             :  * \brief Construct a new color table.
      34             :  *
      35             :  * This constructor is the same as the C GDALCreateColorTable() function.
      36             :  *
      37             :  * @param eInterpIn the interpretation to be applied to GDALColorEntry
      38             :  * values.
      39             :  */
      40             : 
      41        1863 : GDALColorTable::GDALColorTable(GDALPaletteInterp eInterpIn) : eInterp(eInterpIn)
      42             : {
      43        1863 : }
      44             : 
      45             : /************************************************************************/
      46             : /*                        GDALCreateColorTable()                        */
      47             : /************************************************************************/
      48             : 
      49             : /**
      50             :  * \brief Construct a new color table.
      51             :  *
      52             :  * This function is the same as the C++ method GDALColorTable::GDALColorTable()
      53             :  */
      54          56 : GDALColorTableH CPL_STDCALL GDALCreateColorTable(GDALPaletteInterp eInterp)
      55             : 
      56             : {
      57          56 :     return GDALColorTable::ToHandle(new GDALColorTable(eInterp));
      58             : }
      59             : 
      60             : /************************************************************************/
      61             : /*                          ~GDALColorTable()                           */
      62             : /************************************************************************/
      63             : 
      64             : /**
      65             :  * \brief Destructor.
      66             :  *
      67             :  * This destructor is the same as the C GDALDestroyColorTable() function.
      68             :  */
      69             : 
      70             : GDALColorTable::~GDALColorTable() = default;
      71             : 
      72             : /************************************************************************/
      73             : /*                       GDALDestroyColorTable()                        */
      74             : /************************************************************************/
      75             : 
      76             : /**
      77             :  * \brief Destroys a color table.
      78             :  *
      79             :  * This function is the same as the C++ method GDALColorTable::~GDALColorTable()
      80             :  */
      81          68 : void CPL_STDCALL GDALDestroyColorTable(GDALColorTableH hTable)
      82             : 
      83             : {
      84          68 :     delete GDALColorTable::FromHandle(hTable);
      85          68 : }
      86             : 
      87             : /************************************************************************/
      88             : /*                           GetColorEntry()                            */
      89             : /************************************************************************/
      90             : 
      91             : /**
      92             :  * \brief Fetch a color entry from table.
      93             :  *
      94             :  * This method is the same as the C function GDALGetColorEntry().
      95             :  *
      96             :  * @param i entry offset from zero to GetColorEntryCount()-1.
      97             :  *
      98             :  * @return pointer to internal color entry, or NULL if index is out of range.
      99             :  */
     100             : 
     101     2099540 : const GDALColorEntry *GDALColorTable::GetColorEntry(int i) const
     102             : 
     103             : {
     104     2099540 :     if (i < 0 || i >= static_cast<int>(aoEntries.size()))
     105           0 :         return nullptr;
     106             : 
     107     2099540 :     return &aoEntries[i];
     108             : }
     109             : 
     110             : /************************************************************************/
     111             : /*                         GDALGetColorEntry()                          */
     112             : /************************************************************************/
     113             : 
     114             : /**
     115             :  * \brief Fetch a color entry from table.
     116             :  *
     117             :  * This function is the same as the C++ method GDALColorTable::GetColorEntry()
     118             :  */
     119        6625 : const GDALColorEntry *CPL_STDCALL GDALGetColorEntry(GDALColorTableH hTable,
     120             :                                                     int i)
     121             : 
     122             : {
     123        6625 :     VALIDATE_POINTER1(hTable, "GDALGetColorEntry", nullptr);
     124             : 
     125        6625 :     return GDALColorTable::FromHandle(hTable)->GetColorEntry(i);
     126             : }
     127             : 
     128             : /************************************************************************/
     129             : /*                         GetColorEntryAsRGB()                         */
     130             : /************************************************************************/
     131             : 
     132             : /**
     133             :  * \brief Fetch a table entry in RGB format.
     134             :  *
     135             :  * In theory this method should support translation of color palettes in
     136             :  * non-RGB color spaces into RGB on the fly, but currently it only works
     137             :  * on RGB color tables.
     138             :  *
     139             :  * This method is the same as the C function GDALGetColorEntryAsRGB().
     140             :  *
     141             :  * @param i entry offset from zero to GetColorEntryCount()-1.
     142             :  *
     143             :  * @param poEntry the existing GDALColorEntry to be overwritten with the RGB
     144             :  * values.
     145             :  *
     146             :  * @return TRUE on success, or FALSE if the conversion isn't supported.
     147             :  */
     148             : 
     149       97910 : int GDALColorTable::GetColorEntryAsRGB(int i, GDALColorEntry *poEntry) const
     150             : 
     151             : {
     152       97910 :     if (eInterp != GPI_RGB || i < 0 || i >= static_cast<int>(aoEntries.size()))
     153           0 :         return FALSE;
     154             : 
     155       97910 :     *poEntry = aoEntries[i];
     156       97910 :     return TRUE;
     157             : }
     158             : 
     159             : /************************************************************************/
     160             : /*                       GDALGetColorEntryAsRGB()                       */
     161             : /************************************************************************/
     162             : 
     163             : /**
     164             :  * \brief Fetch a table entry in RGB format.
     165             :  *
     166             :  * This function is the same as the C++ method
     167             :  * GDALColorTable::GetColorEntryAsRGB().
     168             :  */
     169        7424 : int CPL_STDCALL GDALGetColorEntryAsRGB(GDALColorTableH hTable, int i,
     170             :                                        GDALColorEntry *poEntry)
     171             : 
     172             : {
     173        7424 :     VALIDATE_POINTER1(hTable, "GDALGetColorEntryAsRGB", 0);
     174        7424 :     VALIDATE_POINTER1(poEntry, "GDALGetColorEntryAsRGB", 0);
     175             : 
     176        7424 :     return GDALColorTable::FromHandle(hTable)->GetColorEntryAsRGB(i, poEntry);
     177             : }
     178             : 
     179             : /************************************************************************/
     180             : /*                           SetColorEntry()                            */
     181             : /************************************************************************/
     182             : 
     183             : /**
     184             :  * \brief Set entry in color table.
     185             :  *
     186             :  * Note that the passed in color entry is copied, and no internal reference
     187             :  * to it is maintained.  Also, the passed in entry must match the color
     188             :  * interpretation of the table to which it is being assigned.
     189             :  *
     190             :  * The table is grown as needed to hold the supplied offset.
     191             :  *
     192             :  * This function is the same as the C function GDALSetColorEntry().
     193             :  *
     194             :  * @param i entry offset from zero to GetColorEntryCount()-1.
     195             :  * @param poEntry value to assign to table.
     196             :  */
     197             : 
     198      508044 : void GDALColorTable::SetColorEntry(int i, const GDALColorEntry *poEntry)
     199             : 
     200             : {
     201      508044 :     if (i < 0)
     202           0 :         return;
     203             : 
     204             :     try
     205             :     {
     206      508044 :         if (i >= static_cast<int>(aoEntries.size()))
     207             :         {
     208      118090 :             GDALColorEntry oBlack = {0, 0, 0, 0};
     209      118090 :             aoEntries.resize(i + 1, oBlack);
     210             :         }
     211             : 
     212      508044 :         aoEntries[i] = *poEntry;
     213             :     }
     214           0 :     catch (std::exception &e)
     215             :     {
     216           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
     217             :     }
     218             : }
     219             : 
     220             : /************************************************************************/
     221             : /*                         GDALSetColorEntry()                          */
     222             : /************************************************************************/
     223             : 
     224             : /**
     225             :  * \brief Set entry in color table.
     226             :  *
     227             :  * This function is the same as the C++ method GDALColorTable::SetColorEntry()
     228             :  */
     229        6780 : void CPL_STDCALL GDALSetColorEntry(GDALColorTableH hTable, int i,
     230             :                                    const GDALColorEntry *poEntry)
     231             : 
     232             : {
     233        6780 :     VALIDATE_POINTER0(hTable, "GDALSetColorEntry");
     234        6780 :     VALIDATE_POINTER0(poEntry, "GDALSetColorEntry");
     235             : 
     236        6780 :     GDALColorTable::FromHandle(hTable)->SetColorEntry(i, poEntry);
     237             : }
     238             : 
     239             : /************************************************************************/
     240             : /*                               Clone()                                */
     241             : /************************************************************************/
     242             : 
     243             : /**
     244             :  * \brief Make a copy of a color table.
     245             :  *
     246             :  * This method is the same as the C function GDALCloneColorTable().
     247             :  */
     248             : 
     249         287 : GDALColorTable *GDALColorTable::Clone() const
     250             : 
     251             : {
     252         287 :     return new GDALColorTable(*this);
     253             : }
     254             : 
     255             : /************************************************************************/
     256             : /*                        GDALCloneColorTable()                         */
     257             : /************************************************************************/
     258             : 
     259             : /**
     260             :  * \brief Make a copy of a color table.
     261             :  *
     262             :  * This function is the same as the C++ method GDALColorTable::Clone()
     263             :  */
     264          17 : GDALColorTableH CPL_STDCALL GDALCloneColorTable(GDALColorTableH hTable)
     265             : 
     266             : {
     267          17 :     VALIDATE_POINTER1(hTable, "GDALCloneColorTable", nullptr);
     268             : 
     269          17 :     return GDALColorTable::ToHandle(
     270          17 :         GDALColorTable::FromHandle(hTable)->Clone());
     271             : }
     272             : 
     273             : /************************************************************************/
     274             : /*                         GetColorEntryCount()                         */
     275             : /************************************************************************/
     276             : 
     277             : /**
     278             :  * \brief Get number of color entries in table.
     279             :  *
     280             :  * This method is the same as the function GDALGetColorEntryCount().
     281             :  *
     282             :  * @return the number of color entries.
     283             :  */
     284             : 
     285      150325 : int GDALColorTable::GetColorEntryCount() const
     286             : 
     287             : {
     288      150325 :     return static_cast<int>(aoEntries.size());
     289             : }
     290             : 
     291             : /************************************************************************/
     292             : /*                       GDALGetColorEntryCount()                       */
     293             : /************************************************************************/
     294             : 
     295             : /**
     296             :  * \brief Get number of color entries in table.
     297             :  *
     298             :  * This function is the same as the C++ method
     299             :  * GDALColorTable::GetColorEntryCount()
     300             :  */
     301        1118 : int CPL_STDCALL GDALGetColorEntryCount(GDALColorTableH hTable)
     302             : 
     303             : {
     304        1118 :     VALIDATE_POINTER1(hTable, "GDALGetColorEntryCount", 0);
     305             : 
     306        1118 :     return GDALColorTable::FromHandle(hTable)->GetColorEntryCount();
     307             : }
     308             : 
     309             : /************************************************************************/
     310             : /*                      GetPaletteInterpretation()                      */
     311             : /************************************************************************/
     312             : 
     313             : /**
     314             :  * \brief Fetch palette interpretation.
     315             :  *
     316             :  * The returned value is used to interpret the values in the GDALColorEntry.
     317             :  *
     318             :  * This method is the same as the C function GDALGetPaletteInterpretation().
     319             :  *
     320             :  * @return palette interpretation enumeration value, usually GPI_RGB.
     321             :  */
     322             : 
     323          19 : GDALPaletteInterp GDALColorTable::GetPaletteInterpretation() const
     324             : 
     325             : {
     326          19 :     return eInterp;
     327             : }
     328             : 
     329             : /************************************************************************/
     330             : /*                    GDALGetPaletteInterpretation()                    */
     331             : /************************************************************************/
     332             : 
     333             : /**
     334             :  * \brief Fetch palette interpretation.
     335             :  *
     336             :  * This function is the same as the C++ method
     337             :  * GDALColorTable::GetPaletteInterpretation()
     338             :  */
     339             : GDALPaletteInterp CPL_STDCALL
     340          10 : GDALGetPaletteInterpretation(GDALColorTableH hTable)
     341             : 
     342             : {
     343          10 :     VALIDATE_POINTER1(hTable, "GDALGetPaletteInterpretation", GPI_Gray);
     344             : 
     345          10 :     return GDALColorTable::FromHandle(hTable)->GetPaletteInterpretation();
     346             : }
     347             : 
     348             : /**
     349             :  * \brief Create color ramp
     350             :  *
     351             :  * Automatically creates a color ramp from one color entry to
     352             :  * another. It can be called several times to create multiples ramps
     353             :  * in the same color table.
     354             :  *
     355             :  * This function is the same as the C function GDALCreateColorRamp().
     356             :  *
     357             :  * @param nStartIndex index to start the ramp on the color table [0..255]
     358             :  * @param psStartColor a color entry value to start the ramp
     359             :  * @param nEndIndex index to end the ramp on the color table [0..255]
     360             :  * @param psEndColor a color entry value to end the ramp
     361             :  * @return total number of entries, -1 to report error
     362             :  */
     363             : 
     364         257 : int GDALColorTable::CreateColorRamp(int nStartIndex,
     365             :                                     const GDALColorEntry *psStartColor,
     366             :                                     int nEndIndex,
     367             :                                     const GDALColorEntry *psEndColor)
     368             : {
     369             :     // Validate indexes.
     370         257 :     if (nStartIndex < 0 || nStartIndex > 255 || nEndIndex < 0 ||
     371         257 :         nEndIndex > 255 || nStartIndex > nEndIndex)
     372             :     {
     373           0 :         return -1;
     374             :     }
     375             : 
     376             :     // Validate color entries.
     377         257 :     if (psStartColor == nullptr || psEndColor == nullptr)
     378             :     {
     379           0 :         return -1;
     380             :     }
     381             : 
     382             :     // Calculate number of colors in-between + 1.
     383         257 :     const int nColors = nEndIndex - nStartIndex;
     384             : 
     385             :     // Set starting color.
     386         257 :     SetColorEntry(nStartIndex, psStartColor);
     387             : 
     388         257 :     if (nColors == 0)
     389             :     {
     390           0 :         return GetColorEntryCount();  // Only one color.  No ramp to create.
     391             :     }
     392             : 
     393             :     // Set ending color.
     394         257 :     SetColorEntry(nEndIndex, psEndColor);
     395             : 
     396             :     // Calculate the slope of the linear transformation.
     397         257 :     const double dfColors = static_cast<double>(nColors);
     398         257 :     const double dfSlope1 = (psEndColor->c1 - psStartColor->c1) / dfColors;
     399         257 :     const double dfSlope2 = (psEndColor->c2 - psStartColor->c2) / dfColors;
     400         257 :     const double dfSlope3 = (psEndColor->c3 - psStartColor->c3) / dfColors;
     401         257 :     const double dfSlope4 = (psEndColor->c4 - psStartColor->c4) / dfColors;
     402             : 
     403             :     // Loop through the new colors.
     404         257 :     GDALColorEntry sColor = *psStartColor;
     405             : 
     406         764 :     for (int i = 1; i < nColors; i++)
     407             :     {
     408         507 :         sColor.c1 = static_cast<short>(i * dfSlope1 + psStartColor->c1);
     409         507 :         sColor.c2 = static_cast<short>(i * dfSlope2 + psStartColor->c2);
     410         507 :         sColor.c3 = static_cast<short>(i * dfSlope3 + psStartColor->c3);
     411         507 :         sColor.c4 = static_cast<short>(i * dfSlope4 + psStartColor->c4);
     412             : 
     413         507 :         SetColorEntry(nStartIndex + i, &sColor);
     414             :     }
     415             : 
     416             :     // Return the total number of colors.
     417         257 :     return GetColorEntryCount();
     418             : }
     419             : 
     420             : /************************************************************************/
     421             : /*                         GDALCreateColorRamp()                        */
     422             : /************************************************************************/
     423             : 
     424             : /**
     425             :  * \brief Create color ramp
     426             :  *
     427             :  * This function is the same as the C++ method GDALColorTable::CreateColorRamp()
     428             :  */
     429           1 : void CPL_STDCALL GDALCreateColorRamp(GDALColorTableH hTable, int nStartIndex,
     430             :                                      const GDALColorEntry *psStartColor,
     431             :                                      int nEndIndex,
     432             :                                      const GDALColorEntry *psEndColor)
     433             : {
     434           1 :     VALIDATE_POINTER0(hTable, "GDALCreateColorRamp");
     435             : 
     436           1 :     GDALColorTable::FromHandle(hTable)->CreateColorRamp(
     437             :         nStartIndex, psStartColor, nEndIndex, psEndColor);
     438             : }
     439             : 
     440             : /************************************************************************/
     441             : /*                           IsSame()                                   */
     442             : /************************************************************************/
     443             : 
     444             : /**
     445             :  * \brief Returns if the current color table is the same as another one.
     446             :  *
     447             :  * @param poOtherCT other color table to be compared to.
     448             :  * @return TRUE if both color tables are identical.
     449             :  */
     450             : 
     451          14 : int GDALColorTable::IsSame(const GDALColorTable *poOtherCT) const
     452             : {
     453          42 :     return aoEntries.size() == poOtherCT->aoEntries.size() &&
     454          14 :            (aoEntries.empty() ||
     455          14 :             memcmp(&aoEntries[0], &poOtherCT->aoEntries[0],
     456          28 :                    aoEntries.size() * sizeof(GDALColorEntry)) == 0);
     457             : }
     458             : 
     459             : /************************************************************************/
     460             : /*                          IsIdentity()                                */
     461             : /************************************************************************/
     462             : 
     463             : /**
     464             :  * \brief Returns if the current color table is the identity, that is
     465             :  * for each index i, colortable[i].c1 = .c2 = .c3 = i and .c4 = 255
     466             :  *
     467             :  * @since GDAL 3.4.1
     468             :  */
     469             : 
     470          18 : bool GDALColorTable::IsIdentity() const
     471             : {
     472         290 :     for (int i = 0; i < static_cast<int>(aoEntries.size()); ++i)
     473             :     {
     474         562 :         if (aoEntries[i].c1 != i || aoEntries[i].c2 != i ||
     475         562 :             aoEntries[i].c3 != i || aoEntries[i].c4 != 255)
     476             :         {
     477          17 :             return false;
     478             :         }
     479             :     }
     480           1 :     return true;
     481             : }
     482             : 
     483             : /************************************************************************/
     484             : /*                          FindRasterRenderer()                        */
     485             : /************************************************************************/
     486             : 
     487         271 : static bool FindRasterRenderer(const CPLXMLNode *const psNode,
     488             :                                bool bVisitSibblings, const CPLXMLNode *&psRet)
     489             : {
     490         271 :     bool bRet = true;
     491             : 
     492         271 :     if (psNode->eType == CXT_Element &&
     493         269 :         strcmp(psNode->pszValue, "rasterrenderer") == 0)
     494             :     {
     495           2 :         const char *pszType = CPLGetXMLValue(psNode, "type", "");
     496           2 :         if (strcmp(pszType, "paletted") == 0 ||
     497           1 :             strcmp(pszType, "singlebandpseudocolor") == 0)
     498             :         {
     499           2 :             bRet = psRet == nullptr;
     500           2 :             if (bRet)
     501             :             {
     502           2 :                 psRet = psNode;
     503             :             }
     504             :         }
     505             :     }
     506             : 
     507        1196 :     for (const CPLXMLNode *psIter = psNode->psChild; bRet && psIter;
     508         925 :          psIter = psIter->psNext)
     509             :     {
     510         925 :         if (psIter->eType == CXT_Element)
     511         267 :             bRet = FindRasterRenderer(psIter, false, psRet);
     512             :     }
     513             : 
     514         271 :     if (bVisitSibblings)
     515             :     {
     516           4 :         for (const CPLXMLNode *psIter = psNode->psNext; bRet && psIter;
     517           2 :              psIter = psIter->psNext)
     518             :         {
     519           2 :             if (psIter->eType == CXT_Element)
     520           2 :                 bRet = FindRasterRenderer(psIter, false, psRet);
     521             :         }
     522             :     }
     523             : 
     524         271 :     return bRet;
     525             : }
     526             : 
     527           2 : static const CPLXMLNode *FindRasterRenderer(const CPLXMLNode *psNode)
     528             : {
     529           2 :     const CPLXMLNode *psRet = nullptr;
     530           2 :     if (!FindRasterRenderer(psNode, true, psRet))
     531             :     {
     532           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     533             :                  "Several raster renderers with color tables found");
     534           0 :         return nullptr;
     535             :     }
     536           2 :     if (!psRet)
     537             :     {
     538           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No color table found");
     539           0 :         return nullptr;
     540             :     }
     541           2 :     return psRet;
     542             : }
     543             : 
     544             : /************************************************************************/
     545             : /*                            LoadFromFile()                            */
     546             : /************************************************************************/
     547             : 
     548             : /**
     549             :  * \brief Load a color table from a (text) file.
     550             :  *
     551             :  * Supported formats are:
     552             :  * - QGIS Layer Style File (.qml) or QGIS Layer Definition File (.qlr) using
     553             :  *   "Palette/unique values" raster renderer or "Single band pseudocolor" renderer
     554             :  * - GMT or GRASS text files, when entry index are integers
     555             :  *
     556             :  * @return a new color table, or NULL in case of error.
     557             :  * @since GDAL 3.12
     558             :  */
     559             : 
     560             : /* static */
     561             : std::unique_ptr<GDALColorTable>
     562           6 : GDALColorTable::LoadFromFile(const char *pszFilename)
     563             : {
     564          12 :     const std::string osExt = CPLGetExtensionSafe(pszFilename);
     565          12 :     auto poCT = std::make_unique<GDALColorTable>();
     566           6 :     if (EQUAL(osExt.c_str(), "qlr") || EQUAL(osExt.c_str(), "qml"))
     567             :     {
     568           2 :         GByte *pabyData = nullptr;
     569           2 :         if (!VSIIngestFile(nullptr, pszFilename, &pabyData, nullptr,
     570             :                            10 * 1024 * 1024))
     571           0 :             return nullptr;
     572             :         CPLXMLTreeCloser oTree(
     573           2 :             CPLParseXMLString(reinterpret_cast<const char *>(pabyData)));
     574           2 :         CPLFree(pabyData);
     575           2 :         if (!oTree)
     576           0 :             return nullptr;
     577           2 :         const CPLXMLNode *psRasterRenderer = FindRasterRenderer(oTree.get());
     578           2 :         if (!psRasterRenderer)
     579             :         {
     580           0 :             return nullptr;
     581             :         }
     582           2 :         const char *pszType = CPLGetXMLValue(psRasterRenderer, "type", "");
     583           2 :         const char *pszColorEntryNodeName =
     584           2 :             strcmp(pszType, "paletted") == 0 ? "paletteEntry" : "item";
     585             :         const CPLXMLNode *psEntry =
     586           2 :             CPLSearchXMLNode(psRasterRenderer, pszColorEntryNodeName);
     587           2 :         if (!psEntry)
     588             :         {
     589           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find color entry");
     590           0 :             return nullptr;
     591             :         }
     592          26 :         for (; psEntry; psEntry = psEntry->psNext)
     593             :         {
     594          24 :             if (psEntry->eType == CXT_Element &&
     595          24 :                 strcmp(psEntry->pszValue, pszColorEntryNodeName) == 0)
     596             :             {
     597             :                 // <paletteEntry value="74" label="74" alpha="255" color="#431be1"/>
     598             :                 // <item value="74" label="74" alpha="255" color="#ffffcc"/>
     599          23 :                 char *pszEndPtr = nullptr;
     600          23 :                 const char *pszValue = CPLGetXMLValue(psEntry, "value", "");
     601          23 :                 const auto nVal = std::strtol(pszValue, &pszEndPtr, 10);
     602          23 :                 if (pszEndPtr != pszValue + strlen(pszValue) || nVal < 0 ||
     603             :                     nVal > 65536)
     604             :                 {
     605           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     606             :                              "Unsupported value '%s' for color entry. Only "
     607             :                              "integer value in [0, 65535] range supported",
     608             :                              pszValue);
     609           0 :                     return nullptr;
     610             :                 }
     611             : 
     612          23 :                 long nAlpha = 255;
     613             :                 const char *pszAlpha =
     614          23 :                     CPLGetXMLValue(psEntry, "alpha", nullptr);
     615          23 :                 if (pszAlpha && pszEndPtr == pszAlpha + strlen(pszAlpha))
     616             :                 {
     617           0 :                     nAlpha = std::clamp<long>(
     618           0 :                         std::strtol(pszAlpha, &pszEndPtr, 10), 0, 255);
     619             :                 }
     620             : 
     621          23 :                 long nColor = 0;
     622          23 :                 const char *pszColor = CPLGetXMLValue(psEntry, "color", "");
     623          46 :                 if (strlen(pszColor) == 7 && pszColor[0] == '#' &&
     624          46 :                     (nColor = std::strtol(pszColor + 1, &pszEndPtr, 16)) >= 0 &&
     625          46 :                     nColor <= 0xFFFFFF &&
     626          23 :                     pszEndPtr == pszColor + strlen(pszColor))
     627             :                 {
     628             :                     GDALColorEntry sColor;
     629          23 :                     sColor.c1 = static_cast<GByte>((nColor >> 16) & 0xFF);
     630          23 :                     sColor.c2 = static_cast<GByte>((nColor >> 8) & 0xFF);
     631          23 :                     sColor.c3 = static_cast<GByte>(nColor & 0xFF);
     632          23 :                     sColor.c4 = static_cast<GByte>(nAlpha);
     633          23 :                     poCT->SetColorEntry(static_cast<int>(nVal), &sColor);
     634             :                 }
     635             :                 else
     636             :                 {
     637           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     638             :                              "Unsupported color '%s' for color entry.",
     639             :                              pszColor);
     640           0 :                     return nullptr;
     641             :                 }
     642             :             }
     643             :         }
     644             :     }
     645             :     else
     646             :     {
     647           4 :         const auto asEntries = GDALLoadTextColorMap(pszFilename, nullptr);
     648           4 :         if (asEntries.empty())
     649           2 :             return nullptr;
     650         514 :         for (const auto &sEntry : asEntries)
     651             :         {
     652         512 :             if (!(sEntry.dfVal >= 0 && sEntry.dfVal <= 65536 &&
     653         512 :                   static_cast<int>(sEntry.dfVal) == sEntry.dfVal))
     654             :             {
     655           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     656             :                          "Unsupported value '%f' for color entry. Only integer "
     657             :                          "value in [0, 65535] range supported",
     658           0 :                          sEntry.dfVal);
     659           0 :                 return nullptr;
     660             :             }
     661             :             GDALColorEntry sColor;
     662         512 :             sColor.c1 = static_cast<GByte>(sEntry.nR);
     663         512 :             sColor.c2 = static_cast<GByte>(sEntry.nG);
     664         512 :             sColor.c3 = static_cast<GByte>(sEntry.nB);
     665         512 :             sColor.c4 = static_cast<GByte>(sEntry.nA);
     666         512 :             poCT->SetColorEntry(static_cast<int>(sEntry.dfVal), &sColor);
     667             :         }
     668             :     }
     669           4 :     return poCT;
     670             : }
     671             : 
     672             : /************************************************************************/
     673             : /*                    GDALGetAbsoluteValFromPct()                       */
     674             : /************************************************************************/
     675             : 
     676             : /* dfPct : percentage between 0 and 1 */
     677           0 : static double GDALGetAbsoluteValFromPct(GDALRasterBand *poBand, double dfPct)
     678             : {
     679           0 :     int bSuccessMin = FALSE;
     680           0 :     int bSuccessMax = FALSE;
     681           0 :     double dfMin = poBand->GetMinimum(&bSuccessMin);
     682           0 :     double dfMax = poBand->GetMaximum(&bSuccessMax);
     683           0 :     if (!bSuccessMin || !bSuccessMax)
     684             :     {
     685           0 :         double dfMean = 0.0;
     686           0 :         double dfStdDev = 0.0;
     687           0 :         CPLDebug("GDAL", "Computing source raster statistics...");
     688           0 :         poBand->ComputeStatistics(false, &dfMin, &dfMax, &dfMean, &dfStdDev,
     689           0 :                                   nullptr, nullptr);
     690             :     }
     691           0 :     return dfMin + dfPct * (dfMax - dfMin);
     692             : }
     693             : 
     694             : /************************************************************************/
     695             : /*                     GDALFindNamedColor()                             */
     696             : /************************************************************************/
     697             : 
     698           0 : static bool GDALFindNamedColor(const char *pszColorName, int *pnR, int *pnG,
     699             :                                int *pnB)
     700             : {
     701             : 
     702             :     typedef struct
     703             :     {
     704             :         const char *name;
     705             :         float r, g, b;
     706             :     } NamedColor;
     707             : 
     708             :     static const NamedColor namedColors[] = {
     709             :         {"white", 1.00, 1.00, 1.00},   {"black", 0.00, 0.00, 0.00},
     710             :         {"red", 1.00, 0.00, 0.00},     {"green", 0.00, 1.00, 0.00},
     711             :         {"blue", 0.00, 0.00, 1.00},    {"yellow", 1.00, 1.00, 0.00},
     712             :         {"magenta", 1.00, 0.00, 1.00}, {"cyan", 0.00, 1.00, 1.00},
     713             :         {"aqua", 0.00, 0.75, 0.75},    {"grey", 0.75, 0.75, 0.75},
     714             :         {"gray", 0.75, 0.75, 0.75},    {"orange", 1.00, 0.50, 0.00},
     715             :         {"brown", 0.75, 0.50, 0.25},   {"purple", 0.50, 0.00, 1.00},
     716             :         {"violet", 0.50, 0.00, 1.00},  {"indigo", 0.00, 0.50, 1.00},
     717             :     };
     718             : 
     719           0 :     *pnR = 0;
     720           0 :     *pnG = 0;
     721           0 :     *pnB = 0;
     722           0 :     for (const auto &namedColor : namedColors)
     723             :     {
     724           0 :         if (EQUAL(pszColorName, namedColor.name))
     725             :         {
     726           0 :             *pnR = static_cast<int>(255.0f * namedColor.r);
     727           0 :             *pnG = static_cast<int>(255.0f * namedColor.g);
     728           0 :             *pnB = static_cast<int>(255.0f * namedColor.b);
     729           0 :             return true;
     730             :         }
     731             :     }
     732           0 :     return false;
     733             : }
     734             : 
     735             : /************************************************************************/
     736             : /*                          GDALLoadTextColorMap()                      */
     737             : /************************************************************************/
     738             : 
     739             : /**
     740             :  * \brief Load a color map from a GMT or GRASS text file.
     741             :  * @since GDAL 3.12
     742             :  */
     743             : 
     744          47 : std::vector<GDALColorAssociation> GDALLoadTextColorMap(const char *pszFilename,
     745             :                                                        GDALRasterBand *poBand)
     746             : {
     747          94 :     auto fpColorFile = VSIVirtualHandleUniquePtr(VSIFOpenL(pszFilename, "rt"));
     748          47 :     if (fpColorFile == nullptr)
     749             :     {
     750           3 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszFilename);
     751           3 :         return {};
     752             :     }
     753             : 
     754          88 :     std::vector<GDALColorAssociation> asColorAssociation;
     755             : 
     756          44 :     int bHasNoData = FALSE;
     757             :     const double dfNoDataValue =
     758          44 :         poBand ? poBand->GetNoDataValue(&bHasNoData) : 0.0;
     759             : 
     760          44 :     bool bIsGMT_CPT = false;
     761             :     GDALColorAssociation sColor;
     762             :     while (const char *pszLine =
     763         795 :                CPLReadLine2L(fpColorFile.get(), 10 * 1024, nullptr))
     764             :     {
     765         751 :         if (pszLine[0] == '#' && strstr(pszLine, "COLOR_MODEL"))
     766             :         {
     767           1 :             if (strstr(pszLine, "COLOR_MODEL = RGB") == nullptr)
     768             :             {
     769           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     770             :                          "Only COLOR_MODEL = RGB is supported");
     771           0 :                 return {};
     772             :             }
     773           1 :             bIsGMT_CPT = true;
     774             :         }
     775             : 
     776         751 :         if (pszLine[0] == '#' || pszLine[0] == '/' || pszLine[0] == 0)
     777             :         {
     778             :             /* Skip comment and blank lines */
     779           4 :             continue;
     780             :         }
     781             : 
     782             :         const CPLStringList aosFields(
     783         748 :             CSLTokenizeStringComplex(pszLine, " ,\t:", FALSE, FALSE));
     784         748 :         const int nTokens = aosFields.size();
     785             : 
     786         748 :         if (bIsGMT_CPT && nTokens == 8)
     787             :         {
     788           3 :             sColor.dfVal = CPLAtof(aosFields[0]);
     789           3 :             sColor.nR = atoi(aosFields[1]);
     790           3 :             sColor.nG = atoi(aosFields[2]);
     791           3 :             sColor.nB = atoi(aosFields[3]);
     792           3 :             sColor.nA = 255;
     793           3 :             asColorAssociation.push_back(sColor);
     794             : 
     795           3 :             sColor.dfVal = CPLAtof(aosFields[4]);
     796           3 :             sColor.nR = atoi(aosFields[5]);
     797           3 :             sColor.nG = atoi(aosFields[6]);
     798           3 :             sColor.nB = atoi(aosFields[7]);
     799           3 :             sColor.nA = 255;
     800           3 :             asColorAssociation.push_back(sColor);
     801             :         }
     802         745 :         else if (bIsGMT_CPT && nTokens == 4)
     803             :         {
     804             :             // The first token might be B (background), F (foreground) or N
     805             :             // (nodata) Just interested in N.
     806           3 :             if (EQUAL(aosFields[0], "N") && bHasNoData)
     807             :             {
     808           1 :                 sColor.dfVal = dfNoDataValue;
     809           1 :                 sColor.nR = atoi(aosFields[1]);
     810           1 :                 sColor.nG = atoi(aosFields[2]);
     811           1 :                 sColor.nB = atoi(aosFields[3]);
     812           1 :                 sColor.nA = 255;
     813           1 :                 asColorAssociation.push_back(sColor);
     814             :             }
     815             :         }
     816         742 :         else if (!bIsGMT_CPT && nTokens >= 2)
     817             :         {
     818         742 :             if (EQUAL(aosFields[0], "nv"))
     819             :             {
     820           5 :                 if (!bHasNoData)
     821             :                 {
     822           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
     823             :                              "Input dataset has no nodata value. "
     824             :                              "Ignoring 'nv' entry in color palette");
     825           1 :                     continue;
     826             :                 }
     827           4 :                 sColor.dfVal = dfNoDataValue;
     828             :             }
     829        1403 :             else if (strlen(aosFields[0]) > 1 &&
     830         666 :                      aosFields[0][strlen(aosFields[0]) - 1] == '%')
     831             :             {
     832           0 :                 const double dfPct = CPLAtof(aosFields[0]) / 100.0;
     833           0 :                 if (dfPct < 0.0 || dfPct > 1.0)
     834             :                 {
     835           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     836             :                              "Wrong value for a percentage : %s", aosFields[0]);
     837           0 :                     return {};
     838             :                 }
     839           0 :                 if (!poBand)
     840             :                 {
     841           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     842             :                              "Percentage value not supported");
     843           0 :                     return {};
     844             :                 }
     845           0 :                 sColor.dfVal = GDALGetAbsoluteValFromPct(poBand, dfPct);
     846             :             }
     847             :             else
     848             :             {
     849         737 :                 sColor.dfVal = CPLAtof(aosFields[0]);
     850             :             }
     851             : 
     852         741 :             if (nTokens >= 4)
     853             :             {
     854         741 :                 sColor.nR = atoi(aosFields[1]);
     855         741 :                 sColor.nG = atoi(aosFields[2]);
     856         741 :                 sColor.nB = atoi(aosFields[3]);
     857         741 :                 sColor.nA =
     858         741 :                     (CSLCount(aosFields) >= 5) ? atoi(aosFields[4]) : 255;
     859             :             }
     860             :             else
     861             :             {
     862           0 :                 int nR = 0;
     863           0 :                 int nG = 0;
     864           0 :                 int nB = 0;
     865           0 :                 if (!GDALFindNamedColor(aosFields[1], &nR, &nG, &nB))
     866             :                 {
     867           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Unknown color : %s",
     868             :                              aosFields[1]);
     869           0 :                     return {};
     870             :                 }
     871           0 :                 sColor.nR = nR;
     872           0 :                 sColor.nG = nG;
     873           0 :                 sColor.nB = nB;
     874           0 :                 sColor.nA =
     875           0 :                     (CSLCount(aosFields) >= 3) ? atoi(aosFields[2]) : 255;
     876             :             }
     877         741 :             asColorAssociation.push_back(sColor);
     878             :         }
     879         751 :     }
     880             : 
     881          44 :     if (asColorAssociation.empty())
     882             :     {
     883           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     884             :                  "No color association found in %s", pszFilename);
     885             :     }
     886          44 :     return asColorAssociation;
     887             : }

Generated by: LCOV version 1.14