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 : }
|