1 /******************************************************************************
2  *
3  * Project:  XPM Driver
4  * Purpose:  Implement GDAL XPM Support
5  * Author:   Frank Warmerdam, warmerdam@pobox.com
6  *
7  ******************************************************************************
8  * Copyright (c) 2002, Frank Warmerdam
9  * Copyright (c) 2008-2010, Even Rouault <even dot rouault at spatialys.com>
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ****************************************************************************/
29 
30 #include "gdal_pam.h"
31 #include "cpl_string.h"
32 #include "memdataset.h"
33 #include "gdal_frmts.h"
34 
35 #include <cstdlib>
36 #include <algorithm>
37 
38 CPL_CVSID("$Id: xpmdataset.cpp f6099e5ed704166bf5cc113a053dd1b2725cb391 2020-03-22 11:20:10 +0100 Kai Pastor $")
39 
40 static unsigned char *ParseXPM( const char *pszInput,
41                                 unsigned int nFileSize,
42                                 int *pnXSize, int *pnYSize,
43                                 GDALColorTable **ppoRetTable );
44 
45 /************************************************************************/
46 /* ==================================================================== */
47 /*                              XPMDataset                              */
48 /* ==================================================================== */
49 /************************************************************************/
50 
51 class XPMDataset final: public GDALPamDataset
52 {
53   public:
54                  XPMDataset() {}
55                  ~XPMDataset();
56 
57     static GDALDataset *Open( GDALOpenInfo * );
58     static int          Identify( GDALOpenInfo * );
59 };
60 
61 /************************************************************************/
62 /*                            ~XPMDataset()                             */
63 /************************************************************************/
64 
65 XPMDataset::~XPMDataset()
66 
67 {
68     FlushCache();
69 }
70 
71 /************************************************************************/
72 /*                            Identify()                                */
73 /************************************************************************/
74 
75 int XPMDataset::Identify( GDALOpenInfo * poOpenInfo )
76 
77 {
78 /* -------------------------------------------------------------------- */
79 /*      First we check to see if the file has the expected header       */
80 /*      bytes.  For now we expect the XPM file to start with a line     */
81 /*      containing the letters XPM, and to have "static" in the         */
82 /*      header.                                                         */
83 /* -------------------------------------------------------------------- */
84     return poOpenInfo->nHeaderBytes >= 32 &&
85            strstr(reinterpret_cast<const char *>( poOpenInfo->pabyHeader ),
86                   "XPM") != nullptr &&
87            strstr(reinterpret_cast<const char *>( poOpenInfo->pabyHeader ),
88                   "static") != nullptr;
89 }
90 
91 /************************************************************************/
92 /*                                Open()                                */
93 /************************************************************************/
94 
95 GDALDataset *XPMDataset::Open( GDALOpenInfo * poOpenInfo )
96 
97 {
98     if( !Identify(poOpenInfo) || poOpenInfo->fpL == nullptr )
99         return nullptr;
100 
101     if( poOpenInfo->eAccess == GA_Update )
102     {
103         CPLError( CE_Failure, CPLE_NotSupported,
104                   "The XPM driver does not support update access to existing"
105                   " files." );
106         return nullptr;
107     }
108 
109 /* -------------------------------------------------------------------- */
110 /*      Read the whole file into a memory strings.                      */
111 /* -------------------------------------------------------------------- */
112     VSILFILE *fp = poOpenInfo->fpL;
113     poOpenInfo->fpL = nullptr;
114 
115     if( VSIFSeekL( fp, 0, SEEK_END ) != 0 )
116     {
117         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
118         return nullptr;
119     }
120     unsigned int nFileSize = static_cast<unsigned int>( VSIFTellL( fp ) );
121 
122     char *pszFileContents = reinterpret_cast<char *>( VSI_MALLOC_VERBOSE(nFileSize+1) );
123     if( pszFileContents == nullptr  )
124     {
125         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
126         return nullptr;
127     }
128     pszFileContents[nFileSize] = '\0';
129 
130     if( VSIFSeekL( fp, 0, SEEK_SET ) != 0 ||
131         VSIFReadL( pszFileContents, 1, nFileSize, fp ) != nFileSize)
132     {
133         CPLFree( pszFileContents );
134         CPLError( CE_Failure, CPLE_FileIO,
135                   "Failed to read all %d bytes from file %s.",
136                   nFileSize, poOpenInfo->pszFilename );
137         CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
138         return nullptr;
139     }
140 
141     CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
142     fp = nullptr;
143 
144 /* -------------------------------------------------------------------- */
145 /*      Convert into a binary image.                                    */
146 /* -------------------------------------------------------------------- */
147     CPLErrorReset();
148 
149     int nXSize;
150     int nYSize;
151     GDALColorTable *poCT = nullptr;
152 
153     GByte *pabyImage = ParseXPM( pszFileContents, nFileSize, &nXSize, &nYSize, &poCT );
154 
155     CPLFree( pszFileContents );
156 
157     if( pabyImage == nullptr )
158     {
159         return nullptr;
160     }
161 
162 /* -------------------------------------------------------------------- */
163 /*      Create a corresponding GDALDataset.                             */
164 /* -------------------------------------------------------------------- */
165     XPMDataset *poDS = new XPMDataset();
166 
167 /* -------------------------------------------------------------------- */
168 /*      Capture some information from the file that is of interest.     */
169 /* -------------------------------------------------------------------- */
170     poDS->nRasterXSize = nXSize;
171     poDS->nRasterYSize = nYSize;
172 
173 /* -------------------------------------------------------------------- */
174 /*      Create band information objects.                                */
175 /* -------------------------------------------------------------------- */
176     MEMRasterBand *poBand = new MEMRasterBand( poDS, 1, pabyImage, GDT_Byte, 1,
177                                                nXSize, TRUE );
178     poBand->SetColorTable( poCT );
179     poDS->SetBand( 1, poBand );
180 
181     delete poCT;
182 
183 /* -------------------------------------------------------------------- */
184 /*      Initialize any PAM information.                                 */
185 /* -------------------------------------------------------------------- */
186     poDS->SetDescription( poOpenInfo->pszFilename );
187     poDS->TryLoadXML();
188 
189 /* -------------------------------------------------------------------- */
190 /*      Support overviews.                                              */
191 /* -------------------------------------------------------------------- */
192     poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
193 
194     return poDS;
195 }
196 
197 /************************************************************************/
198 /*                           XPMCreateCopy()                            */
199 /************************************************************************/
200 
201 static GDALDataset *
202 XPMCreateCopy( const char * pszFilename,
203                GDALDataset *poSrcDS,
204                int bStrict,
205                char ** /* papszOptions */,
206                GDALProgressFunc /* pfnProgress */,
207                void * /* pProgressData */)
208 {
209 /* -------------------------------------------------------------------- */
210 /*      Some some rudimentary checks                                    */
211 /* -------------------------------------------------------------------- */
212     const int nBands = poSrcDS->GetRasterCount();
213     if( nBands != 1 )
214     {
215         CPLError( CE_Failure, CPLE_NotSupported,
216                   "XPM driver only supports one band images.\n" );
217 
218         return nullptr;
219     }
220 
221     if( poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte
222         && bStrict )
223     {
224         CPLError( CE_Failure, CPLE_NotSupported,
225                   "XPM driver doesn't support data type %s. "
226                   "Only eight bit bands supported.\n",
227                   GDALGetDataTypeName(
228                       poSrcDS->GetRasterBand(1)->GetRasterDataType()) );
229 
230         return nullptr;
231     }
232 
233 /* -------------------------------------------------------------------- */
234 /*      If there is no colortable on the source image, create a         */
235 /*      greyscale one with 64 levels of grey.                           */
236 /* -------------------------------------------------------------------- */
237     GDALRasterBand *poBand = poSrcDS->GetRasterBand(1);
238 
239     GDALColorTable oGreyTable;
240     GDALColorTable *poCT = poBand->GetColorTable();
241 
242     if( poCT == nullptr )
243     {
244         poCT = &oGreyTable;
245 
246         for( int i = 0; i < 256; i++ )
247         {
248             GDALColorEntry sColor;
249 
250             sColor.c1 = (short) i;
251             sColor.c2 = (short) i;
252             sColor.c3 = (short) i;
253             sColor.c4 = 255;
254 
255             poCT->SetColorEntry( i, &sColor );
256         }
257     }
258 
259 /* -------------------------------------------------------------------- */
260 /*      Build list of active colors, and the mapping from pixels to     */
261 /*      our active colormap.                                            */
262 /* -------------------------------------------------------------------- */
263     const char *pszColorCodes
264         = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@"
265         "#$%^&*()-+=[]|:;,.<>?/";
266 
267     int  anPixelMapping[256];
268     GDALColorEntry asPixelColor[256];
269     int nActiveColors = std::min(poCT->GetColorEntryCount(),256);
270 
271     // Setup initial colortable and pixel value mapping.
272     memset( anPixelMapping+0, 0, sizeof(int) * 256 );
273     for( int i = 0; i < nActiveColors; i++ )
274     {
275         poCT->GetColorEntryAsRGB( i, asPixelColor + i );
276         anPixelMapping[i] = i;
277     }
278 
279 /* ==================================================================== */
280 /*      Iterate merging colors until we are under our limit (about 85). */
281 /* ==================================================================== */
282     while( nActiveColors > static_cast<int>( strlen(pszColorCodes) ) )
283     {
284         int nClosestDistance = 768;
285         int iClose1 = -1;
286         int iClose2 = -1;
287 
288         // Find the closest pair of colors.
289         for( int iColor1 = 0; iColor1 < nActiveColors; iColor1++ )
290         {
291             for( int iColor2 = iColor1+1; iColor2 < nActiveColors; iColor2++ )
292             {
293                 int nDistance;
294 
295                 if( asPixelColor[iColor1].c4 < 128
296                     && asPixelColor[iColor2].c4 < 128 )
297                     nDistance = 0;
298                 else
299                     nDistance =
300                         std::abs(asPixelColor[iColor1].c1 -
301                                  asPixelColor[iColor2].c1)
302                         + std::abs(asPixelColor[iColor1].c2 -
303                                    asPixelColor[iColor2].c2)
304                         + std::abs(asPixelColor[iColor1].c3 -
305                                    asPixelColor[iColor2].c3);
306 
307                 if( nDistance < nClosestDistance )
308                 {
309                     nClosestDistance = nDistance;
310                     iClose1 = iColor1;
311                     iClose2 = iColor2;
312                 }
313             }
314 
315             if( nClosestDistance < 8 )
316                 break;
317         }
318 
319         // This should never happen!
320         if( iClose1 == -1 )
321             break;
322 
323         // Merge two selected colors - shift icolor2 into icolor1 and
324         // move the last active color into icolor2's slot.
325         for( int i = 0; i < 256; i++ )
326         {
327             if( anPixelMapping[i] == iClose2 )
328                 anPixelMapping[i] = iClose1;
329             else if( anPixelMapping[i] == nActiveColors-1 )
330                 anPixelMapping[i] = iClose2;
331         }
332 
333         asPixelColor[iClose2] = asPixelColor[nActiveColors-1];
334         nActiveColors--;
335     }
336 
337 /* ==================================================================== */
338 /*      Write the output image.                                         */
339 /* ==================================================================== */
340     VSILFILE *fpPBM = VSIFOpenL( pszFilename, "wb+" );
341     if( fpPBM == nullptr )
342     {
343         CPLError( CE_Failure, CPLE_OpenFailed,
344                   "Unable to create file `%s'.",
345                   pszFilename );
346 
347         return nullptr;
348     }
349 
350 /* -------------------------------------------------------------------- */
351 /*      Write the header lines.                                         */
352 /* -------------------------------------------------------------------- */
353     bool bOK = VSIFPrintfL( fpPBM, "/* XPM */\n" ) >= 0;
354     bOK &= VSIFPrintfL( fpPBM, "static char *%s[] = {\n",
355              CPLGetBasename( pszFilename ) ) >= 0;
356     bOK &= VSIFPrintfL( fpPBM, "/* width height num_colors chars_per_pixel */\n" ) >= 0;
357 
358     const int nXSize = poSrcDS->GetRasterXSize();
359     const int nYSize = poSrcDS->GetRasterYSize();
360 
361     bOK &= VSIFPrintfL( fpPBM, "\"  %3d   %3d     %3d             1\",\n",
362              nXSize, nYSize, nActiveColors ) >= 0;
363 
364     bOK &= VSIFPrintfL( fpPBM, "/* colors */\n" ) >= 0;
365 
366 /* -------------------------------------------------------------------- */
367 /*      Write the color table.                                          */
368 /* -------------------------------------------------------------------- */
369     for( int i = 0; bOK && i < nActiveColors; i++ )
370     {
371         if( asPixelColor[i].c4 < 128 )
372             bOK &= VSIFPrintfL( fpPBM, "\"%c c None\",\n", pszColorCodes[i] ) >= 0;
373         else
374             bOK &= VSIFPrintfL( fpPBM,
375                      "\"%c c #%02x%02x%02x\",\n",
376                      pszColorCodes[i],
377                      asPixelColor[i].c1,
378                      asPixelColor[i].c2,
379                      asPixelColor[i].c3 ) >= 0;
380     }
381 
382 /* -------------------------------------------------------------------- */
383 /*      Dump image.                                                     */
384 /* -------------------------------------------------------------------- */
385     GByte *pabyScanline = reinterpret_cast<GByte *>( CPLMalloc( nXSize ) );
386 
387     for( int iLine = 0; bOK && iLine < nYSize; iLine++ )
388     {
389         if( poBand->RasterIO(
390                GF_Read, 0, iLine, nXSize, 1,
391                reinterpret_cast<void *>( pabyScanline), nXSize, 1, GDT_Byte,
392                0, 0, nullptr ) != CE_None )
393         {
394             CPLFree( pabyScanline );
395             CPL_IGNORE_RET_VAL(VSIFCloseL( fpPBM ));
396             return nullptr;
397         }
398 
399         bOK &= VSIFPutcL( '"', fpPBM ) >= 0;
400         for( int iPixel = 0; iPixel < nXSize; iPixel++ )
401             bOK &= VSIFPutcL( pszColorCodes[anPixelMapping[pabyScanline[iPixel]]],
402                    fpPBM) >= 0;
403         bOK &= VSIFPrintfL( fpPBM, "\",\n" ) >= 0;
404     }
405 
406     CPLFree( pabyScanline );
407 
408 /* -------------------------------------------------------------------- */
409 /*      cleanup                                                         */
410 /* -------------------------------------------------------------------- */
411     bOK &= VSIFPrintfL( fpPBM, "};\n" ) >= 0;
412     if( VSIFCloseL( fpPBM ) != 0 )
413         bOK = false;
414 
415     if( !bOK )
416         return nullptr;
417 
418 /* -------------------------------------------------------------------- */
419 /*      Re-open dataset, and copy any auxiliary pam information.         */
420 /* -------------------------------------------------------------------- */
421     GDALPamDataset *poDS = reinterpret_cast<GDALPamDataset *>(
422         GDALOpen( pszFilename, GA_ReadOnly ) );
423 
424     if( poDS )
425         poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT );
426 
427     return poDS;
428 }
429 
430 /************************************************************************/
431 /*                          GDALRegister_XPM()                          */
432 /************************************************************************/
433 
434 void GDALRegister_XPM()
435 
436 {
437     if( GDALGetDriverByName( "XPM" ) != nullptr )
438         return;
439 
440     GDALDriver *poDriver = new GDALDriver();
441 
442     poDriver->SetDescription( "XPM" );
443     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
444     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "X11 PixMap Format" );
445     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/xpm.html" );
446     poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "xpm" );
447     poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/x-xpixmap" );
448     poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES, "Byte" );
449     poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
450 
451     poDriver->pfnOpen = XPMDataset::Open;
452     poDriver->pfnIdentify = XPMDataset::Identify;
453     poDriver->pfnCreateCopy = XPMCreateCopy;
454 
455     GetGDALDriverManager()->RegisterDriver( poDriver );
456 }
457 
458 /************************************************************************/
459 /*                              ParseXPM()                              */
460 /************************************************************************/
461 
462 static unsigned char *
463 ParseXPM( const char *pszInput,
464           unsigned int nFileSize,
465           int *pnXSize, int *pnYSize,
466           GDALColorTable **ppoRetTable )
467 
468 {
469 /* ==================================================================== */
470 /*      Parse input into an array of strings from within the first C    */
471 /*      initializer (list os comma separated strings in braces).        */
472 /* ==================================================================== */
473     const char *pszNext = pszInput;
474 
475     // Skip till after open brace.
476     while( *pszNext != '\0' && *pszNext != '{' )
477         pszNext++;
478 
479     if( *pszNext == '\0' )
480         return nullptr;
481 
482     pszNext++;
483 
484     // Read lines till close brace.
485 
486     char **papszXPMList = nullptr;
487     int  i = 0;
488 
489     while( *pszNext != '\0' && *pszNext != '}' )
490     {
491         // skip whole comment.
492         if( STARTS_WITH_CI(pszNext, "/*") )
493         {
494             pszNext += 2;
495             while( *pszNext != '\0' && !STARTS_WITH_CI(pszNext, "*/") )
496                 pszNext++;
497         }
498 
499         // reading string constants
500         else if( *pszNext == '"' )
501         {
502             pszNext++;
503             i = 0;
504 
505             while( pszNext[i] != '\0' && pszNext[i] != '"' )
506                 i++;
507 
508             if( pszNext[i] == '\0' )
509             {
510                 CSLDestroy( papszXPMList );
511                 return nullptr;
512             }
513 
514             char *pszLine = reinterpret_cast<char *>( CPLMalloc(i+1) );
515             strncpy( pszLine, pszNext, i );
516             pszLine[i] = '\0';
517 
518             papszXPMList = CSLAddString( papszXPMList, pszLine );
519             CPLFree( pszLine );
520             pszNext = pszNext + i + 1;
521         }
522 
523         // just ignore everything else (whitespace, commas, newlines, etc).
524         else
525             pszNext++;
526     }
527 
528     if( papszXPMList == nullptr || CSLCount(papszXPMList) < 3 || *pszNext != '}' )
529     {
530         CSLDestroy( papszXPMList );
531         return nullptr;
532     }
533 
534 /* -------------------------------------------------------------------- */
535 /*      Get the image information.                                      */
536 /* -------------------------------------------------------------------- */
537     int nColorCount, nCharsPerPixel;
538 
539     if( sscanf( papszXPMList[0], "%d %d %d %d",
540                 pnXSize, pnYSize, &nColorCount, &nCharsPerPixel ) != 4 ||
541         *pnXSize <= 0 || *pnYSize <= 0 || nColorCount <= 0 || nColorCount > 256 ||
542         static_cast<GUIntBig>(*pnXSize) * static_cast<GUIntBig>(*pnYSize) > nFileSize )
543     {
544         CPLError( CE_Failure, CPLE_AppDefined,
545                   "Image definition (%s) not well formed.",
546                   papszXPMList[0] );
547         CSLDestroy( papszXPMList );
548         return nullptr;
549     }
550 
551     if( nCharsPerPixel != 1 )
552     {
553         CPLError( CE_Failure, CPLE_AppDefined,
554                   "Only one character per pixel XPM images supported by GDAL at this time." );
555         CSLDestroy( papszXPMList );
556         return nullptr;
557     }
558 
559 /* -------------------------------------------------------------------- */
560 /*      Parse out colors.                                               */
561 /* -------------------------------------------------------------------- */
562     int anCharLookup[256];
563     GDALColorTable oCTable;
564 
565     for( i = 0; i < 256; i++ )
566         anCharLookup[i] = -1;
567 
568     for( int iColor = 0; iColor < nColorCount; iColor++ )
569     {
570         if( papszXPMList[iColor+1] == nullptr ||
571             papszXPMList[iColor+1][0] == '\0' )
572         {
573             CPLError( CE_Failure, CPLE_AppDefined,
574                       "Missing color definition for %d in XPM header.",
575                       iColor+1 );
576             CSLDestroy( papszXPMList );
577             return nullptr;
578         }
579 
580         char **papszTokens = CSLTokenizeString( papszXPMList[iColor+1]+1 );
581 
582         if( CSLCount(papszTokens) != 2 || !EQUAL(papszTokens[0],"c") )
583         {
584             CPLError( CE_Failure, CPLE_AppDefined,
585                       "Ill formed color definition (%s) in XPM header.",
586                       papszXPMList[iColor+1] );
587             CSLDestroy( papszXPMList );
588             CSLDestroy( papszTokens );
589             return nullptr;
590         }
591 
592         anCharLookup[*(reinterpret_cast<GByte*>(papszXPMList[iColor+1]))] = iColor;
593 
594         GDALColorEntry sColor;
595         unsigned int nRed, nGreen, nBlue;
596 
597         if( EQUAL(papszTokens[1],"None") )
598         {
599             sColor.c1 = 0;
600             sColor.c2 = 0;
601             sColor.c3 = 0;
602             sColor.c4 = 0;
603         }
604         else if( sscanf( papszTokens[1], "#%02x%02x%02x",
605                          &nRed, &nGreen, &nBlue ) != 3 )
606         {
607             CPLError( CE_Failure, CPLE_AppDefined,
608                       "Ill formed color definition (%s) in XPM header.",
609                       papszXPMList[iColor+1] );
610             CSLDestroy( papszXPMList );
611             CSLDestroy( papszTokens );
612             return nullptr;
613         }
614         else
615         {
616             sColor.c1 = static_cast<short>( nRed );
617             sColor.c2 = static_cast<short>( nGreen );
618             sColor.c3 = static_cast<short>( nBlue );
619             sColor.c4 = 255;
620         }
621 
622         oCTable.SetColorEntry( iColor, &sColor );
623 
624         CSLDestroy( papszTokens );
625     }
626 
627 /* -------------------------------------------------------------------- */
628 /*      Prepare image buffer.                                           */
629 /* -------------------------------------------------------------------- */
630     GByte *pabyImage
631         = reinterpret_cast<GByte *>( VSI_CALLOC_VERBOSE(*pnXSize, *pnYSize) );
632     if( pabyImage == nullptr )
633     {
634         CSLDestroy( papszXPMList );
635         return nullptr;
636     }
637 
638 /* -------------------------------------------------------------------- */
639 /*      Parse image.                                                    */
640 /* -------------------------------------------------------------------- */
641     for( int iLine = 0; iLine < *pnYSize; iLine++ )
642     {
643         const GByte *pabyInLine = reinterpret_cast<GByte*>(
644                                 papszXPMList[iLine + nColorCount + 1]);
645 
646         if( pabyInLine == nullptr )
647         {
648             CPLFree( pabyImage );
649             CSLDestroy( papszXPMList );
650             CPLError( CE_Failure, CPLE_AppDefined,
651                       "Insufficient imagery lines in XPM image." );
652             return nullptr;
653         }
654 
655         for( int iPixel = 0; iPixel < *pnXSize; iPixel++ )
656         {
657             if( pabyInLine[iPixel] == '\0' )
658                 break;
659             const int nPixelValue
660                 = anCharLookup[pabyInLine[iPixel]];
661             if( nPixelValue != -1 )
662                 pabyImage[iLine * *pnXSize + iPixel]
663                     = static_cast<GByte>( nPixelValue );
664         }
665     }
666 
667     CSLDestroy( papszXPMList );
668 
669     *ppoRetTable = oCTable.Clone();
670 
671     return pabyImage;
672 }
673