about summary refs log tree commit diff
path: root/converter/other/pamtouil.c
blob: 58f57eef4b1d91ac5f180a82f2aa4d0dbc483c7f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
/* pamtouil.c - convert PBM, PGM, PPM, or PPM+alpha to Motif UIL icon file
**
** Bryan Henderson converted ppmtouil to pamtouil on 2002.05.04.
**
** Jef Poskanzer derived pamtouil from ppmtoxpm, which is
** Copyright (C) 1990 by Mark W. Snitily.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#define _BSD_SOURCE  /* Make sure string.h contains strdup() */
#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
#include <ctype.h>
#include <string.h>
#include "pam.h"
#include "pammap.h"
#include "colorname.h"
#include "shhopt.h"
#include "nstring.h"

/* Max number of colors allowed in ppm input. */
#define MAXCOLORS 256

/* Lower bound and upper bound of character-pixels printed in UIL output. */
#define LOW_CHAR '`'
#define HIGH_CHAR '~'

struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char *inputFilespec;  /* Filespecs of input files */
    char *outname;         /* output filename, less "_icon" */
    unsigned int verbose;
};



typedef struct {    /* character-pixel mapping */
    const char* cixel;    /* character string printed for pixel */
    const char* rgbname;  /* ascii rgb color, either mnemonic or #rgb value */
    const char* uilname;  /* same, with spaces replaced by underbars */
    bool        transparent;
} cixel_map;



static void
parseCommandLine(int argc, char ** argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.  The outname array is in newly
   malloc'ed storage.
-----------------------------------------------------------------------------*/
    optEntry *option_def = malloc( 100*sizeof( optEntry ) );
        /* Instructions to optParseOptions3 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;
    unsigned int outnameSpec;
    const char *outnameOpt;

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENT3(0, "name",       OPT_STRING, &outnameOpt, 
            &outnameSpec,       0);
    OPTENT3(0, "verbose",    OPT_FLAG,   NULL, 
            &cmdlineP->verbose, 0);

    opt.opt_table = option_def;
    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = FALSE;  /* We may have parms that are negative numbers */

    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */

    if (argc-1 == 0)
        cmdlineP->inputFilespec = "-";  /* stdin */
    else if (argc-1 == 1)
        cmdlineP->inputFilespec = argv[1];
    else 
        pm_error("Too many arguments (%d).  "
                 "Only need one: the input filespec", argc-1);

    if (outnameSpec) {
        char * barPos;

        cmdlineP->outname = strdup(outnameOpt);

        /* Remove trailing "_icon" */
        barPos = strrchr(cmdlineP->outname, '_');
        if (streq(barPos, "_icon")) 
            *barPos = '\0';
    } else {
        if (streq(cmdlineP->inputFilespec, "-"))
            cmdlineP->outname = strdup("noname");
        else {
            char * dotPos;

            cmdlineP->outname = strdup(cmdlineP->inputFilespec);

            /* remove extension */
            dotPos = strrchr(cmdlineP->outname, '.');
            if (dotPos)
                *dotPos = '\0';
        }
    }
}




static char*
genNumstr(int  const number, 
          int  const base, 
          char const low_char, 
          int  const digits ) {
/*----------------------------------------------------------------------------
  Generate a string 'digits' characters long in newly malloc'ed
  storage that is the number 'number' displayed in base 'base'.  Fill it on
  the left with zero digits and truncate on the left if it doesn't fit.

  Use the characters 'low_char' to 'low_char' + 'base' to represent the
  digits 0 to 'base'. 
-----------------------------------------------------------------------------*/
    char* str;
    char* p;
    int d;
    int i;

    /* Allocate memory for printed number.  Abort if error. */
    str = (char*) malloc(digits + 1);
    if (str == NULL)
        pm_error("out of memory allocating number string");

    /* Generate characters starting with least significant digit. */
    i = number;
    p = str + digits;
    *p-- = '\0';    /* nul terminate string */
    while (p >= str) {
        d = i % base;
        i /= base;
        *p-- = low_char + d;
    }
    return str;
}



static const char * 
uilName(const char * const rgbname, bool const transparent) {
/*----------------------------------------------------------------------------
   Return a string in newly malloc'ed storage which is an appropriate 
   color name for the UIL palette.  It is the same as the rgb name,
   except that blanks are replaced by underscores, and if 'transparent'
   is true, it is "background color".  The latter is a strange name of
   a color, but it works pretty much the same in the UIL colortable() value.
-----------------------------------------------------------------------------*/
    char * output;

    if (transparent)
        output = strdup("background color");
    else {
        int i;

        output = malloc(strlen(rgbname) + 5 + 1);
        if (output == NULL)
            pm_error( "out of memory allocating color name" );
        
        for (i = 0; i < strlen(rgbname); ++i) {
            if (rgbname[i] == ' ')
                output[i] = '_';
            else
                output[i] = rgbname[i];
        }
        output[strlen(rgbname)] = '\0';
    }

    return output;
}



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) {

    unsigned int const base = (int) HIGH_CHAR - (int) LOW_CHAR + 1;
    char* colorname;
    unsigned int colorIndex;
    {
        /* Figure out how many characters per pixel we'll be using.
        ** Don't want to be forced to link with libm.a, so using a
        ** division loop rather than a log function.  
        */
        unsigned int i;
        for (*charsppP = 0, i = ncolors; i > 0; ++(*charsppP))
            i /= base;
    }

    /* Generate the character-pixel string and the rgb name for each colormap
    ** entry.
    */
    for (colorIndex = 0; colorIndex < ncolors; ++colorIndex) {
        bool nameAlreadyInCmap;
        unsigned int indexOfName;
        bool transparent;
        int j;
        
        if (pamP->depth-1 < PAM_TRN_PLANE)
            transparent = FALSE;
        else 
            transparent = 
                chv[colorIndex]->tuple[PAM_TRN_PLANE] < pamP->maxval/2;

        /* Generate color name string. */
        colorname = pam_colorname(pamP, chv[colorIndex]->tuple, 
                                  PAM_COLORNAME_ENGLISH);

        /* We may have already assigned a character code to this color
           name/transparency because the same color name can apply to
           two different colors because we said we wanted the closest
           matching color that has an English name, and we recognize
           only one transparent color.  If that's the case, we just
           make a cross-reference.  
        */
        nameAlreadyInCmap = FALSE;   /* initial assumption */
        for (j = 0; j < colorIndex; ++j) {
            if (cmap[j].rgbname != NULL && 
                streq(colorname, cmap[j].rgbname) &&
                cmap[j].transparent == transparent) {
                nameAlreadyInCmap = TRUE;
                indexOfName = j;
            }
        }
        if (nameAlreadyInCmap) {
            /* Make the entry a cross-reference to the earlier entry */
            cmap[colorIndex].uilname = NULL;
            cmap[colorIndex].rgbname = NULL;
            cmap[colorIndex].cixel = cmap[indexOfName].cixel;
        } else {
            cmap[colorIndex].uilname = uilName(colorname, transparent);
            cmap[colorIndex].rgbname = strdup(colorname);
            if (cmap[colorIndex].rgbname == NULL)
                pm_error( "out of memory allocating color name" );
            
            cmap[colorIndex].transparent = transparent;
            
            /* Generate color value characters. */
            cmap[colorIndex].cixel = 
                genNumstr(colorIndex, base, LOW_CHAR, *charsppP);
            if (verbose)
                pm_message("Adding color '%s' %s = '%s' to UIL colormap",
                           cmap[colorIndex].rgbname, 
                           cmap[colorIndex].transparent ? "TRANS" : "OPAQUE",
                           cmap[colorIndex].cixel);
        }
    }
}



static void
writeUilHeader(const char * const outname) {
    /* Write out the UIL header. */
    printf( "module %s\n", outname );
    printf( "version = 'V1.0'\n" );
    printf( "names = case_sensitive\n" );
    printf( "include file 'XmAppl.uil';\n" );
}



static void
writeColormap(const char * const outname, 
              cixel_map          cmap[MAXCOLORS], 
              unsigned int const ncolors) {

    {
        int i;

        /* Write out the colors. */
        printf("\n");
        printf("value\n");
        for (i = 0; i < ncolors; ++i)
            if (cmap[i].uilname != NULL && !cmap[i].transparent) 
                printf("    %s : color( '%s' );\n", 
                       cmap[i].uilname, cmap[i].rgbname );
    }
    {
        /* Write out the ascii colormap. */

        int i;
        bool printedOne;

        printf("\n");
        printf("value\n");
        printf("  %s_rgb : color_table (\n", outname);
        printedOne = FALSE; 
        for (i = 0; i < ncolors; ++i)
            if (cmap[i].uilname != NULL) {
                if (printedOne)
                    printf(",\n");
                printf("    %s = '%s'", cmap[i].uilname, cmap[i].cixel);
                printedOne = TRUE;
            }     
        printf("\n");
        printf("    );\n");
    }
}



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) {
    
    int row;
    /* Write out the ascii character-pixel image. */

    printf("\n");
    printf("%s_icon : exported icon( color_table = %s_rgb,\n",
           outname, outname);
    for (row = 0; row < pamP->height; ++row) {
        int col;

        printf("    '");
        for (col = 0; col < pamP->width; ++col) {
            int colorIndex;
            int found;
            if ((col * charspp) % 70 == 0 && col > 0)
                printf( "\\\n" );       /* line continuation */
            pnm_lookuptuple(pamP, cht, tuples[row][col], &found, &colorIndex);
            if (!found)
                pm_error("INTERNAL ERROR: color not found in colormap");
            printf("%s", cmap[colorIndex].cixel);
        }
        if (row != pamP->height - 1)
            printf("',\n");
        else
            printf("'\n"); 
    }
    printf(");\n");
    printf("\n");
}


static void
freeString(const char * const s) {
/*----------------------------------------------------------------------------
   This is just free(), but with type checking for const char *.
-----------------------------------------------------------------------------*/
    free((void *)s);
}



static void
freeCmap(cixel_map cmap[], unsigned int const ncolors) {

    int i;

    for (i = 0; i < ncolors; ++i) {
        cixel_map const cmapEntry = cmap[i];
        if (cmapEntry.uilname)
            freeString(cmapEntry.uilname);
        if (cmapEntry.rgbname)
            freeString(cmapEntry.uilname);
    }
}



int
main(int argc, char *argv[]) {

    struct cmdlineInfo cmdline;
    struct pam pam;   /* Input PAM image */
    FILE* ifP;
    tuple** tuples;
    unsigned int ncolors;
    tuplehash cht;
    tupletable chv;
    cixel_map cmap[MAXCOLORS];
    unsigned int charspp;

    pnm_init(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFilespec);
    tuples = pnm_readpam(ifP, &pam, PAM_STRUCT_SIZE(tuple_type));
    pm_close(ifP);

    pm_message("computing colormap...");

    chv = pnm_computetuplefreqtable(&pam, tuples, MAXCOLORS, &ncolors);
    if (chv == NULL)
        pm_error("too many colors - try doing a 'pnmquant %u'", MAXCOLORS);
    if (cmdline.verbose)
        pm_message("%u colors found", ncolors);

    /* Make a hash table for fast color lookup. */
    cht = pnm_computetupletablehash(&pam, chv, ncolors);

    /* Now generate the character-pixel colormap table. */
    pm_message("looking up color names, assigning character codes...");
    genCmap(&pam, chv, ncolors, cmap, &charspp, cmdline.verbose);

    pm_message("generating UIL...");
    writeUilHeader(cmdline.outname);

    writeColormap(cmdline.outname, cmap, ncolors);

    writeRaster(&pam, tuples, cmdline.outname, cmap, ncolors, cht, charspp);

    printf("end module;\n");

    free(cmdline.outname);
    freeCmap(cmap, ncolors);

    return 0;
}