1 /* ppmtoxpm.c - read a portable pixmap and produce a (version 3) X11 pixmap
2 **
3 ** Copyright (C) 1990 by Mark W. Snitily
4 **
5 ** Permission to use, copy, modify, and distribute this software and its
6 ** documentation for any purpose and without fee is hereby granted, provided
7 ** that the above copyright notice appear in all copies and that both that
8 ** copyright notice and this permission notice appear in supporting
9 ** documentation. This software is provided "as is" without express or
10 ** implied warranty.
11 **
12 ** This tool was developed for Schlumberger Technologies, ATE Division, and
13 ** with their permission is being made available to the public with the above
14 ** copyright notice and permission notice.
15 **
16 ** Upgraded to XPM2 by
17 ** Paul Breslaw, Mecasoft SA, Zurich, Switzerland (paul@mecazh.uu.ch)
18 ** Thu Nov 8 16:01:17 1990
19 **
20 ** Upgraded to XPM version 3 by
21 ** Arnaud Le Hors (lehors@mirsa.inria.fr)
22 ** Tue Apr 9 1991
23 **
24 ** Rainer Sinkwitz sinkwitz@ifi.unizh.ch - 21 Nov 91:
25 ** - Bug fix, should should malloc space for rgbn[j].name+1 in line 441
26 ** caused segmentation faults
27 **
28 ** - lowercase conversion of RGB names def'ed out,
29 ** considered harmful.
30 **
31 ** Michael Pall (pall@rz.uni-karlsruhe.de) - 29 Nov 93:
32 ** - Use the algorithm from xpm-lib for pixel encoding
33 ** (base 93 not base 28 -> saves a lot of space for colorful xpms)
34 */
35
36 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
37 #define _BSD_SOURCE /* Make sure strdup() is in string.h */
38 #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */
39
40 #include <stdio.h>
41 #include <ctype.h>
42 #include <string.h>
43
44 #include "pm_c_util.h"
45 #include "ppm.h"
46 #include "shhopt.h"
47 #include "nstring.h"
48 #include "mallocvar.h"
49
50 /* Max number of entries we will put in the XPM color map
51 Don't forget the one entry for transparency.
52
53 We don't use this anymore. Ppmtoxpm now has no arbitrary limit on
54 the number of colors.
55
56 We're assuming this isn't in fact an XPM format requirement, because
57 we've seen it work with 257, and 257 seems to be common, because it's
58 the classic 256 colors, plus transparency. The value was 256 for
59 ages before we added transparency capability to this program in May
60 2001. At that time, we started failing with 256 color images.
61 Some limit was also necessary before then because ppm_computecolorhash()
62 required us to specify a maximum number of colors. It doesn't anymore.
63
64 If we find out that some XPM processing programs choke on more than
65 256 colors, we'll have to readdress this issue. - Bryan. 2001.05.13.
66 */
67 #define MAXCOLORS 256
68
69 #define MAXPRINTABLE 92 /* number of printable ascii chars
70 * minus \ and " for string compat
71 * and ? to avoid ANSI trigraphs. */
72
73 static const char * const printable =
74 " .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZ\
75 ASDFGHJKLPIUYTREWQ!~^/()_`'][{}|";
76
77
78 struct cmdlineInfo {
79 /* All the information the user supplied in the command line,
80 in a form easy for the program to use.
81 */
82 const char *inputFilename;
83 const char *name;
84 const char *rgb;
85 const char *alpha_filename;
86 unsigned int hexonly;
87 unsigned int verbose;
88 };
89
90
91
92 static void
parseCommandLine(int argc,char ** argv,struct cmdlineInfo * const cmdlineP)93 parseCommandLine(int argc, char ** argv,
94 struct cmdlineInfo * const cmdlineP) {
95 /*----------------------------------------------------------------------------
96 Note that the file spec array we return is stored in the storage that
97 was passed to us as the argv array.
98 -----------------------------------------------------------------------------*/
99 optEntry *option_def;
100 /* Instructions to OptParseOptions3 on how to parse our options.
101 */
102 optStruct3 opt;
103
104 unsigned int option_def_index;
105 const char * nameOpt;
106 unsigned int nameSpec;
107
108 MALLOCARRAY(option_def, 100);
109
110 option_def_index = 0; /* incremented by OPTENTRY */
111 OPTENT3(0, "alphamask", OPT_STRING, &cmdlineP->alpha_filename,
112 NULL, 0);
113 OPTENT3(0, "name", OPT_STRING, &nameOpt,
114 &nameSpec, 0);
115 OPTENT3(0, "rgb", OPT_STRING, &cmdlineP->rgb,
116 NULL, 0);
117 OPTENT3(0, "hexonly", OPT_FLAG, NULL,
118 &cmdlineP->hexonly, 0);
119 OPTENT3(0, "verbose", OPT_FLAG, NULL,
120 &cmdlineP->verbose, 0);
121
122 /* Set the defaults */
123 cmdlineP->alpha_filename = NULL; /* no transparency */
124 cmdlineP->rgb = NULL; /* no rgb file specified */
125
126 opt.opt_table = option_def;
127 opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */
128 opt.allowNegNum = FALSE; /* We may have parms that are negative numbers */
129
130 pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
131 /* Uses and sets argc, argv, and some of *cmdlineP and others. */
132
133 if (argc-1 == 0)
134 cmdlineP->inputFilename = "-";
135 else if (argc-1 != 1)
136 pm_error("Program takes zero or one argument (filename). You "
137 "specified %d", argc-1);
138 else
139 cmdlineP->inputFilename = argv[1];
140
141 /* If output filename not specified, use input filename as default. */
142 if (nameSpec)
143 cmdlineP->name = nameOpt;
144 else if (streq(cmdlineP->inputFilename, "-"))
145 cmdlineP->name = "noname";
146 else {
147 static char name[80+1];
148 char *cp;
149
150 STRSCPY(name, cmdlineP->inputFilename);
151 cp = strchr(name, '.');
152 if (cp)
153 *cp = '\0'; /* remove extension */
154 cmdlineP->name = name;
155 }
156 }
157
158
159 typedef struct { /* rgb values and ascii names (from
160 * rgb text file) */
161 int r, g, b; /* rgb values, range of 0 -> 65535 */
162 char *name; /* color mnemonic of rgb value */
163 } rgb_names;
164
165 typedef struct {
166 /* Information for an XPM color map entry */
167 char *cixel;
168 /* XPM color number, as might appear in the XPM raster */
169 const char *rgbname;
170 /* color name, e.g. "blue" or "#01FF23" */
171 } cixel_map;
172
173
174
175 static char *
genNumstr(unsigned int const input,int const digits)176 genNumstr(unsigned int const input, int const digits) {
177 /*---------------------------------------------------------------------------
178 Given a number and a base (MAXPRINTABLE), this routine prints the
179 number into a malloc'ed string and returns it. The length of the
180 string is specified by "digits". It is not printed in decimal or
181 any other number system you're used to. Rather, each digit is from
182 the set printable[], which contains MAXPRINTABLE characters; the
183 character printable[n] has value n.
184
185 The string is printable[0] filled with high order zeroes.
186
187 Example:
188 Assume:
189 printable[0] == 'q'
190 printable[1] == '%'
191 MAXPRINTABLE == 2
192 digits == 5
193 input == 3
194 Result is the malloc'ed string "qqq%%"
195 ---------------------------------------------------------------------------*/
196 char *str, *p;
197 int d;
198 unsigned int i;
199
200 /* Allocate memory for printed number. Abort if error. */
201 if (!(str = (char *) malloc(digits + 1)))
202 pm_error("out of memory");
203
204 i = input;
205 /* Generate characters starting with least significant digit. */
206 p = str + digits;
207 *p-- = '\0'; /* nul terminate string */
208 while (p >= str) {
209 d = i % MAXPRINTABLE;
210 i /= MAXPRINTABLE;
211 *p-- = printable[d];
212 }
213
214 if (i != 0)
215 pm_error("Overflow converting %d to %d digits in base %d",
216 input, digits, MAXPRINTABLE);
217
218 return str;
219 }
220
221
222
223 static unsigned int
xpmMaxvalFromMaxval(pixval const maxval)224 xpmMaxvalFromMaxval(pixval const maxval) {
225
226 unsigned int retval;
227
228 /*
229 * Determine how many hex digits we'll be normalizing to if the rgb
230 * value doesn't match a color mnemonic.
231 */
232 if (maxval <= 0x000F)
233 retval = 0x000F;
234 else if (maxval <= 0x00FF)
235 retval = 0x00FF;
236 else if (maxval <= 0x0FFF)
237 retval = 0x0FFF;
238 else if (maxval <= 0xFFFF)
239 retval = 0xFFFF;
240 else
241 pm_error("Internal error - impossible maxval %x", maxval);
242
243 return retval;
244 }
245
246
247
248 static unsigned int
charsPerPixelForSize(unsigned int const cmapSize)249 charsPerPixelForSize(unsigned int const cmapSize) {
250 /*----------------------------------------------------------------------------
251 Return the number of characters it will take to represent a pixel in
252 an XPM that has a colormap of size 'cmapSize'. Each pixel in an XPM
253 represents an index into the colormap with a base-92 scheme where each
254 character is one of 92 printable characters. Ergo, if the colormap
255 has at most 92 characters, each pixel will be represented by a single
256 character. If it has more than 92, but at most 92*92, it will take 2,
257 etc.
258
259 If cmapSize is zero, there's no such thing as an XPM pixel, so we
260 return an undefined value.
261 -----------------------------------------------------------------------------*/
262 unsigned int charsPerPixel;
263
264 if (cmapSize > 0) {
265 unsigned int j;
266
267 for (charsPerPixel = 0, j = cmapSize-1; j > 0; ++charsPerPixel)
268 j /= MAXPRINTABLE;
269 }
270 return charsPerPixel;
271 }
272
273
274
275 static void
genCmap(colorhist_vector const chv,int const ncolors,pixval const maxval,colorhash_table const colornameHash,const char * const colornames[],bool const includeTransparent,cixel_map ** const cmapP,unsigned int * const transIndexP,unsigned int * const cmapSizeP,unsigned int * const charsPerPixelP)276 genCmap(colorhist_vector const chv,
277 int const ncolors,
278 pixval const maxval,
279 colorhash_table const colornameHash,
280 const char * const colornames[],
281 bool const includeTransparent,
282 cixel_map ** const cmapP,
283 unsigned int * const transIndexP,
284 unsigned int * const cmapSizeP,
285 unsigned int * const charsPerPixelP) {
286 /*----------------------------------------------------------------------------
287 Generate the XPM color map in cixel_map format (which is just a step
288 away from the actual text that needs to be written the XPM file). The
289 color map is defined by 'chv', which contains 'ncolors' colors which
290 have maxval 'maxval'.
291
292 Output is in newly malloc'ed storage, with its address returned as
293 *cmapP. We return the number of entries in it as *cmapSizeP.
294
295 This map includes an entry for transparency, whether the raster uses
296 it or not. We return its index as *transIndexP.
297
298 In the map, identify colors by the names given by 'colornameHash' and
299 colornames[]. 'colornameHash' maps a color in 'pixel' form to an
300 index into colornames[]; colornames[] contains the text to identify the
301 color in the XPM format. The colors in 'colornameHash' have maxval 255.
302 If a color is not in 'colornameHash', use hexadecimal notation in the
303 output colormap.
304
305 But if 'colornameHash' is null, don't use color names at all. Just use
306 hexadecimal notation.
307
308 Return as *charsPerPixel the number of characters, or digits, that
309 will be needed in the XPM raster to form an index into this color map.
310 -----------------------------------------------------------------------------*/
311 unsigned int const cmapSize = ncolors + (includeTransparent ? 1 : 0);
312
313 cixel_map * cmap; /* malloc'ed */
314 unsigned int cmapIndex;
315 unsigned int charsPerPixel;
316 unsigned int xpmMaxval;
317
318 MALLOCARRAY(cmap, cmapSize);
319 if (cmapP == NULL)
320 pm_error("Out of memory allocating %u bytes for a color map.",
321 (unsigned)sizeof(cixel_map) * (ncolors+1));
322
323 xpmMaxval = xpmMaxvalFromMaxval(maxval);
324
325 charsPerPixel = charsPerPixelForSize(cmapSize);
326
327 /*
328 * Generate the character-pixel string and the rgb name for each
329 * colormap entry.
330 */
331 for (cmapIndex = 0; cmapIndex < ncolors; ++cmapIndex) {
332 pixel const color = chv[cmapIndex].color;
333
334 pixel color255;
335 /* The color, scaled to maxval 255 */
336 const char * colorname; /* malloc'ed */
337 /*
338 * The character-pixel string is simply a printed number in base
339 * MAXPRINTABLE where the digits of the number range from
340 * printable[0] .. printable[MAXPRINTABLE-1] and the printed length
341 * of the number is 'charsPerPixel'.
342 */
343 cmap[cmapIndex].cixel = genNumstr(cmapIndex, charsPerPixel);
344
345 PPM_DEPTH(color255, color, maxval, 255);
346
347 if (colornameHash == NULL)
348 colorname = NULL;
349 else {
350 int colornameIndex;
351 colornameIndex = ppm_lookupcolor(colornameHash, &color255);
352 if (colornameIndex >= 0)
353 colorname = strdup(colornames[colornameIndex]);
354 else
355 colorname = NULL;
356 }
357 if (colorname)
358 cmap[cmapIndex].rgbname = colorname;
359 else {
360 /* Color has no name; represent it in hexadecimal */
361
362 pixel scaledColor;
363 const char * hexString; /* malloc'ed */
364
365 PPM_DEPTH(scaledColor, color, maxval, xpmMaxval);
366
367 pm_asprintf(&hexString, xpmMaxval == 0x000F ? "#%X%X%X" :
368 xpmMaxval == 0x00FF ? "#%02X%02X%02X" :
369 xpmMaxval == 0x0FFF ? "#%03X%03X%03X" :
370 "#%04X%04X%04X",
371 PPM_GETR(scaledColor),
372 PPM_GETG(scaledColor),
373 PPM_GETB(scaledColor)
374 );
375
376 if (hexString == NULL)
377 pm_error("Unable to allocate storage for hex string");
378 cmap[cmapIndex].rgbname = hexString;
379 }
380 }
381
382 if (includeTransparent) {
383 /* Add the special transparency entry to the colormap */
384 unsigned int const transIndex = ncolors;
385 cmap[transIndex].rgbname = strdup("None");
386 cmap[transIndex].cixel = genNumstr(transIndex, charsPerPixel);
387 *transIndexP = transIndex;
388 }
389 *cmapP = cmap;
390 *cmapSizeP = cmapSize;
391 *charsPerPixelP = charsPerPixel;
392 }
393
394
395
396 static void
destroyCmap(cixel_map * const cmap,unsigned int const cmapSize)397 destroyCmap(cixel_map * const cmap,
398 unsigned int const cmapSize) {
399
400 int i;
401 /* Free the real color entries */
402 for (i = 0; i < cmapSize; i++) {
403 pm_strfree(cmap[i].rgbname);
404 free(cmap[i].cixel);
405 }
406 free(cmap);
407 }
408
409
410
411 static void
writeXpmFile(FILE * const outfile,pixel ** const pixels,gray ** const alpha,pixval const alphamaxval,char const name[],int const cols,int const rows,unsigned int const cmapSize,unsigned int const charsPerPixel,cixel_map const cmap[],colorhash_table const cht,unsigned int const transIndex)412 writeXpmFile(FILE * const outfile,
413 pixel ** const pixels,
414 gray ** const alpha,
415 pixval const alphamaxval,
416 char const name[],
417 int const cols,
418 int const rows,
419 unsigned int const cmapSize,
420 unsigned int const charsPerPixel,
421 cixel_map const cmap[],
422 colorhash_table const cht,
423 unsigned int const transIndex) {
424 /*----------------------------------------------------------------------------
425 Write the whole XPM file to the open stream 'outfile'.
426
427 'cmap' is the colormap to be placed in the XPM. 'cmapSize' is the
428 number of entries in it. 'cht' is a hash table that gives you an
429 index into 'cmap' given a color. 'transIndex' is the index into cmap
430 of the transparent color, and is valid only if 'alpha' is non-null
431 (otherwise, cmap might not contain a transparent color).
432 -----------------------------------------------------------------------------*/
433 /* First the header */
434 printf("/* XPM */\n");
435 fprintf(outfile, "static char *%s[] = {\n", name);
436 fprintf(outfile, "/* width height ncolors chars_per_pixel */\n");
437 fprintf(outfile, "\"%d %d %d %d\",\n", cols, rows,
438 cmapSize, charsPerPixel);
439
440 {
441 int i;
442 /* Write out the colormap (part of header) */
443 fprintf(outfile, "/* colors */\n");
444 for (i = 0; i < cmapSize; i++) {
445 fprintf(outfile, "\"%s c %s\",\n", cmap[i].cixel, cmap[i].rgbname);
446 }
447 }
448 {
449 int row;
450
451 /* And now the raster */
452 fprintf(outfile, "/* pixels */\n");
453 for (row = 0; row < rows; row++) {
454 int col;
455 fprintf(outfile, "\"");
456 for (col = 0; col < cols; col++) {
457 if (alpha && alpha[row][col] <= alphamaxval/2)
458 /* It's a transparent pixel */
459 fprintf(outfile, "%s", cmap[transIndex].cixel);
460 else
461 fprintf(outfile, "%s",
462 cmap[ppm_lookupcolor(cht,
463 &pixels[row][col])].cixel);
464 }
465 fprintf(outfile, "\"%s\n", (row == (rows - 1) ? "" : ","));
466 }
467 }
468 /* And close up */
469 fprintf(outfile, "};\n");
470 }
471
472
473
474 static void
readAlpha(const char filespec[],gray *** const alphaP,int const cols,int const rows,pixval * const alphamaxvalP)475 readAlpha(const char filespec[], gray *** const alphaP,
476 int const cols, int const rows, pixval * const alphamaxvalP) {
477
478 FILE * alpha_file;
479 int alphacols, alpharows;
480
481 alpha_file = pm_openr(filespec);
482 *alphaP = pgm_readpgm(alpha_file, &alphacols, &alpharows, alphamaxvalP);
483 pm_close(alpha_file);
484
485 if (cols != alphacols || rows != alpharows)
486 pm_error("Alpha mask is not the same dimensions as the "
487 "image. Image is %d by %d, while mask is %d x %d.",
488 cols, rows, alphacols, alpharows);
489 }
490
491
492
493 static void
computecolorhash(pixel ** const pixels,gray ** const alpha,int const cols,int const rows,gray const alphaMaxval,colorhash_table * const chtP,unsigned int * const ncolorsP,bool * const transparentSomewhereP)494 computecolorhash(pixel ** const pixels,
495 gray ** const alpha,
496 int const cols,
497 int const rows,
498 gray const alphaMaxval,
499 colorhash_table * const chtP,
500 unsigned int * const ncolorsP,
501 bool * const transparentSomewhereP) {
502 /*----------------------------------------------------------------------------
503 Compute a colorhash_table with one entry for each color in 'pixels' that
504 is not mostly transparent according to alpha mask 'alpha' (which has
505 maxval 'alphaMaxval'). alpha == NULL means all pixels are opaque.
506
507 The value associated with the color in the hash we build is meaningless.
508
509 Return the colorhash_table as *chtP, and the number of colors in it
510 as *ncolorsP. Return *transparentSomewhereP == TRUE iff the image has
511 at least one pixel that is mostly transparent.
512 -----------------------------------------------------------------------------*/
513 colorhash_table cht;
514 int row;
515
516 cht = ppm_alloccolorhash( );
517 *ncolorsP = 0; /* initial value */
518 *transparentSomewhereP = FALSE; /* initial assumption */
519
520 /* Go through the entire image, building a hash table of colors. */
521 for (row = 0; row < rows; ++row) {
522 int col;
523
524 for (col = 0; col < cols; ++col) {
525 if (!alpha || alpha[row][col] > alphaMaxval/2) {
526 /* It's mostly opaque, so add this color to the hash
527 if it's not already there.
528 */
529 pixel const color = pixels[row][col];
530 int const lookupRc = ppm_lookupcolor(cht, &color);
531
532 if (lookupRc < 0) {
533 /* It's not in the hash yet, so add it */
534 ppm_addtocolorhash(cht, &color, 0);
535 ++(*ncolorsP);
536 }
537 } else
538 *transparentSomewhereP = TRUE;
539 }
540 }
541 *chtP = cht;
542 }
543
544
545
546 static void
computeColormap(pixel ** const pixels,gray ** const alpha,int const cols,int const rows,gray const alphaMaxval,colorhist_vector * const chvP,colorhash_table * const chtP,unsigned int * const ncolorsP,bool * const transparentSomewhereP)547 computeColormap(pixel ** const pixels,
548 gray ** const alpha,
549 int const cols,
550 int const rows,
551 gray const alphaMaxval,
552 colorhist_vector * const chvP,
553 colorhash_table * const chtP,
554 unsigned int * const ncolorsP,
555 bool * const transparentSomewhereP) {
556 /*----------------------------------------------------------------------------
557 Compute the color map for the image 'pixels', which is 'cols' by 'rows',
558 in Netpbm data structures (a colorhist_vector for index-to-color lookups
559 and a colorhash_table for color-to-index lookups).
560
561 Exclude pixels that alpha mask 'alpha' (which has maxval
562 'alphaMaxval') says are mostly transparent. alpha == NULL means all
563 pixels are opaque.
564
565 We return as *chvP an array of the colors present in 'pixels',
566 excluding those that are mostly transparent. We return as
567 *ncolorsP the number of such colors. We return
568 *transparentSomewhereP == TRUE iff the image has at least one
569 pixel that is mostly transparent.
570 -----------------------------------------------------------------------------*/
571 colorhash_table histCht;
572
573 pm_message("(Computing colormap...");
574 computecolorhash(pixels, alpha, cols, rows, alphaMaxval,
575 &histCht, ncolorsP, transparentSomewhereP);
576 pm_message("...Done. %d colors found.)", *ncolorsP);
577
578 *chvP = ppm_colorhashtocolorhist(histCht, *ncolorsP);
579 ppm_freecolorhash(histCht);
580 /* Despite the name, the following generates an index on *chvP,
581 with which given a color you can quickly find the entry number
582 in *chvP that contains that color.
583 */
584 *chtP = ppm_colorhisttocolorhash(*chvP, *ncolorsP);
585 }
586
587
588
589 int
main(int argc,char * argv[])590 main(int argc, char *argv[]) {
591
592 FILE *ifp;
593 int rows, cols;
594 unsigned int ncolors;
595 bool transparentSomewhere;
596 pixval maxval, alphaMaxval;
597 colorhash_table cht;
598 colorhist_vector chv;
599
600 colorhash_table colornameHash;
601 /* Hash table to map colors to their names */
602 const char ** colornames;
603 /* Table of color names; 'colornameHash' yields an index into this
604 array.
605 */
606
607 pixel **pixels;
608 gray **alpha;
609
610 /* Used for rgb value -> character-pixel string mapping */
611 cixel_map *cmap; /* malloc'ed */
612 /* The XPM colormap */
613 unsigned int cmapSize;
614 /* Number of entries in 'cmap' */
615 unsigned int transIndex;
616 /* Index into 'cmap' of the transparent color, if there is one */
617
618 unsigned int charsPerPixel;
619
620 struct cmdlineInfo cmdline;
621
622 ppm_init(&argc, argv);
623
624 parseCommandLine(argc, argv, &cmdline);
625
626 ifp = pm_openr(cmdline.inputFilename);
627 pixels = ppm_readppm(ifp, &cols, &rows, &maxval);
628 pm_close(ifp);
629
630 if (cmdline.alpha_filename)
631 readAlpha(cmdline.alpha_filename, &alpha, cols, rows, &alphaMaxval);
632 else
633 alpha = NULL;
634
635 computeColormap(pixels, alpha, cols, rows, alphaMaxval,
636 &chv, &cht, &ncolors, &transparentSomewhere);
637
638 if (cmdline.hexonly)
639 colornameHash = NULL;
640 else if (cmdline.rgb)
641 ppm_readcolornamefile(cmdline.rgb, TRUE, &colornameHash, &colornames);
642 else
643 ppm_readcolornamefile(NULL, FALSE, &colornameHash, &colornames);
644
645 /* Now generate the character-pixel colormap table. */
646 genCmap(chv, ncolors, maxval,
647 colornameHash, colornames, transparentSomewhere,
648 &cmap, &transIndex, &cmapSize, &charsPerPixel);
649
650 writeXpmFile(stdout, pixels, alpha, alphaMaxval,
651 cmdline.name, cols, rows, cmapSize,
652 charsPerPixel, cmap, cht, transIndex);
653
654 if (colornameHash) {
655 ppm_freecolorhash(colornameHash);
656 ppm_freecolornames(colornames);
657 }
658 destroyCmap(cmap, cmapSize);
659 ppm_freearray(pixels, rows);
660 if (alpha) pgm_freearray(alpha, rows);
661
662 return 0;
663 }
664
665