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