1 /* pamtouil.c - convert PBM, PGM, PPM, or PPM+alpha to Motif UIL icon file
2 **
3 ** Bryan Henderson converted ppmtouil to pamtouil on 2002.05.04.
4 **
5 ** Jef Poskanzer derived pamtouil from ppmtoxpm, which is
6 ** Copyright (C) 1990 by Mark W. Snitily.
7 **
8 ** Permission to use, copy, modify, and distribute this software and its
9 ** documentation for any purpose and without fee is hereby granted, provided
10 ** that the above copyright notice appear in all copies and that both that
11 ** copyright notice and this permission notice appear in supporting
12 ** documentation.  This software is provided "as is" without express or
13 ** implied warranty.
14 */
15 
16 #define _DEFAULT_SOURCE /* New name for SVID & BSD source defines */
17 #define _BSD_SOURCE  /* Make sure string.h contains strdup() */
18 #define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
19 #include <ctype.h>
20 #include <string.h>
21 
22 #include "pm_c_util.h"
23 #include "pam.h"
24 #include "pammap.h"
25 #include "colorname.h"
26 #include "shhopt.h"
27 #include "nstring.h"
28 
29 /* Max number of colors allowed in ppm input. */
30 #define MAXCOLORS 256
31 
32 /* Lower bound and upper bound of character-pixels printed in UIL output. */
33 #define LOW_CHAR '`'
34 #define HIGH_CHAR '~'
35 
36 struct cmdlineInfo {
37     /* All the information the user supplied in the command line,
38        in a form easy for the program to use.
39     */
40     const char * inputFilespec;  /* Filespecs of input files */
41     char * outname;         /* output filename, less "_icon" */
42     unsigned int verbose;
43 };
44 
45 
46 
47 typedef struct {    /* character-pixel mapping */
48     const char * cixel;    /* character string printed for pixel */
49     const char * rgbname;  /* ascii rgb color, either mnemonic or #rgb value */
50     const char * uilname;  /* same, with spaces replaced by underbars */
51     bool         transparent;
52 } cixel_map;
53 
54 
55 
56 static void
parseCommandLine(int argc,char ** argv,struct cmdlineInfo * const cmdlineP)57 parseCommandLine(int argc, char ** argv,
58                  struct cmdlineInfo * const cmdlineP) {
59 /*----------------------------------------------------------------------------
60    Note that the file spec array we return is stored in the storage that
61    was passed to us as the argv array.  The outname array is in newly
62    malloc'ed storage.
63 -----------------------------------------------------------------------------*/
64     optEntry *option_def = malloc( 100*sizeof( optEntry ) );
65         /* Instructions to pm_optParseOptions3 on how to parse our options.
66          */
67     optStruct3 opt;
68 
69     unsigned int option_def_index;
70     unsigned int outnameSpec;
71     const char *outnameOpt;
72 
73     option_def_index = 0;   /* incremented by OPTENTRY */
74     OPTENT3(0, "name",       OPT_STRING, &outnameOpt,
75             &outnameSpec,       0);
76     OPTENT3(0, "verbose",    OPT_FLAG,   NULL,
77             &cmdlineP->verbose, 0);
78 
79     opt.opt_table = option_def;
80     opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
81     opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */
82 
83     pm_optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
84         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
85 
86     if (argc-1 == 0)
87         cmdlineP->inputFilespec = "-";  /* stdin */
88     else if (argc-1 == 1)
89         cmdlineP->inputFilespec = argv[1];
90     else
91         pm_error("Too many arguments (%d).  "
92                  "Only need one: the input filespec", argc-1);
93 
94     if (outnameSpec) {
95         char * barPos;
96 
97         cmdlineP->outname = strdup(outnameOpt);
98 
99         /* Remove trailing "_icon" */
100         barPos = strrchr(cmdlineP->outname, '_');
101         if (barPos && streq(barPos, "_icon"))
102             *barPos = '\0';
103     } else {
104         if (streq(cmdlineP->inputFilespec, "-"))
105             cmdlineP->outname = strdup("noname");
106         else {
107             char * dotPos;
108 
109             cmdlineP->outname = strdup(cmdlineP->inputFilespec);
110 
111             /* remove extension */
112             dotPos = strrchr(cmdlineP->outname, '.');
113             if (dotPos)
114                 *dotPos = '\0';
115         }
116     }
117 }
118 
119 
120 
121 
122 static char*
genNumstr(int const number,int const base,char const low_char,int const digits)123 genNumstr(int  const number,
124           int  const base,
125           char const low_char,
126           int  const digits ) {
127 /*----------------------------------------------------------------------------
128   Generate a string 'digits' characters long in newly malloc'ed
129   storage that is the number 'number' displayed in base 'base'.  Fill it on
130   the left with zero digits and truncate on the left if it doesn't fit.
131 
132   Use the characters 'low_char' to 'low_char' + 'base' to represent the
133   digits 0 to 'base'.
134 -----------------------------------------------------------------------------*/
135     char* str;
136     char* p;
137     int d;
138     int i;
139 
140     /* Allocate memory for printed number.  Abort if error. */
141     str = (char*) malloc(digits + 1);
142     if (str == NULL)
143         pm_error("out of memory allocating number string");
144 
145     /* Generate characters starting with least significant digit. */
146     i = number;
147     p = str + digits;
148     *p-- = '\0';    /* nul terminate string */
149     while (p >= str) {
150         d = i % base;
151         i /= base;
152         *p-- = low_char + d;
153     }
154     return str;
155 }
156 
157 
158 
159 static const char *
uilName(const char * const rgbname,bool const transparent)160 uilName(const char * const rgbname,
161         bool         const transparent) {
162 /*----------------------------------------------------------------------------
163    Return a string in newly malloc'ed storage which is an appropriate
164    color name for the UIL palette.  It is the same as the rgb name,
165    except that blanks are replaced by underscores, and if 'transparent'
166    is true, it is "background color".  The latter is a strange name of
167    a color, but it works pretty much the same in the UIL colortable() value.
168 -----------------------------------------------------------------------------*/
169     char * output;
170 
171     if (transparent)
172         output = strdup("background color");
173     else {
174         int i;
175 
176         output = malloc(strlen(rgbname) + 5 + 1);
177         if (output == NULL)
178             pm_error( "out of memory allocating color name" );
179 
180         for (i = 0; i < strlen(rgbname); ++i) {
181             if (rgbname[i] == ' ')
182                 output[i] = '_';
183             else
184                 output[i] = rgbname[i];
185         }
186         output[strlen(rgbname)] = '\0';
187     }
188 
189     return output;
190 }
191 
192 
193 
194 static void
genCmap(struct pam * const pamP,tupletable const chv,unsigned int const ncolors,cixel_map cmap[MAXCOLORS],unsigned int * const charsppP,bool const verbose)195 genCmap(struct pam *   const pamP,
196         tupletable     const chv,
197         unsigned int   const ncolors,
198         cixel_map            cmap[MAXCOLORS],
199         unsigned int * const charsppP,
200         bool           const verbose) {
201 
202     unsigned int const base = (int) HIGH_CHAR - (int) LOW_CHAR + 1;
203     char* colorname;
204     unsigned int colorIndex;
205     {
206         /* Figure out how many characters per pixel we'll be using.
207            Don't want to be forced to link with libm.a, so using a
208            division loop rather than a log function.
209         */
210         unsigned int i;
211         for (*charsppP = 0, i = ncolors; i > 0; ++(*charsppP))
212             i /= base;
213     }
214 
215     /* Generate the character-pixel string and the rgb name for each colormap
216        entry.
217     */
218     for (colorIndex = 0; colorIndex < ncolors; ++colorIndex) {
219         bool nameAlreadyInCmap;
220         unsigned int indexOfName;
221         bool transparent;
222         int j;
223 
224         if (pamP->depth-1 < PAM_TRN_PLANE)
225             transparent = FALSE;
226         else
227             transparent =
228                 chv[colorIndex]->tuple[PAM_TRN_PLANE] < pamP->maxval/2;
229 
230         /* Generate color name string. */
231         colorname = pam_colorname(pamP, chv[colorIndex]->tuple,
232                                   PAM_COLORNAME_ENGLISH);
233 
234         /* We may have already assigned a character code to this color
235            name/transparency because the same color name can apply to
236            two different colors because we said we wanted the closest
237            matching color that has an English name, and we recognize
238            only one transparent color.  If that's the case, we just
239            make a cross-reference.
240         */
241         nameAlreadyInCmap = FALSE;   /* initial assumption */
242         for (j = 0; j < colorIndex; ++j) {
243             if (cmap[j].rgbname != NULL &&
244                 streq(colorname, cmap[j].rgbname) &&
245                 cmap[j].transparent == transparent) {
246                 nameAlreadyInCmap = TRUE;
247                 indexOfName = j;
248             }
249         }
250         if (nameAlreadyInCmap) {
251             /* Make the entry a cross-reference to the earlier entry */
252             cmap[colorIndex].uilname = NULL;
253             cmap[colorIndex].rgbname = NULL;
254             cmap[colorIndex].cixel   = cmap[indexOfName].cixel;
255         } else {
256             cmap[colorIndex].uilname = uilName(colorname, transparent);
257             cmap[colorIndex].rgbname = strdup(colorname);
258             if (cmap[colorIndex].rgbname == NULL)
259                 pm_error("out of memory allocating color name");
260 
261             cmap[colorIndex].transparent = transparent;
262 
263             /* Generate color value characters. */
264             cmap[colorIndex].cixel =
265                 genNumstr(colorIndex, base, LOW_CHAR, *charsppP);
266             if (verbose)
267                 pm_message("Adding color '%s' %s = '%s' to UIL colormap",
268                            cmap[colorIndex].rgbname,
269                            cmap[colorIndex].transparent ? "TRANS" : "OPAQUE",
270                            cmap[colorIndex].cixel);
271         }
272     }
273 }
274 
275 
276 
277 static void
writeUilHeader(const char * const outname)278 writeUilHeader(const char * const outname) {
279     /* Write out the UIL header. */
280     printf( "module %s\n", outname );
281     printf( "version = 'V1.0'\n" );
282     printf( "names = case_sensitive\n" );
283     printf( "include file 'XmAppl.uil';\n" );
284 }
285 
286 
287 
288 static void
writeColormap(const char * const outname,cixel_map cmap[MAXCOLORS],unsigned int const ncolors)289 writeColormap(const char * const outname,
290               cixel_map          cmap[MAXCOLORS],
291               unsigned int const ncolors) {
292 
293     {
294         int i;
295 
296         /* Write out the colors. */
297         printf("\n");
298         printf("value\n");
299         for (i = 0; i < ncolors; ++i)
300             if (cmap[i].uilname != NULL && !cmap[i].transparent)
301                 printf("    %s : color( '%s' );\n",
302                        cmap[i].uilname, cmap[i].rgbname );
303     }
304     {
305         /* Write out the ascii colormap. */
306 
307         int i;
308         bool printedOne;
309 
310         printf("\n");
311         printf("value\n");
312         printf("  %s_rgb : color_table (\n", outname);
313         printedOne = FALSE;
314         for (i = 0; i < ncolors; ++i)
315             if (cmap[i].uilname != NULL) {
316                 if (printedOne)
317                     printf(",\n");
318                 printf("    %s = '%s'", cmap[i].uilname, cmap[i].cixel);
319                 printedOne = TRUE;
320             }
321         printf("\n");
322         printf("    );\n");
323     }
324 }
325 
326 
327 
328 static void
writeRaster(struct pam * const pamP,tuple ** const tuples,const char * const outname,cixel_map cmap[MAXCOLORS],unsigned int const ncolors,tuplehash const cht,unsigned int const charspp)329 writeRaster(struct pam *  const pamP,
330             tuple **      const tuples,
331             const char *  const outname,
332             cixel_map           cmap[MAXCOLORS],
333             unsigned int  const ncolors,
334             tuplehash     const cht,
335             unsigned int  const charspp) {
336 /*----------------------------------------------------------------------------
337    Write out the ascii character-pixel image.
338 -----------------------------------------------------------------------------*/
339     unsigned int row;
340 
341     printf("\n");
342     printf("%s_icon : exported icon( color_table = %s_rgb,\n",
343            outname, outname);
344     for (row = 0; row < pamP->height; ++row) {
345         unsigned int col;
346 
347         printf("    '");
348         for (col = 0; col < pamP->width; ++col) {
349             int colorIndex;
350             int found;
351             if ((col * charspp) % 70 == 0 && col > 0)
352                 printf( "\\\n" );       /* line continuation */
353             pnm_lookuptuple(pamP, cht, tuples[row][col], &found, &colorIndex);
354             if (!found)
355                 pm_error("INTERNAL ERROR: color not found in colormap");
356             printf("%s", cmap[colorIndex].cixel);
357         }
358         if (row != pamP->height - 1)
359             printf("',\n");
360         else
361             printf("'\n");
362     }
363     printf(");\n");
364     printf("\n");
365 }
366 
367 
368 
369 static void
freeCmap(cixel_map const cmap[],unsigned int const ncolors)370 freeCmap(cixel_map    const cmap[],
371          unsigned int const ncolors) {
372 
373     unsigned int i;
374 
375     for (i = 0; i < ncolors; ++i) {
376         cixel_map const cmapEntry = cmap[i];
377         if (cmapEntry.uilname)
378             pm_strfree(cmapEntry.uilname);
379         if (cmapEntry.rgbname)
380             pm_strfree(cmapEntry.rgbname);
381     }
382 }
383 
384 
385 
386 int
main(int argc,char * argv[])387 main(int argc, char *argv[]) {
388 
389     struct cmdlineInfo cmdline;
390     struct pam pam;   /* Input PAM image */
391     FILE * ifP;
392     tuple ** tuples;
393     unsigned int ncolors;
394     tuplehash cht;
395     tupletable chv;
396     cixel_map cmap[MAXCOLORS];
397     unsigned int charspp;
398 
399     pnm_init(&argc, argv);
400 
401     parseCommandLine(argc, argv, &cmdline);
402 
403     ifP = pm_openr(cmdline.inputFilespec);
404     tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
405     pm_close(ifP);
406 
407     pm_message("computing colormap...");
408 
409     chv = pnm_computetuplefreqtable(&pam, tuples, MAXCOLORS, &ncolors);
410     if (chv == NULL)
411         pm_error("too many colors - try doing a 'pnmquant %u'", MAXCOLORS);
412     if (cmdline.verbose)
413         pm_message("%u colors found", ncolors);
414 
415     /* Make a hash table for fast color lookup. */
416     cht = pnm_computetupletablehash(&pam, chv, ncolors);
417 
418     /* Now generate the character-pixel colormap table. */
419     pm_message("looking up color names, assigning character codes...");
420     genCmap(&pam, chv, ncolors, cmap, &charspp, cmdline.verbose);
421 
422     pm_message("generating UIL...");
423     writeUilHeader(cmdline.outname);
424 
425     writeColormap(cmdline.outname, cmap, ncolors);
426 
427     writeRaster(&pam, tuples, cmdline.outname, cmap, ncolors, cht, charspp);
428 
429     printf("end module;\n");
430 
431     free(cmdline.outname);
432     freeCmap(cmap, ncolors);
433 
434     return 0;
435 }
436