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