1 /* xpmtoppm.c - convert XPM file (X11 pixmap) to PPM
2 
3    Copyright and history information is at end of file
4 */
5 
6 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
7 #define _BSD_SOURCE   /* Make sure strdup() is in string.h */
8 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
9 
10 #include <assert.h>
11 #include <string.h>
12 
13 #include "pm_c_util.h"
14 #include "mallocvar.h"
15 #include "shhopt.h"
16 #include "nstring.h"
17 #include "ppm.h"
18 
19 #define MAX_LINE (8 * 1024)
20   /* The maximum size XPM input line we can handle. */
21 
22 /* number of xpmColorKeys */
23 #define NKEYS 5
24 
25 const char *xpmColorKeys[] =
26 {
27  "s",                   /* key #1: symbol */
28  "m",                   /* key #2: mono visual */
29  "g4",                  /* key #3: 4 grays visual */
30  "g",                   /* key #4: gray visual */
31  "c",                   /* key #5: color visual */
32 };
33 
34 struct cmdlineInfo {
35     /* All the information the user supplied in the command line,
36        in a form easy for the program to use.
37     */
38     const char * input_filespec;  /* Filespecs of input files */
39     const char * alpha_filename;
40     int alpha_stdout;
41     int verbose;
42 };
43 
44 
45 static bool verbose;
46 
47 
48 
49 static void
parseCommandLine(int argc,char ** argv,struct cmdlineInfo * cmdlineP)50 parseCommandLine(int argc, char ** argv,
51                  struct cmdlineInfo *cmdlineP) {
52 /*----------------------------------------------------------------------------
53    Note that the file spec array we return is stored in the storage that
54    was passed to us as the argv array.
55 -----------------------------------------------------------------------------*/
56     optEntry * option_def;
57         /* Instructions to OptParseOptions2 on how to parse our options.
58          */
59     optStruct3 opt;
60 
61     unsigned int option_def_index;
62 
63     MALLOCARRAY_NOFAIL(option_def, 100);
64 
65     option_def_index = 0;   /* incremented by OPTENT3 */
66     OPTENT3(0,   "alphaout",   OPT_STRING, &cmdlineP->alpha_filename,
67             NULL, 0);
68     OPTENT3(0,   "verbose",    OPT_FLAG,   &cmdlineP->verbose,
69             NULL, 0);
70 
71     cmdlineP->alpha_filename = NULL;
72     cmdlineP->verbose = FALSE;
73 
74     opt.opt_table = option_def;
75     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
76     opt.allowNegNum = TRUE;  /* We may have parms that are negative numbers */
77 
78     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
79         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
80 
81     if (argc - 1 == 0)
82         cmdlineP->input_filespec = NULL;  /* he wants stdin */
83     else if (argc - 1 == 1)
84         cmdlineP->input_filespec = strdup(argv[1]);
85     else
86         pm_error("Too many arguments.  The only argument accepted\n"
87                  "is the input file specification");
88 
89     if (cmdlineP->alpha_filename &&
90         streq(cmdlineP->alpha_filename, "-"))
91         cmdlineP->alpha_stdout = TRUE;
92     else
93         cmdlineP->alpha_stdout = FALSE;
94 
95 }
96 
97 
98 
99 struct ColorNameHashTableEntry {
100 /*----------------------------------------------------------------------------
101    An entry in the color name hash table.  It maps a color name to a
102    color, or is empty.
103 -----------------------------------------------------------------------------*/
104     bool empty;
105     char colorName[3];
106         /* Actual length 0-3.  NOT NUL-terminated */
107     pixel color;
108 };
109 
110 
111 
112 typedef struct {
113 /*----------------------------------------------------------------------------
114    This is a color map which is primarily a hash table that maps an XPM
115    color name to a color.  An XPM color name is a 0-3 character name that
116    appears in the raster of an XPM image to uniquely identify a color.
117    The header of the XPM contains a listing of all the color names that
118    appear in the raster, identifying a color for each.
119 
120    We represent a color as a 'pixel'.
121 -----------------------------------------------------------------------------*/
122     unsigned int nameSize;
123         /* Size of color names in this hash.  0-3 */
124     struct ColorNameHashTableEntry * transparentP;
125         /* The element of 'table' that is for the transparent color.
126            NULL if there is none.
127         */
128 
129     /* This is an internally chained hash table, i.e. there are no linked
130        lists.  You use the hash function to get an index into the hash table.
131        If the entry indexed by that is not for the color name you're looking
132        for, you look at the next entry down, and keep going down until you
133        either find the color name you're looking for or hit an empty entry.
134 
135        So that we never run out of space for new color names, we make the
136        creator of the hash table tell us the maximum number of colors there
137        will be.  We allocate twice that size in order to reduce average hash
138        chain length.
139     */
140     unsigned int size;
141     struct ColorNameHashTableEntry * table;
142 } ColorNameHash;
143 
144 
145 
146 static ColorNameHash *
hash_create(unsigned int const nColors,unsigned int const nameSize)147 hash_create(unsigned int const nColors,
148             unsigned int const nameSize) {
149 
150     ColorNameHash * hashP;
151 
152     MALLOCVAR_NOFAIL(hashP);
153 
154     hashP->nameSize = nameSize;
155 
156     hashP->size = nColors * 2;
157 
158     MALLOCARRAY(hashP->table, hashP->size);
159 
160     if (!hashP->table)
161         pm_error("Failed to allocate memory for a %u-entry "
162                  "color name hash table.", hashP->size);
163 
164     {
165         unsigned int i;
166         for (i = 0; i < hashP->size; ++i)
167             hashP->table[i].empty = true;
168     }
169 
170     hashP->transparentP = NULL;
171 
172     return hashP;
173 }
174 
175 
176 
177 static void
hash_destroy(ColorNameHash * const hashP)178 hash_destroy(ColorNameHash * const hashP) {
179 
180     free(hashP->table);
181 
182     free(hashP);
183 }
184 
185 
186 
187 static unsigned int
hashColorName(const char * const name,unsigned int const size,unsigned int const hashTableSize)188 hashColorName(const char * const name,
189               unsigned int const size,
190               unsigned int const hashTableSize) {
191 /*----------------------------------------------------------------------------
192    Return the hash value (initial index into the color name hash table)
193    for the color name 'name', which is 'size' characters long.  The hash
194    is to be in the range [0, hashTableSize).
195 -----------------------------------------------------------------------------*/
196     /* I have no idea if this is an appropriate hash function.  I borrowed
197        it from pnm_hashTuple()
198     */
199 
200     unsigned int const hash_factor[] = {1, 33, 33*33};
201 
202     unsigned int i;
203     unsigned int hash;
204 
205     hash = 0;  /* initial value */
206     for (i = 0; i < size; ++i) {
207         hash += name[i] * hash_factor[i];
208     }
209     hash %= hashTableSize;
210     return hash;
211 }
212 
213 
214 
215 static bool
entryMatch(struct ColorNameHashTableEntry const entry,const char * const name,unsigned int const size)216 entryMatch(struct ColorNameHashTableEntry const entry,
217            const char *                   const name,
218            unsigned int                   const size) {
219 
220     if (entry.empty)
221         return true;
222 
223     assert(size <= ARRAY_SIZE(entry.colorName));
224 
225     {
226         unsigned int i;
227 
228         for (i = 0; i < size; ++i) {
229             if (name[i] != entry.colorName[i])
230                 return false;
231         }
232     }
233 
234     return true;
235 }
236 
237 
238 
239 static void
bumpIndex(unsigned int * const indexP,unsigned int const tableSize,unsigned int const limit)240 bumpIndex(unsigned int * const indexP,
241           unsigned int   const tableSize,
242           unsigned int   const limit) {
243 /*----------------------------------------------------------------------------
244    Bump *indexP to the next entry in a table of size 'tableSize', in a
245    circular fashion.  But abort the program if this would take us to
246    'limit'.
247 -----------------------------------------------------------------------------*/
248     *indexP += 1;
249     if (*indexP >= tableSize)
250         *indexP = 0;
251 
252     if (*indexP == limit)
253         pm_error("INTERNAL ERROR: color name hash table is full");
254 }
255 
256 
257 
258 static void
hash_find(const ColorNameHash * const hashP,const char * const name,struct ColorNameHashTableEntry ** const entryPP)259 hash_find(const ColorNameHash *             const hashP,
260           const char *                      const name,
261           struct ColorNameHashTableEntry ** const entryPP) {
262 /*----------------------------------------------------------------------------
263    Find the entry in the color hash table *hashP for the color
264    named 'name' in the lexicon of this XPM file.  If the color is in the
265    table, this is where it is.  If it isn't, this is where it should go.
266 -----------------------------------------------------------------------------*/
267     unsigned int const initialIndex  =
268         hashColorName(name, hashP->nameSize, hashP->size);
269 
270     unsigned int i;
271 
272     for (i = initialIndex;
273          !entryMatch(hashP->table[i], name, hashP->nameSize);
274          bumpIndex(&i, hashP->size, initialIndex));
275 
276     *entryPP = &hashP->table[i];
277 }
278 
279 
280 
281 static void
hash_add(ColorNameHash * const hashP,const char * const name,pixel const color,bool const isTransparent)282 hash_add(ColorNameHash * const hashP,
283          const char *    const name,
284          pixel           const color,
285          bool            const isTransparent) {
286 
287     struct ColorNameHashTableEntry * entryP;
288 
289     hash_find(hashP, name, &entryP);
290 
291     if (!entryP->empty)
292         pm_error("Color name appears multiple times in color map");
293 
294     entryP->empty = false;
295     {
296         unsigned int i;
297         for (i = 0; i < hashP->nameSize; ++i)
298             entryP->colorName[i] = name[i];
299     }
300     entryP->color = color;
301 
302     if (isTransparent) {
303         if (hashP->transparentP)
304             pm_error("There are multiple NONE (transparent) entries in "
305                      "the XPM color map");
306         else
307             hashP->transparentP = entryP;
308     }
309 }
310 
311 
312 
313 static pixel
hash_color(const ColorNameHash * const hashP,const char * const name)314 hash_color(const ColorNameHash * const hashP,
315            const char *          const name) {
316 
317     struct ColorNameHashTableEntry * entryP;
318 
319     hash_find(hashP, name, &entryP);
320 
321     if (entryP->empty)
322         pm_error("Color name in raster is not in color map");
323 
324     return entryP->color;
325 }
326 
327 
328 
329 static bool
hash_isTransparent(const ColorNameHash * const hashP,const char * const name)330 hash_isTransparent(const ColorNameHash * const hashP,
331                    const char *          const name) {
332 
333     struct ColorNameHashTableEntry * entryP;
334 
335     hash_find(hashP, name, &entryP);
336 
337     return (entryP == hashP->transparentP);
338 }
339 
340 
341 
342 static char lastInputLine[MAX_LINE+1];
343     /* contents of line most recently read from input */
344 static bool backup;
345     /* TRUE means next read should be a reread of the most recently read
346        line, i.e. lastInputLine, instead of a read from the input file.
347     */
348 
349 
350 
351 static void
getLine(char * const line,size_t const size,FILE * const stream)352 getLine(char * const line,
353         size_t const size,
354         FILE * const stream) {
355 /*----------------------------------------------------------------------------
356    Read the next line from the input file 'stream', through the one-line
357    buffer lastInputLine[].
358 
359    If 'backup' is true, the "next line" is the previously read line, i.e.
360    the one in that one-line buffer.  Otherwise, the "next line" is the next
361    line from the real file.  After reading the backed up line, we reset
362    'backup' to false.
363 
364    Return the line as a null terminated string in *line, which is an
365    array of 'size' bytes.
366 
367    Exit program if the line doesn't fit in the buffer.
368 -----------------------------------------------------------------------------*/
369     if (size > sizeof(lastInputLine))
370         pm_error("INTERNAL ERROR: getLine() received 'size' parameter "
371                  "which is out of bounds");
372 
373     if (backup) {
374         strncpy(line, lastInputLine, size);
375         backup = FALSE;
376     } else {
377         if (fgets(line, size, stream) == NULL)
378             pm_error("EOF or read error on input file");
379         if (strlen(line) == size - 1)
380             pm_error("Input file has line that is too long (longer than "
381                      "%u bytes).", (unsigned)size - 1);
382         STRSCPY(lastInputLine, line);
383     }
384 }
385 
386 
387 
388 static void
getword(char * const output,char ** const cursorP)389 getword(char * const output, char ** const cursorP) {
390 
391     char *t1;
392     char *t2;
393 
394     for (t1=*cursorP; ISSPACE(*t1); t1++); /* skip white space */
395     for (t2 = t1; !ISSPACE(*t2) && *t2 != '"' && *t2 != '\0'; t2++);
396         /* Move to next white space, ", or eol */
397     if (t2 > t1)
398         strncpy(output, t1, t2 - t1);
399     output[t2 - t1] = '\0';
400     *cursorP = t2;
401 }
402 
403 
404 
405 static void
addToColorMap(ColorNameHash * const hashP,const char * const colorName,char const colorspec[],bool const isTransparent)406 addToColorMap(ColorNameHash * const hashP,
407               const char *    const colorName,
408               char            const colorspec[],
409               bool            const isTransparent) {
410 /*----------------------------------------------------------------------------
411    Add the color named by colorspec[] to the colormap represented by *hashP,
412    as the color associated with XPM color name 'colorNumber'.
413 
414    Note that *hashP determines how long 'colorName' is.
415 -----------------------------------------------------------------------------*/
416     hash_add(hashP, colorName, ppm_parsecolor(colorspec, PPM_MAXMAXVAL),
417              isTransparent);
418 }
419 
420 
421 
422 static void
validateColorName(const char * const name,unsigned int const charsPerPixel)423 validateColorName(const char * const name,
424                   unsigned int const charsPerPixel) {
425 
426     unsigned int i;
427 
428     for (i = 0; i < charsPerPixel; ++i) {
429         if (name[i] == '"')
430             pm_error("A color map entry ends in the middle of the colormap "
431                      "index");
432         else if (name[i] == '\0')
433             pm_error("The XPM file ends in the middle of a color map entry");
434     }
435 }
436 
437 
438 
439 static void
interpretXpm3ColorTableLine(char const line[],unsigned int const seqNum,unsigned int const charsPerPixel,ColorNameHash * const hashP)440 interpretXpm3ColorTableLine(char               const line[],
441                             unsigned int       const seqNum,
442                             unsigned int       const charsPerPixel,
443                             ColorNameHash *    const hashP) {
444 /*----------------------------------------------------------------------------
445    Interpret one line of the color table in the XPM header.  'line' is the
446    line from the XPM file.  It is the seqNum'th color table entry in the file.
447    The raster in the file uses 'charsPerPixel' characters per pixel (i.e.
448    a an XPM color name is 'charsPerPixel' characters).
449 
450    Add the information from this color table entry to the color name hash
451    *hashP.
452 
453    The line may include values for multiple kinds of color (grayscale,
454    color, etc.).  We take the highest of these (e.g. color over grayscale).
455 
456    If a color table entry indicates transparency, set *transparentP
457    to indicate the XPM color name.
458 -----------------------------------------------------------------------------*/
459     /* Note: this code seems to allow for multi-word color specifications,
460        but I'm not aware that such are legal.  Ultimately, ppm_parsecolor()
461        interprets the name, and I believe it takes only single word
462        color specifications.  -Bryan 2001.05.06.
463     */
464     char str2[MAX_LINE+1];
465     char * t1;
466     char * t2;
467     int endOfEntry;   /* boolean */
468 
469     unsigned int curkey, key, highkey;  /* current color key */
470     bool lastwaskey;
471         /* The last token we processes was a key, and we have processed
472            at least one token.
473         */
474     char curbuf[BUFSIZ];        /* current buffer */
475     bool isTransparent;
476 
477     const char * colorName;
478         /* The 0-3 character name this color map line gives the color
479            (i.e. the name that the raster uses).  This is NOT NUL-terminated.
480            It's length is bytesPerPixel.
481         */
482 
483     /* read the chars */
484     t1 = strchr(line, '"');
485     if (t1 == NULL)
486         pm_error("A line that is supposed to be an entry in the color "
487                  "table does not start with a quote.  The line is '%s'.  "
488                  "It is the %uth entry in the color table.",
489                  line, seqNum);
490     else
491         ++t1;  /* Points now to first color number character */
492 
493     validateColorName(t1, charsPerPixel);
494     colorName = t1;
495 
496     t1 += charsPerPixel;
497 
498     /*
499      * read color keys and values
500      */
501     curkey = 0;
502     highkey = 1;
503     lastwaskey = FALSE;
504     t2 = t1;
505     endOfEntry = FALSE;
506     while ( !endOfEntry ) {
507         int isKey;   /* boolean */
508         getword(str2, &t2);
509         if (strlen(str2) == 0)
510             endOfEntry = TRUE;
511         else {
512             /* See if the word we got is a valid key (and get its key
513                number if so)
514             */
515             for (key = 1;
516                  key <= NKEYS && !streq(xpmColorKeys[key - 1], str2);
517                  key++);
518             isKey = (key <= NKEYS);
519 
520             if (lastwaskey || !isKey) {
521                 /* This word is a color specification (or "none" for
522                    transparent).
523                 */
524                 if (!curkey)
525                     pm_error("Missing color key token in color table line "
526                              "'%s' before '%s'.", line, str2);
527                 if (!lastwaskey)
528                     strcat(curbuf, " ");        /* append space */
529                 if ( (strneq(str2, "None", 4))
530                      || (strneq(str2, "none", 4)) ) {
531                     /* This entry identifies the transparent color number */
532                     strcat(curbuf, "#000000");  /* Make it black */
533                     isTransparent = TRUE;
534                 } else
535                     strcat(curbuf, str2);       /* append buf */
536                 lastwaskey = FALSE;
537             } else {
538                 /* This word is a key.  So we've seen the last of the
539                    info for the previous key, and we must either put it
540                    in the color map or ignore it if we already have a higher
541                    color form in the colormap for this colormap entry.
542                 */
543                 if (curkey > highkey) { /* flush string */
544                     addToColorMap(hashP, colorName, curbuf, isTransparent);
545                     highkey = curkey;
546                 }
547                 /* initialize state to process this new key */
548                 curkey = key;
549                 curbuf[0] = '\0';
550                 isTransparent = FALSE;
551                 lastwaskey = TRUE;
552             }
553             if (*t2 == '"') break;
554         }
555     }
556     /* Put the info for the last key in the line into the colormap (or
557        ignore it if there's already a higher color form for this colormap
558        entry in it)
559     */
560     if (curkey > highkey) {
561         addToColorMap(hashP, colorName, curbuf, isTransparent);
562         highkey = curkey;
563     }
564     if (highkey == 1)
565         pm_error("C error scanning color table");
566 }
567 
568 
569 
570 static void
readV3ColorTable(FILE * const ifP,ColorNameHash ** const colorNameHashPP,unsigned int const nColors,unsigned int const charsPerPixel)571 readV3ColorTable(FILE *             const ifP,
572                  ColorNameHash **   const colorNameHashPP,
573                  unsigned int       const nColors,
574                  unsigned int       const charsPerPixel) {
575 /*----------------------------------------------------------------------------
576    Read the color table from the XPM Version 3 header.
577 
578    Assume *ifP is positioned to the color table; leave it positioned after.
579 -----------------------------------------------------------------------------*/
580     ColorNameHash * const colorNameHashP = hash_create(nColors, charsPerPixel);
581 
582     unsigned int seqNum;
583         /* Sequence number of entry within color table in XPM header */
584 
585     for (seqNum = 0; seqNum < nColors; ++seqNum) {
586         char line[MAX_LINE+1];
587         getLine(line, sizeof(line), ifP);
588         /* skip the comment line if any */
589         if (strneq(line, "/*", 2))
590             getLine(line, sizeof(line), ifP);
591 
592         interpretXpm3ColorTableLine(line, seqNum, charsPerPixel,
593                                     colorNameHashP);
594 
595     }
596     *colorNameHashPP = colorNameHashP;
597 }
598 
599 
600 
601 static void
readXpm3Header(FILE * const ifP,unsigned int * const widthP,unsigned int * const heightP,unsigned int * const charsPerPixelP,ColorNameHash ** const colorNameHashPP)602 readXpm3Header(FILE *             const ifP,
603                unsigned int *     const widthP,
604                unsigned int *     const heightP,
605                unsigned int *     const charsPerPixelP,
606                ColorNameHash **   const colorNameHashPP) {
607 /*----------------------------------------------------------------------------
608   Read the header of the XPM file on stream *ifP.  Assume the
609   getLine() stream is currently positioned to the beginning of the
610   file and it is a Version 3 XPM file.  Leave the stream positioned
611   after the header.
612 
613   Return as *widthP and *heightP the dimensions of the image indicated
614   by the header.
615 
616   Return as *charsPerPixelP the number of characters the header says the
617   raster uses for each pixel, i.e. the XPM color name length.
618 
619   Return the color map as *colorNameHashPP.
620 -----------------------------------------------------------------------------*/
621     char line[MAX_LINE+1];
622     const char * xpm3_signature = "/* XPM */";
623 
624     unsigned int width, height;
625     unsigned int nColors;
626     unsigned int charsPerPixel;
627 
628     /* Read the XPM signature comment */
629     getLine(line, sizeof(line), ifP);
630     if (!strneq(line, xpm3_signature, strlen(xpm3_signature)))
631         pm_error("Apparent XPM 3 file does not start with '/* XPM */'.  "
632                  "First line is '%s'", xpm3_signature);
633 
634     /* Read the assignment line */
635     getLine(line, sizeof(line), ifP);
636     if (!strneq(line, "static char", 11))
637         pm_error("Cannot find data structure declaration.  Expected a "
638                  "line starting with 'static char', but found the line "
639                  "'%s'.", line);
640 
641     getLine(line, sizeof(line), ifP);
642 
643     /* Skip the comment block, if one starts here */
644     if (strneq(line, "/*", 2)) {
645         while (!strstr(line, "*/"))
646             getLine(line, sizeof(line), ifP);
647         getLine(line, sizeof(line), ifP);
648     }
649 
650     /* Parse the hints line */
651     if (sscanf(line, "\"%u %u %u %u\",", &width, &height,
652                &nColors, &charsPerPixel) != 4)
653         pm_error("error scanning hints line");
654 
655     if (verbose) {
656         pm_message("Width x Height:  %u x %u", width, height);
657         pm_message("no. of colors:  %u", nColors);
658         pm_message("chars per pixel:  %u", charsPerPixel);
659     }
660 
661     readV3ColorTable(ifP, colorNameHashPP, nColors, charsPerPixel);
662 
663     *widthP         = width;
664     *heightP        = height;
665     *charsPerPixelP = charsPerPixel;
666 }
667 
668 
669 
670 static void
readV1ColorTable(FILE * const ifP,ColorNameHash ** const colorNameHashPP,unsigned int const nColors,unsigned int const charsPerPixel)671 readV1ColorTable(FILE *           const ifP,
672                  ColorNameHash ** const colorNameHashPP,
673                  unsigned int     const nColors,
674                  unsigned int     const charsPerPixel) {
675 /*----------------------------------------------------------------------------
676    Read the color table from the XPM Version 1 header.
677 
678    Assume *ifP is positioned to the color table; leave it positioned after.
679 -----------------------------------------------------------------------------*/
680     ColorNameHash * const colorNameHashP = hash_create(nColors, charsPerPixel);
681 
682     unsigned int i;
683 
684     for (i = 0; i < nColors; ++i) {
685         char line[MAX_LINE+1];
686         char str1[MAX_LINE+1];
687         char str2[MAX_LINE+1];
688         char * t1;
689         char * t2;
690 
691         getLine(line, sizeof(line), ifP);
692 
693         if ((t1 = strchr(line, '"')) == NULL)
694             pm_error("D error scanning color table");
695         if ((t2 = strchr(t1 + 1, '"')) == NULL)
696             pm_error("E error scanning color table");
697         if (t2 - t1 - 1 != charsPerPixel)
698             pm_error("wrong number of chars per pixel in color table");
699         strncpy(str1, t1 + 1, t2 - t1 - 1);
700         str1[t2 - t1 - 1] = '\0';
701 
702         if ((t1 = strchr(t2 + 1, '"')) == NULL)
703             pm_error("F error scanning color table");
704         if ((t2 = strchr(t1 + 1, '"')) == NULL)
705             pm_error("G error scanning color table");
706         strncpy(str2, t1 + 1, t2 - t1 - 1);
707         str2[t2 - t1 - 1] = '\0';
708 
709         addToColorMap(colorNameHashP, str1, str2, false);
710     }
711     *colorNameHashPP = colorNameHashP;
712 }
713 
714 
715 
716 static void
readXpm1Header(FILE * const ifP,unsigned int * const widthP,unsigned int * const heightP,unsigned int * const charsPerPixelP,ColorNameHash ** const colorNameHashPP)717 readXpm1Header(FILE *           const ifP,
718                unsigned int *   const widthP,
719                unsigned int *   const heightP,
720                unsigned int *   const charsPerPixelP,
721                ColorNameHash ** const colorNameHashPP) {
722 /*----------------------------------------------------------------------------
723   Read the header of the XPM file on stream *ifP.  Assume the
724   getLine() stream is currently positioned to the beginning of the
725   file and it is a Version 1 XPM file.  Leave the stream positioned
726   after the header.
727 
728   Return the information from the header the same as for readXpm3Header.
729 -----------------------------------------------------------------------------*/
730     int format, v;
731     bool processedStaticChar;
732         /* We have read up to and interpreted the "static char..." line */
733     char * t1;
734     unsigned int nColors;
735     bool gotPixel, gotNColors, gotWidth, gotHeight, gotFormat;
736 
737     gotNColors = false;
738     gotWidth   = false;
739     gotHeight  = false;
740     gotFormat  = false;
741     gotPixel   = false;
742 
743     /* Read the initial defines. */
744     processedStaticChar = FALSE;
745     while (!processedStaticChar) {
746         char line[MAX_LINE+1];
747         char str1[MAX_LINE+1];
748 
749         getLine(line, sizeof(line), ifP);
750 
751         if (sscanf(line, "#define %s %d", str1, &v) == 2) {
752             if ((t1 = strrchr(str1, '_')) == NULL)
753                 t1 = str1;
754             else
755                 ++t1;
756             if (streq(t1, "format")) {
757                 gotFormat = true;
758                 format = v;
759             } else if (streq(t1, "width")) {
760                 gotWidth = true;
761                 *widthP = v;
762             } else if (streq(t1, "height")) {
763                 gotHeight = true;
764                 *heightP = v;
765             } else if (streq(t1, "nColors")) {
766                 gotNColors = true;
767                 nColors = v;
768             } else if (streq(t1, "pixel")) {
769                 gotPixel = TRUE;
770                 *charsPerPixelP = v;
771             }
772         } else if (strneq(line, "static char", 11)) {
773             if ((t1 = strrchr(line, '_')) == NULL)
774                 t1 = line;
775             else
776                 ++t1;
777             processedStaticChar = TRUE;
778         }
779     }
780     /* File is positioned to "static char" line, which is in line[] and
781        t1 points to position of last "_" in the line, or the beginning of
782        the line if there is no "_"
783     */
784     if (!gotPixel)
785         pm_error("No 'pixel' value (characters per pixel)");
786     if (!gotFormat)
787         pm_error("missing or invalid format");
788     if (format != 1)
789         pm_error("can't handle XPM version %d", format);
790     if (!gotWidth)
791         pm_error("missing or invalid width");
792     if (!gotHeight)
793         pm_error("missing or invalid height");
794     if (!gotNColors)
795         pm_error("missing or invalid nColors");
796 
797     if (*charsPerPixelP > 2)
798         pm_message("WARNING: > 2 characters per pixel uses a lot of memory");
799 
800     /* If there's a monochrome color table, skip it. */
801     if (strneq(t1, "mono", 4)) {
802         for (;;) {
803             char line[MAX_LINE+1];
804             getLine(line, sizeof(line), ifP);
805             if (strneq(line, "static char", 11))
806                 break;
807         }
808     }
809     readV1ColorTable(ifP, colorNameHashPP, nColors, *charsPerPixelP);
810 
811     /* Position to first line of raster (which is the line after
812        "static char ...").
813     */
814     for (;;) {
815         char line[MAX_LINE+1];
816         getLine(line, sizeof(line), ifP);
817         if (strneq(line, "static char", 11))
818             break;
819     }
820 }
821 
822 
823 
824 static void
validateRasterPixel(const char * const pixelChars,unsigned int const charsPerPixel)825 validateRasterPixel(const char * const pixelChars,
826                     unsigned int const charsPerPixel) {
827 
828     unsigned int i;
829 
830     for (i = 0; i < charsPerPixel; ++i) {
831         if (pixelChars[i] == '\0')
832             pm_error("XPM input file ends in the middle of a string "
833                      "that represents a raster line");
834         else if (pixelChars[i] == '"')
835             pm_error("A string that represents a raster line in the "
836                      "XPM input file is too short to contain all the "
837                      "pixels (%u characters each)",
838                      charsPerPixel);
839     }
840 }
841 
842 
843 
844 static void
convertRow(char const line[],unsigned int const width,unsigned int const charsPerPixel,const ColorNameHash * const colorNameHashP,pixel * const pixrow,bit * const alpharow)845 convertRow(char                  const line[],
846            unsigned int          const width,
847            unsigned int          const charsPerPixel,
848            const ColorNameHash * const colorNameHashP,
849            pixel *               const pixrow,
850            bit *                 const alpharow) {
851 /*----------------------------------------------------------------------------
852    Convert one row from XPM input, which describes one raster line of the
853    image, to PPM.  The XPM line is in 'line', and its format is 'width' pixel,
854    'charsPerPixel' characters per pixel.  *colorNameHashP is the color table
855    that applies to the line.
856 
857    Put the PPM pixels in 'pixrow'.
858 
859    Also produce PBM row 'alpharow' with the transparency information from the
860    row.
861 
862    If the line doesn't start with a quote (e.g. it is empty), we issue
863    a warning and just treat the line as one that describes no pixels.
864 
865    Abort program if there aren't exactly 'width' pixels in the line.
866 -----------------------------------------------------------------------------*/
867     const char * lineCursor;
868 
869     lineCursor = strchr(line, '"');  /* position to 1st quote in line */
870     if (lineCursor == NULL) {
871         /* We've seen a purported XPM that had a blank line in it.  Just
872            ignoring it was the right thing to do.  05.05.27.
873         */
874         pm_message("WARNING:  No opening quotation mark in XPM input "
875                    "line which is supposed to be a line of raster data: "
876                    "'%s'.  Ignoring this line.", line);
877     } else {
878         unsigned int col;
879 
880         ++lineCursor; /* Skip to first character after quote */
881 
882         /* Handle pixels until a close quote, eol, or we've returned all
883            the pixels Caller wants.
884         */
885         for (col = 0; col < width; ++col) {
886 
887             validateRasterPixel(lineCursor, charsPerPixel);
888 
889             pixrow[col] = hash_color(colorNameHashP, lineCursor);
890 
891             alpharow[col] = hash_isTransparent(colorNameHashP, lineCursor) ?
892                 PBM_BLACK : PBM_WHITE;
893 
894             lineCursor += charsPerPixel;
895         }
896         if (*lineCursor != '"')
897             pm_error("A raster line continues past width of image");
898     }
899 }
900 
901 
902 
903 static void
convertRaster(FILE * const ifP,unsigned int const cols,unsigned int const rows,unsigned int const charsPerPixel,const ColorNameHash * const colorNameHashP,FILE * const imageOutFileP,FILE * const alphaOutFileP)904 convertRaster(FILE *                const ifP,
905               unsigned int          const cols,
906               unsigned int          const rows,
907               unsigned int          const charsPerPixel,
908               const ColorNameHash * const colorNameHashP,
909               FILE *                const imageOutFileP,
910               FILE *                const alphaOutFileP) {
911 /*----------------------------------------------------------------------------
912   Read the XPM raster from *ifP and write the PPM raster to *imageOutFileP
913   and the alpha channel to *alphaOutFileP (where those are, respectively,
914   non-null).
915 
916   The dimensions are 'cols' by 'rows' and the color map for the XPM
917   raster is *colorNameHashP.
918 -----------------------------------------------------------------------------*/
919     char line[MAX_LINE+1];
920     pixel * pixrow;
921     bit * alpharow;
922     unsigned int row;
923 
924     pixrow   = ppm_allocrow(cols);
925     alpharow = pbm_allocrow(cols);
926 
927     for (row = 0; row < rows; ++row) {
928         bool haveLine;
929 
930         for (haveLine = false; !haveLine; ) {
931             getLine(line, sizeof(line), ifP);
932 
933             if (strneq(line, "/*", 2)) {
934                 /* It's a comment.  Ignore it. */
935             } else
936                 haveLine = true;
937         }
938         convertRow(line, cols, charsPerPixel, colorNameHashP,
939                    pixrow, alpharow);
940 
941         if (imageOutFileP)
942             ppm_writeppmrow(imageOutFileP,
943                             pixrow, cols, PPM_MAXMAXVAL, 0);
944         if (alphaOutFileP)
945             pbm_writepbmrow(alphaOutFileP, alpharow, cols, 0);
946     }
947 
948     pbm_freerow(alpharow);
949     ppm_freerow(pixrow);
950 }
951 
952 
953 
954 static void
readXpmHeader(FILE * const ifP,unsigned int * const widthP,unsigned int * const heightP,unsigned int * const charsPerPixelP,ColorNameHash ** const colorNameHashPP)955 readXpmHeader(FILE *           const ifP,
956               unsigned int *   const widthP,
957               unsigned int *   const heightP,
958               unsigned int *   const charsPerPixelP,
959               ColorNameHash ** const colorNameHashPP) {
960 /*----------------------------------------------------------------------------
961   Read the XPM header, including color map.
962 
963   In the colormap, put black for the transparent color, if the XPM image
964   contains one.
965 -----------------------------------------------------------------------------*/
966     char line[MAX_LINE+1];
967     char str1[MAX_LINE+1];
968     int rc;
969     unsigned int charsPerPixel;
970     unsigned int width, height;
971 
972     backup = FALSE;
973 
974     /* Read the header line */
975     getLine(line, sizeof(line), ifP);
976     backup = TRUE;  /* back up so next read reads this line again */
977 
978     rc = sscanf(line, "/* %s */", str1);
979     if (rc == 1 && strneq(str1, "XPM", 3)) {
980         /* It's an XPM version 3 file */
981         readXpm3Header(ifP, &width, &height, &charsPerPixel, colorNameHashPP);
982     } else {
983         /* Assume it's an XPM version 1 file */
984         readXpm1Header(ifP, &width, &height, &charsPerPixel, colorNameHashPP);
985     }
986     *widthP         = width;
987     *heightP        = height;
988     *charsPerPixelP = charsPerPixel;
989 }
990 
991 
992 
993 int
main(int argc,char * argv[])994 main(int argc, char *argv[]) {
995 
996     FILE * ifP;
997     FILE * alphaOutFileP;
998     FILE * imageOutFileP;
999     unsigned int cols, rows;
1000     unsigned int charsPerPixel;
1001     ColorNameHash * colorNameHashP;
1002 
1003     struct cmdlineInfo cmdline;
1004 
1005     ppm_init(&argc, argv);
1006 
1007     parseCommandLine(argc, argv, &cmdline);
1008 
1009     verbose = cmdline.verbose;
1010 
1011     if ( cmdline.input_filespec != NULL )
1012         ifP = pm_openr( cmdline.input_filespec);
1013     else
1014         ifP = stdin;
1015 
1016     if (cmdline.alpha_stdout)
1017         alphaOutFileP = stdout;
1018     else if (cmdline.alpha_filename == NULL)
1019         alphaOutFileP = NULL;
1020     else {
1021         alphaOutFileP = pm_openw(cmdline.alpha_filename);
1022     }
1023 
1024     if (cmdline.alpha_stdout)
1025         imageOutFileP = NULL;
1026     else
1027         imageOutFileP = stdout;
1028 
1029     readXpmHeader(ifP, &cols, &rows, &charsPerPixel, &colorNameHashP);
1030 
1031     if (imageOutFileP)
1032         ppm_writeppminit(imageOutFileP, cols, rows, PPM_MAXMAXVAL, 0);
1033     if (alphaOutFileP)
1034         pbm_writepbminit(alphaOutFileP, cols, rows, 0);
1035 
1036 
1037     convertRaster(ifP, cols, rows, charsPerPixel, colorNameHashP,
1038                   imageOutFileP, alphaOutFileP);
1039 
1040     pm_close(ifP);
1041     if (imageOutFileP)
1042         pm_close(imageOutFileP);
1043     if (alphaOutFileP)
1044         pm_close(alphaOutFileP);
1045 
1046     hash_destroy(colorNameHashP);
1047 
1048     return 0;
1049 }
1050 
1051 
1052 
1053 /*
1054 **
1055 ** Copyright (C) 1991 by Jef Poskanzer.
1056 **
1057 ** Permission to use, copy, modify, and distribute this software and its
1058 ** documentation for any purpose and without fee is hereby granted, provided
1059 ** that the above copyright notice appear in all copies and that both that
1060 ** copyright notice and this permission notice appear in supporting
1061 ** documentation.  This software is provided "as is" without express or
1062 ** implied warranty.
1063 **
1064 ** Upgraded to handle XPM version 3 by
1065 **   Arnaud Le Hors (lehors@mirsa.inria.fr)
1066 **   Tue Apr 9 1991
1067 **
1068 ** Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91:
1069 **  - Bug fix, no advance of read ptr, would not read
1070 **    colors like "ac c black" because it would find
1071 **    the "c" of "ac" and then had problems with "c"
1072 **    as color.
1073 **
1074 **  - Now understands multiword X11 color names
1075 **
1076 **  - Now reads multiple color keys. Takes the color
1077 **    of the highest available key. Lines no longer need
1078 **    to begin with key 'c'.
1079 **
1080 **  - expanded line buffer to from 500 to 2048 for bigger files
1081 */
1082 
1083