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