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