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-05-31 00:00:17 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        1829 : GDALColorTable::GDALColorTable(GDALPaletteInterp eInterpIn) : eInterp(eInterpIn)
      42             : {
      43        1829 : }
      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     2099740 : const GDALColorEntry *GDALColorTable::GetColorEntry(int i) const
     102             : 
     103             : {
     104     2099740 :     if (i < 0 || i >= static_cast<int>(aoEntries.size()))
     105           0 :         return nullptr;
     106             : 
     107     2099740 :     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        6575 : const GDALColorEntry *CPL_STDCALL GDALGetColorEntry(GDALColorTableH hTable,
     120             :                                                     int i)
     121             : 
     122             : {
     123        6575 :     VALIDATE_POINTER1(hTable, "GDALGetColorEntry", nullptr);
     124             : 
     125        6575 :     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 overrwritten with the RGB
     144             :  * values.
     145             :  *
     146             :  * @return TRUE on success, or FALSE if the conversion isn't supported.
     147             :  */
     148             : 
     149       97175 : int GDALColorTable::GetColorEntryAsRGB(int i, GDALColorEntry *poEntry) const
     150             : 
     151             : {
     152       97175 :     if (eInterp != GPI_RGB || i < 0 || i >= static_cast<int>(aoEntries.size()))
     153           0 :         return FALSE;
     154             : 
     155       97175 :     *poEntry = aoEntries[i];
     156       97175 :     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        6721 : int CPL_STDCALL GDALGetColorEntryAsRGB(GDALColorTableH hTable, int i,
     170             :                                        GDALColorEntry *poEntry)
     171             : 
     172             : {
     173        6721 :     VALIDATE_POINTER1(hTable, "GDALGetColorEntryAsRGB", 0);
     174        6721 :     VALIDATE_POINTER1(poEntry, "GDALGetColorEntryAsRGB", 0);
     175             : 
     176        6721 :     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      440287 : void GDALColorTable::SetColorEntry(int i, const GDALColorEntry *poEntry)
     199             : 
     200             : {
     201      440287 :     if (i < 0)
     202           0 :         return;
     203             : 
     204             :     try
     205             :     {
     206      440287 :         if (i >= static_cast<int>(aoEntries.size()))
     207             :         {
     208       49868 :             GDALColorEntry oBlack = {0, 0, 0, 0};
     209       49868 :             aoEntries.resize(i + 1, oBlack);
     210             :         }
     211             : 
     212      440287 :         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         285 : GDALColorTable *GDALColorTable::Clone() const
     250             : 
     251             : {
     252         285 :     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      149621 : int GDALColorTable::GetColorEntryCount() const
     286             : 
     287             : {
     288      149621 :     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         413 : int CPL_STDCALL GDALGetColorEntryCount(GDALColorTableH hTable)
     302             : 
     303             : {
     304         413 :     VALIDATE_POINTER1(hTable, "GDALGetColorEntryCount", 0);
     305             : 
     306         413 :     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          18 : GDALPaletteInterp GDALColorTable::GetPaletteInterpretation() const
     324             : 
     325             : {
     326          18 :     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           9 : GDALGetPaletteInterpretation(GDALColorTableH hTable)
     341             : 
     342             : {
     343           9 :     VALIDATE_POINTER1(hTable, "GDALGetPaletteInterpretation", GPI_Gray);
     344             : 
     345           9 :     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             :  * @since GDAL 2.0
     450             :  */
     451             : 
     452          14 : int GDALColorTable::IsSame(const GDALColorTable *poOtherCT) const
     453             : {
     454          42 :     return aoEntries.size() == poOtherCT->aoEntries.size() &&
     455          14 :            (aoEntries.empty() ||
     456          14 :             memcmp(&aoEntries[0], &poOtherCT->aoEntries[0],
     457          28 :                    aoEntries.size() * sizeof(GDALColorEntry)) == 0);
     458             : }
     459             : 
     460             : /************************************************************************/
     461             : /*                          IsIdentity()                                */
     462             : /************************************************************************/
     463             : 
     464             : /**
     465             :  * \brief Returns if the current color table is the identity, that is
     466             :  * for each index i, colortable[i].c1 = .c2 = .c3 = i and .c4 = 255
     467             :  *
     468             :  * @since GDAL 3.4.1
     469             :  */
     470             : 
     471          18 : bool GDALColorTable::IsIdentity() const
     472             : {
     473         290 :     for (int i = 0; i < static_cast<int>(aoEntries.size()); ++i)
     474             :     {
     475         562 :         if (aoEntries[i].c1 != i || aoEntries[i].c2 != i ||
     476         562 :             aoEntries[i].c3 != i || aoEntries[i].c4 != 255)
     477             :         {
     478          17 :             return false;
     479             :         }
     480             :     }
     481           1 :     return true;
     482             : }
     483             : 
     484             : /************************************************************************/
     485             : /*                          FindRasterRenderer()                        */
     486             : /************************************************************************/
     487             : 
     488         271 : static bool FindRasterRenderer(const CPLXMLNode *const psNode,
     489             :                                bool bVisitSibblings, const CPLXMLNode *&psRet)
     490             : {
     491         271 :     bool bRet = true;
     492             : 
     493         271 :     if (psNode->eType == CXT_Element &&
     494         269 :         strcmp(psNode->pszValue, "rasterrenderer") == 0)
     495             :     {
     496           2 :         const char *pszType = CPLGetXMLValue(psNode, "type", "");
     497           2 :         if (strcmp(pszType, "paletted") == 0 ||
     498           1 :             strcmp(pszType, "singlebandpseudocolor") == 0)
     499             :         {
     500           2 :             bRet = psRet == nullptr;
     501           2 :             if (bRet)
     502             :             {
     503           2 :                 psRet = psNode;
     504             :             }
     505             :         }
     506             :     }
     507             : 
     508        1196 :     for (const CPLXMLNode *psIter = psNode->psChild; bRet && psIter;
     509         925 :          psIter = psIter->psNext)
     510             :     {
     511         925 :         if (psIter->eType == CXT_Element)
     512         267 :             bRet = FindRasterRenderer(psIter, false, psRet);
     513             :     }
     514             : 
     515         271 :     if (bVisitSibblings)
     516             :     {
     517           4 :         for (const CPLXMLNode *psIter = psNode->psNext; bRet && psIter;
     518           2 :              psIter = psIter->psNext)
     519             :         {
     520           2 :             if (psIter->eType == CXT_Element)
     521           2 :                 bRet = FindRasterRenderer(psIter, false, psRet);
     522             :         }
     523             :     }
     524             : 
     525         271 :     return bRet;
     526             : }
     527             : 
     528           2 : static const CPLXMLNode *FindRasterRenderer(const CPLXMLNode *psNode)
     529             : {
     530           2 :     const CPLXMLNode *psRet = nullptr;
     531           2 :     if (!FindRasterRenderer(psNode, true, psRet))
     532             :     {
     533           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     534             :                  "Several raster renderers with color tables found");
     535           0 :         return nullptr;
     536             :     }
     537           2 :     if (!psRet)
     538             :     {
     539           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No color table found");
     540           0 :         return nullptr;
     541             :     }
     542           2 :     return psRet;
     543             : }
     544             : 
     545             : /************************************************************************/
     546             : /*                            LoadFromFile()                            */
     547             : /************************************************************************/
     548             : 
     549             : /**
     550             :  * \brief Load a color table from a (text) file.
     551             :  *
     552             :  * Supported formats are:
     553             :  * - QGIS Layer Style File (.qml) or QGIS Layer Definition File (.qlr) using
     554             :  *   "Palette/unique values" raster renderer or "Singleband pseudocolor" renderer
     555             :  * - GMT or GRASS text files, when entry index are integers
     556             :  *
     557             :  * @return a new color table, or NULL in case of error.
     558             :  * @since GDAL 3.12
     559             :  */
     560             : 
     561             : /* static */
     562             : std::unique_ptr<GDALColorTable>
     563           6 : GDALColorTable::LoadFromFile(const char *pszFilename)
     564             : {
     565          12 :     const std::string osExt = CPLGetExtensionSafe(pszFilename);
     566          12 :     auto poCT = std::make_unique<GDALColorTable>();
     567           6 :     if (EQUAL(osExt.c_str(), "qlr") || EQUAL(osExt.c_str(), "qml"))
     568             :     {
     569           2 :         GByte *pabyData = nullptr;
     570           2 :         if (!VSIIngestFile(nullptr, pszFilename, &pabyData, nullptr,
     571             :                            10 * 1024 * 1024))
     572           0 :             return nullptr;
     573             :         CPLXMLTreeCloser oTree(
     574           2 :             CPLParseXMLString(reinterpret_cast<const char *>(pabyData)));
     575           2 :         CPLFree(pabyData);
     576           2 :         if (!oTree)
     577           0 :             return nullptr;
     578           2 :         const CPLXMLNode *psRasterRenderer = FindRasterRenderer(oTree.get());
     579           2 :         if (!psRasterRenderer)
     580             :         {
     581           0 :             return nullptr;
     582             :         }
     583           2 :         const char *pszType = CPLGetXMLValue(psRasterRenderer, "type", "");
     584           2 :         const char *pszColorEntryNodeName =
     585           2 :             strcmp(pszType, "paletted") == 0 ? "paletteEntry" : "item";
     586             :         const CPLXMLNode *psEntry =
     587           2 :             CPLSearchXMLNode(psRasterRenderer, pszColorEntryNodeName);
     588           2 :         if (!psEntry)
     589             :         {
     590           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find color entry");
     591           0 :             return nullptr;
     592             :         }
     593          26 :         for (; psEntry; psEntry = psEntry->psNext)
     594             :         {
     595          24 :             if (psEntry->eType == CXT_Element &&
     596          24 :                 strcmp(psEntry->pszValue, pszColorEntryNodeName) == 0)
     597             :             {
     598             :                 // <paletteEntry value="74" label="74" alpha="255" color="#431be1"/>
     599             :                 // <item value="74" label="74" alpha="255" color="#ffffcc"/>
     600          23 :                 char *pszEndPtr = nullptr;
     601          23 :                 const char *pszValue = CPLGetXMLValue(psEntry, "value", "");
     602          23 :                 const auto nVal = std::strtol(pszValue, &pszEndPtr, 10);
     603          23 :                 if (pszEndPtr != pszValue + strlen(pszValue) || nVal < 0 ||
     604             :                     nVal > 65536)
     605             :                 {
     606           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     607             :                              "Unsupported value '%s' for color entry. Only "
     608             :                              "integer value in [0, 65535] range supported",
     609             :                              pszValue);
     610           0 :                     return nullptr;
     611             :                 }
     612             : 
     613          23 :                 long nAlpha = 255;
     614             :                 const char *pszAlpha =
     615          23 :                     CPLGetXMLValue(psEntry, "alpha", nullptr);
     616          23 :                 if (pszAlpha && pszEndPtr == pszAlpha + strlen(pszAlpha))
     617             :                 {
     618           0 :                     nAlpha = std::clamp<long>(
     619           0 :                         std::strtol(pszAlpha, &pszEndPtr, 10), 0, 255);
     620             :                 }
     621             : 
     622          23 :                 long nColor = 0;
     623          23 :                 const char *pszColor = CPLGetXMLValue(psEntry, "color", "");
     624          46 :                 if (strlen(pszColor) == 7 && pszColor[0] == '#' &&
     625          46 :                     (nColor = std::strtol(pszColor + 1, &pszEndPtr, 16)) >= 0 &&
     626          46 :                     nColor <= 0xFFFFFF &&
     627          23 :                     pszEndPtr == pszColor + strlen(pszColor))
     628             :                 {
     629             :                     GDALColorEntry sColor;
     630          23 :                     sColor.c1 = static_cast<GByte>((nColor >> 16) & 0xFF);
     631          23 :                     sColor.c2 = static_cast<GByte>((nColor >> 8) & 0xFF);
     632          23 :                     sColor.c3 = static_cast<GByte>(nColor & 0xFF);
     633          23 :                     sColor.c4 = static_cast<GByte>(nAlpha);
     634          23 :                     poCT->SetColorEntry(static_cast<int>(nVal), &sColor);
     635             :                 }
     636             :                 else
     637             :                 {
     638           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     639             :                              "Unsupported color '%s' for color entry.",
     640             :                              pszColor);
     641           0 :                     return nullptr;
     642             :                 }
     643             :             }
     644             :         }
     645             :     }
     646             :     else
     647             :     {
     648           4 :         const auto asEntries = GDALLoadTextColorMap(pszFilename, nullptr);
     649           4 :         if (asEntries.empty())
     650           2 :             return nullptr;
     651         514 :         for (const auto &sEntry : asEntries)
     652             :         {
     653         512 :             if (!(sEntry.dfVal >= 0 && sEntry.dfVal <= 65536 &&
     654         512 :                   static_cast<int>(sEntry.dfVal) == sEntry.dfVal))
     655             :             {
     656           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     657             :                          "Unsupported value '%f' for color entry. Only integer "
     658             :                          "value in [0, 65535] range supported",
     659           0 :                          sEntry.dfVal);
     660           0 :                 return nullptr;
     661             :             }
     662             :             GDALColorEntry sColor;
     663         512 :             sColor.c1 = static_cast<GByte>(sEntry.nR);
     664         512 :             sColor.c2 = static_cast<GByte>(sEntry.nG);
     665         512 :             sColor.c3 = static_cast<GByte>(sEntry.nB);
     666         512 :             sColor.c4 = static_cast<GByte>(sEntry.nA);
     667         512 :             poCT->SetColorEntry(static_cast<int>(sEntry.dfVal), &sColor);
     668             :         }
     669             :     }
     670           4 :     return poCT;
     671             : }
     672             : 
     673             : /************************************************************************/
     674             : /*                    GDALGetAbsoluteValFromPct()                       */
     675             : /************************************************************************/
     676             : 
     677             : /* dfPct : percentage between 0 and 1 */
     678           0 : static double GDALGetAbsoluteValFromPct(GDALRasterBand *poBand, double dfPct)
     679             : {
     680           0 :     int bSuccessMin = FALSE;
     681           0 :     int bSuccessMax = FALSE;
     682           0 :     double dfMin = poBand->GetMinimum(&bSuccessMin);
     683           0 :     double dfMax = poBand->GetMaximum(&bSuccessMax);
     684           0 :     if (!bSuccessMin || !bSuccessMax)
     685             :     {
     686           0 :         double dfMean = 0.0;
     687           0 :         double dfStdDev = 0.0;
     688           0 :         CPLDebug("GDAL", "Computing source raster statistics...");
     689           0 :         poBand->ComputeStatistics(false, &dfMin, &dfMax, &dfMean, &dfStdDev,
     690           0 :                                   nullptr, nullptr);
     691             :     }
     692           0 :     return dfMin + dfPct * (dfMax - dfMin);
     693             : }
     694             : 
     695             : /************************************************************************/
     696             : /*                     GDALFindNamedColor()                             */
     697             : /************************************************************************/
     698             : 
     699           0 : static bool GDALFindNamedColor(const char *pszColorName, int *pnR, int *pnG,
     700             :                                int *pnB)
     701             : {
     702             : 
     703             :     typedef struct
     704             :     {
     705             :         const char *name;
     706             :         float r, g, b;
     707             :     } NamedColor;
     708             : 
     709             :     static const NamedColor namedColors[] = {
     710             :         {"white", 1.00, 1.00, 1.00},   {"black", 0.00, 0.00, 0.00},
     711             :         {"red", 1.00, 0.00, 0.00},     {"green", 0.00, 1.00, 0.00},
     712             :         {"blue", 0.00, 0.00, 1.00},    {"yellow", 1.00, 1.00, 0.00},
     713             :         {"magenta", 1.00, 0.00, 1.00}, {"cyan", 0.00, 1.00, 1.00},
     714             :         {"aqua", 0.00, 0.75, 0.75},    {"grey", 0.75, 0.75, 0.75},
     715             :         {"gray", 0.75, 0.75, 0.75},    {"orange", 1.00, 0.50, 0.00},
     716             :         {"brown", 0.75, 0.50, 0.25},   {"purple", 0.50, 0.00, 1.00},
     717             :         {"violet", 0.50, 0.00, 1.00},  {"indigo", 0.00, 0.50, 1.00},
     718             :     };
     719             : 
     720           0 :     *pnR = 0;
     721           0 :     *pnG = 0;
     722           0 :     *pnB = 0;
     723           0 :     for (const auto &namedColor : namedColors)
     724             :     {
     725           0 :         if (EQUAL(pszColorName, namedColor.name))
     726             :         {
     727           0 :             *pnR = static_cast<int>(255.0 * namedColor.r);
     728           0 :             *pnG = static_cast<int>(255.0 * namedColor.g);
     729           0 :             *pnB = static_cast<int>(255.0 * namedColor.b);
     730           0 :             return true;
     731             :         }
     732             :     }
     733           0 :     return false;
     734             : }
     735             : 
     736             : /************************************************************************/
     737             : /*                          GDALLoadTextColorMap()                      */
     738             : /************************************************************************/
     739             : 
     740             : /**
     741             :  * \brief Load a color map from a GMT or GRASS text file.
     742             :  * @since GDAL 3.12
     743             :  */
     744             : 
     745          45 : std::vector<GDALColorAssociation> GDALLoadTextColorMap(const char *pszFilename,
     746             :                                                        GDALRasterBand *poBand)
     747             : {
     748          90 :     auto fpColorFile = VSIVirtualHandleUniquePtr(VSIFOpenL(pszFilename, "rt"));
     749          45 :     if (fpColorFile == nullptr)
     750             :     {
     751           3 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s", pszFilename);
     752           3 :         return {};
     753             :     }
     754             : 
     755          84 :     std::vector<GDALColorAssociation> asColorAssociation;
     756             : 
     757          42 :     int bHasNoData = FALSE;
     758             :     const double dfNoDataValue =
     759          42 :         poBand ? poBand->GetNoDataValue(&bHasNoData) : 0.0;
     760             : 
     761          42 :     bool bIsGMT_CPT = false;
     762             :     GDALColorAssociation sColor;
     763             :     while (const char *pszLine =
     764         779 :                CPLReadLine2L(fpColorFile.get(), 10 * 1024, nullptr))
     765             :     {
     766         737 :         if (pszLine[0] == '#' && strstr(pszLine, "COLOR_MODEL"))
     767             :         {
     768           1 :             if (strstr(pszLine, "COLOR_MODEL = RGB") == nullptr)
     769             :             {
     770           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     771             :                          "Only COLOR_MODEL = RGB is supported");
     772           0 :                 return {};
     773             :             }
     774           1 :             bIsGMT_CPT = true;
     775             :         }
     776             : 
     777         737 :         if (pszLine[0] == '#' || pszLine[0] == '/' || pszLine[0] == 0)
     778             :         {
     779             :             /* Skip comment and blank lines */
     780           4 :             continue;
     781             :         }
     782             : 
     783             :         const CPLStringList aosFields(
     784         734 :             CSLTokenizeStringComplex(pszLine, " ,\t:", FALSE, FALSE));
     785         734 :         const int nTokens = aosFields.size();
     786             : 
     787         734 :         if (bIsGMT_CPT && nTokens == 8)
     788             :         {
     789           3 :             sColor.dfVal = CPLAtof(aosFields[0]);
     790           3 :             sColor.nR = atoi(aosFields[1]);
     791           3 :             sColor.nG = atoi(aosFields[2]);
     792           3 :             sColor.nB = atoi(aosFields[3]);
     793           3 :             sColor.nA = 255;
     794           3 :             asColorAssociation.push_back(sColor);
     795             : 
     796           3 :             sColor.dfVal = CPLAtof(aosFields[4]);
     797           3 :             sColor.nR = atoi(aosFields[5]);
     798           3 :             sColor.nG = atoi(aosFields[6]);
     799           3 :             sColor.nB = atoi(aosFields[7]);
     800           3 :             sColor.nA = 255;
     801           3 :             asColorAssociation.push_back(sColor);
     802             :         }
     803         731 :         else if (bIsGMT_CPT && nTokens == 4)
     804             :         {
     805             :             // The first token might be B (background), F (foreground) or N
     806             :             // (nodata) Just interested in N.
     807           3 :             if (EQUAL(aosFields[0], "N") && bHasNoData)
     808             :             {
     809           1 :                 sColor.dfVal = dfNoDataValue;
     810           1 :                 sColor.nR = atoi(aosFields[1]);
     811           1 :                 sColor.nG = atoi(aosFields[2]);
     812           1 :                 sColor.nB = atoi(aosFields[3]);
     813           1 :                 sColor.nA = 255;
     814           1 :                 asColorAssociation.push_back(sColor);
     815             :             }
     816             :         }
     817         728 :         else if (!bIsGMT_CPT && nTokens >= 2)
     818             :         {
     819         728 :             if (EQUAL(aosFields[0], "nv"))
     820             :             {
     821           5 :                 if (!bHasNoData)
     822             :                 {
     823           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
     824             :                              "Input dataset has no nodata value. "
     825             :                              "Ignoring 'nv' entry in color palette");
     826           1 :                     continue;
     827             :                 }
     828           4 :                 sColor.dfVal = dfNoDataValue;
     829             :             }
     830        1375 :             else if (strlen(aosFields[0]) > 1 &&
     831         652 :                      aosFields[0][strlen(aosFields[0]) - 1] == '%')
     832             :             {
     833           0 :                 const double dfPct = CPLAtof(aosFields[0]) / 100.0;
     834           0 :                 if (dfPct < 0.0 || dfPct > 1.0)
     835             :                 {
     836           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
     837             :                              "Wrong value for a percentage : %s", aosFields[0]);
     838           0 :                     return {};
     839             :                 }
     840           0 :                 if (!poBand)
     841             :                 {
     842           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     843             :                              "Percentage value not supported");
     844           0 :                     return {};
     845             :                 }
     846           0 :                 sColor.dfVal = GDALGetAbsoluteValFromPct(poBand, dfPct);
     847             :             }
     848             :             else
     849             :             {
     850         723 :                 sColor.dfVal = CPLAtof(aosFields[0]);
     851             :             }
     852             : 
     853         727 :             if (nTokens >= 4)
     854             :             {
     855         727 :                 sColor.nR = atoi(aosFields[1]);
     856         727 :                 sColor.nG = atoi(aosFields[2]);
     857         727 :                 sColor.nB = atoi(aosFields[3]);
     858         727 :                 sColor.nA =
     859         727 :                     (CSLCount(aosFields) >= 5) ? atoi(aosFields[4]) : 255;
     860             :             }
     861             :             else
     862             :             {
     863           0 :                 int nR = 0;
     864           0 :                 int nG = 0;
     865           0 :                 int nB = 0;
     866           0 :                 if (!GDALFindNamedColor(aosFields[1], &nR, &nG, &nB))
     867             :                 {
     868           0 :                     CPLError(CE_Failure, CPLE_AppDefined, "Unknown color : %s",
     869             :                              aosFields[1]);
     870           0 :                     return {};
     871             :                 }
     872           0 :                 sColor.nR = nR;
     873           0 :                 sColor.nG = nG;
     874           0 :                 sColor.nB = nB;
     875           0 :                 sColor.nA =
     876           0 :                     (CSLCount(aosFields) >= 3) ? atoi(aosFields[2]) : 255;
     877             :             }
     878         727 :             asColorAssociation.push_back(sColor);
     879             :         }
     880         737 :     }
     881             : 
     882          42 :     if (asColorAssociation.empty())
     883             :     {
     884           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     885             :                  "No color association found in %s", pszFilename);
     886             :     }
     887          42 :     return asColorAssociation;
     888             : }

Generated by: LCOV version 1.14