1 /* rasttopnm.c - read a Sun rasterfile and produce a portable anymap
2 **
3 ** Copyright (C) 1989, 1991 by Jef Poskanzer.
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 
13 #include "pm_c_util.h"
14 #include "mallocvar.h"
15 #include "shhopt.h"
16 #include "pnm.h"
17 #include "rast.h"
18 
19 
20 
21 struct cmdlineInfo {
22     /* All the information the user supplied in the command line,
23        in a form easy for the program to use.
24     */
25     const char * inputFileName;
26     unsigned int index;
27     unsigned int dumpheader;
28     unsigned int dumpcolormap;
29 };
30 
31 
32 
33 static void
parseCommandLine(int argc,const char ** argv,struct cmdlineInfo * const cmdlineP)34 parseCommandLine(int argc, const char ** argv,
35                  struct cmdlineInfo * const cmdlineP) {
36 /*----------------------------------------------------------------------------
37    Note that the file spec array we return is stored in the storage that
38    was passed to us as the argv array.
39 -----------------------------------------------------------------------------*/
40     optEntry * option_def;
41         /* Instructions to OptParseOptions2 on how to parse our options.
42          */
43     optStruct3 opt;
44 
45     unsigned int option_def_index;
46 
47     MALLOCARRAY_NOFAIL(option_def, 100);
48 
49     option_def_index = 0;   /* incremented by OPTENT3 */
50 
51     opt.opt_table = option_def;
52     opt.short_allowed = false;  /* We have no short (old-fashioned) options */
53     opt.allowNegNum = false;  /* We have no parms that are negative numbers */
54 
55     OPTENT3(0,   "index",        OPT_FLAG,   NULL,
56             &cmdlineP->index,          0);
57     OPTENT3(0,   "dumpheader",   OPT_FLAG,   NULL,
58             &cmdlineP->dumpheader,     0);
59     OPTENT3(0,   "dumpcolormap", OPT_FLAG,   NULL,
60             &cmdlineP->dumpcolormap,   0);
61 
62     pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
63         /* Uses and sets argc, argv, and some of *cmdlineP and others. */
64 
65     if (argc-1 == 0)
66         cmdlineP->inputFileName = "-";
67     else if (argc-1 != 1)
68         pm_error("Program takes zero or one argument (filename).  You "
69                  "specified %d", argc-1);
70     else
71         cmdlineP->inputFileName = argv[1];
72 }
73 
74 
75 
76 static bool
colorMapIsGrayscale(colormap_t const colorMap)77 colorMapIsGrayscale(colormap_t const colorMap) {
78 /*----------------------------------------------------------------------------
79    The color map contains only gray.
80 -----------------------------------------------------------------------------*/
81     unsigned int i;
82     bool grayscale;
83 
84     for (i = 0, grayscale = true; i < colorMap.length; ++i) {
85         if (colorMap.map[0][i] != colorMap.map[1][i] ||
86             colorMap.map[1][i] != colorMap.map[2][i]) {
87             grayscale = false;
88         }
89     }
90     return grayscale;
91 }
92 
93 
94 
95 static void
analyzeImage(struct rasterfile const header,colormap_t const colorMap,int * const formatP,xelval * const maxvalP,bool * const grayscaleP,xel * const zeroP,xel * const oneP)96 analyzeImage(struct rasterfile const header,
97              colormap_t        const colorMap,
98              int *             const formatP,
99              xelval *          const maxvalP,
100              bool *            const grayscaleP,
101              xel *             const zeroP,
102              xel *             const oneP) {
103 
104     bool const grayscale =
105         header.ras_maplength == 0 || colorMapIsGrayscale(colorMap);
106 
107     *grayscaleP = grayscale;
108 
109     switch (header.ras_depth) {
110     case 1:
111         if (header.ras_maptype == RMT_NONE && header.ras_maplength == 0) {
112             *maxvalP = 1;
113             *formatP = PBM_TYPE;
114             PNM_ASSIGN1(*zeroP, 1);
115             PNM_ASSIGN1(*oneP, 0);
116         } else if (header.ras_maptype == RMT_EQUAL_RGB &&
117                    header.ras_maplength == 6) {
118             if (grayscale) {
119                 *maxvalP = 255;
120                 *formatP = PGM_TYPE;
121                 PNM_ASSIGN1(*zeroP, colorMap.map[0][0]);
122                 PNM_ASSIGN1(*oneP, colorMap.map[0][1]);
123             } else {
124                 *maxvalP = 255;
125                 *formatP = PPM_TYPE;
126                 PPM_ASSIGN(
127                     *zeroP, colorMap.map[0][0], colorMap.map[1][0],
128                     colorMap.map[2][0]);
129                 PPM_ASSIGN(
130                     *oneP, colorMap.map[0][1], colorMap.map[1][1],
131                     colorMap.map[2][1]);
132             }
133         } else
134             pm_error(
135                 "this depth-1 rasterfile has a non-standard colormap - "
136                 "type %ld length %ld",
137                 header.ras_maptype, header.ras_maplength);
138         break;
139 
140     case 8:
141         if (grayscale) {
142             *maxvalP = 255;
143             *formatP = PGM_TYPE;
144         } else if (header.ras_maptype == RMT_EQUAL_RGB) {
145             *maxvalP = 255;
146             *formatP = PPM_TYPE;
147         } else
148             pm_error(
149                 "this depth-8 rasterfile has a non-standard colormap - "
150                 "type %ld length %ld",
151                 header.ras_maptype, header.ras_maplength);
152         break;
153 
154     case 24:
155     case 32:
156         if (header.ras_maptype == RMT_NONE && header.ras_maplength == 0);
157         else if (header.ras_maptype == RMT_RAW || header.ras_maplength == 768);
158         else
159             pm_error(
160                 "this depth-%ld rasterfile has a non-standard colormap - "
161                 "type %ld length %ld",
162                 header.ras_depth, header.ras_maptype, header.ras_maplength);
163         *maxvalP = 255;
164         *formatP = PPM_TYPE;
165         break;
166 
167     default:
168         pm_error("invalid depth: %ld.  Can handle only depth 1, 8, 24, or 32.",
169                  header.ras_depth);
170     }
171 }
172 
173 
174 
175 static void
reportOutputType(int const format)176 reportOutputType(int const format) {
177 
178     switch (PNM_FORMAT_TYPE(format)) {
179     case PBM_TYPE:
180         pm_message("writing PBM file");
181         break;
182     case PGM_TYPE:
183         pm_message("writing PGM file");
184         break;
185     case PPM_TYPE:
186         pm_message("writing PPM file");
187         break;
188     default:
189         abort();
190     }
191 }
192 
193 
194 
195 static void
convertRowDepth1(const unsigned char * const rastLine,unsigned int const cols,xel const zeroXel,xel const oneXel,xel * const xelrow)196 convertRowDepth1(const unsigned char * const rastLine,
197                  unsigned int          const cols,
198                  xel                   const zeroXel,
199                  xel                   const oneXel,
200                  xel *                 const xelrow) {
201 
202     const unsigned char * byteP;
203      unsigned int col;
204     unsigned char mask;
205 
206     byteP = rastLine;  /* initial value */
207 
208     for (col = 0, mask = 0x80; col < cols; ++col) {
209         if (mask == 0x00) {
210             ++byteP;
211             mask = 0x80;
212         }
213         xelrow[col] = (*byteP & mask) ? oneXel : zeroXel;
214         mask = mask >> 1;
215     }
216 }
217 
218 
219 
220 static void
convertRowDepth8(const unsigned char * const lineStart,unsigned int const cols,bool const colorMapped,bool const useIndexForColor,bool const grayscale,colormap_t const colorMap,xel * const xelrow)221 convertRowDepth8(const unsigned char * const lineStart,
222                  unsigned int          const cols,
223                  bool                  const colorMapped,
224                  bool                  const useIndexForColor,
225                  bool                  const grayscale,
226                  colormap_t            const colorMap,
227                  xel *                 const xelrow) {
228 /*----------------------------------------------------------------------------
229    Convert a line of raster data from the RAST input to a row of raster
230    data for the PNM output.
231 
232    'lineStart' is where the RAST row starts.   'xelrow' is where to put the
233    PNM row.  'cols' is the number of pixels in the row.
234 
235    'colorMapped' means the RAST image is colormapped.  If so, 'colorMap'
236    is the color map from the RAST file and 'useIndexForColor' means not
237    to use that map but instead to create a PGM row of the colormap
238    _indices_.
239 
240    'grayscale' means it is a grayscale image; the output is PGM.
241 -----------------------------------------------------------------------------*/
242     const unsigned char * byteP;
243     unsigned int col;
244 
245     byteP = lineStart; /* initial value */
246 
247     for (col = 0; col < cols; ++col) {
248         if (colorMapped && !useIndexForColor) {
249             if (grayscale)
250                 PNM_ASSIGN1(xelrow[col], colorMap.map[0][*byteP]);
251             else
252                 PPM_ASSIGN(xelrow[col],
253                            colorMap.map[0][*byteP],
254                            colorMap.map[1][*byteP],
255                            colorMap.map[2][*byteP]);
256         } else
257             PNM_ASSIGN1(xelrow[col], *byteP);
258 
259         ++byteP;
260     }
261 }
262 
263 
264 
265 static void
convertRowRgb(const unsigned char * const lineStart,unsigned int const cols,unsigned int const depth,long const rastType,bool const colorMapped,bool const useIndexForColor,colormap_t const colorMap,xel * const xelrow)266 convertRowRgb(const unsigned char * const lineStart,
267               unsigned int          const cols,
268               unsigned int          const depth,
269               long                  const rastType,
270               bool                  const colorMapped,
271               bool                  const useIndexForColor,
272               colormap_t            const colorMap,
273               xel *                 const xelrow) {
274 
275     const unsigned char * byteP;
276     unsigned int col;
277 
278     byteP = lineStart; /* initial value */
279 
280     for (col = 0; col < cols; ++col) {
281         xelval r, g, b;
282 
283         if (depth == 32)
284             ++byteP;
285         if (rastType == RT_FORMAT_RGB) {
286             r = *byteP++;
287             g = *byteP++;
288             b = *byteP++;
289         } else {
290             b = *byteP++;
291             g = *byteP++;
292             r = *byteP++;
293         }
294         if (colorMapped && !useIndexForColor)
295             PPM_ASSIGN(xelrow[col],
296                        colorMap.map[0][r],
297                        colorMap.map[1][g],
298                        colorMap.map[2][b]);
299         else
300             PPM_ASSIGN(xelrow[col], r, g, b);
301     }
302 }
303 
304 
305 
306 static void
writePnm(FILE * const ofP,const struct pixrect * const pixRectP,unsigned int const cols,unsigned int const rows,xelval const maxval,int const format,unsigned int const depth,long const rastType,bool const grayscale,bool const colorMapped,colormap_t const colorMap,xel const zeroXel,xel const oneXel,bool const useIndexForColor)307 writePnm(FILE *                 const ofP,
308          const struct pixrect * const pixRectP,
309          unsigned int           const cols,
310          unsigned int           const rows,
311          xelval                 const maxval,
312          int                    const format,
313          unsigned int           const depth,
314          long                   const rastType,
315          bool                   const grayscale,
316          bool                   const colorMapped,
317          colormap_t             const colorMap,
318          xel                    const zeroXel,
319          xel                    const oneXel,
320          bool                   const useIndexForColor) {
321 
322     struct mpr_data const mprData = *pixRectP->pr_data;
323 
324     xel * xelrow;
325     unsigned int row;
326     unsigned char * lineStart;
327 
328     pnm_writepnminit(ofP, cols, rows, maxval, format, 0);
329 
330     xelrow = pnm_allocrow(cols);
331 
332     reportOutputType(format);
333 
334     for (row = 0, lineStart = mprData.md_image;
335          row < rows;
336          ++row, lineStart += mprData.md_linebytes) {
337 
338         switch (depth) {
339         case 1:
340             convertRowDepth1(lineStart, cols, zeroXel, oneXel, xelrow);
341             break;
342         case 8:
343             convertRowDepth8(lineStart, cols, colorMapped, useIndexForColor,
344                              grayscale, colorMap, xelrow);
345             break;
346         case 24:
347         case 32:
348             convertRowRgb(lineStart, cols, depth, rastType, colorMapped,
349                           useIndexForColor, colorMap, xelrow);
350             break;
351         default:
352             pm_error("Invalid depth value: %u", depth);
353         }
354         pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
355     }
356 }
357 
358 
359 
360 static void
dumpHeader(struct rasterfile const header)361 dumpHeader(struct rasterfile const header) {
362 
363     const char * typeName;
364 
365     switch (header.ras_type) {
366     case RT_OLD:            typeName = "old";           break;
367     case RT_STANDARD:       typeName = "standard";      break;
368     case RT_BYTE_ENCODED:   typeName = "byte encoded";  break;
369     case RT_FORMAT_RGB:     typeName = "format rgb";    break;
370     case RT_FORMAT_TIFF:    typeName = "format_tiff";   break;
371     case RT_FORMAT_IFF:     typeName = "format_iff";    break;
372     case RT_EXPERIMENTAL:   typeName = "experimental";  break;
373     default:                typeName = "???";
374     }
375 
376     pm_message("type: %s (%lu)", typeName, (unsigned long)header.ras_type);
377     pm_message("%luw x %lul x %lud",
378                (unsigned long)header.ras_width,
379                (unsigned long)header.ras_height,
380                (unsigned long)header.ras_depth);
381     pm_message("raster length: %lu", (unsigned long)header.ras_length);
382 
383     if (header.ras_maplength)
384         pm_message("Has color map");
385 }
386 
387 
388 
389 static void
dumpHeaderAnalysis(bool const grayscale,unsigned int const depth,xel const zero,xel const one)390 dumpHeaderAnalysis(bool         const grayscale,
391                    unsigned int const depth,
392                    xel          const zero,
393                    xel          const one) {
394 
395     pm_message("grayscale: %s", grayscale ? "YES" : "NO");
396 
397     if (depth == 1) {
398         pm_message("Zero color: (%u,%u,%u)",
399                    PNM_GETR(zero),
400                    PNM_GETG(zero),
401                    PNM_GETB(zero));
402 
403         pm_message("One color: (%u,%u,%u)",
404                    PNM_GETR(one),
405                    PNM_GETG(one),
406                    PNM_GETB(one));
407     }
408 }
409 
410 
411 
412 static void
dumpColorMap(colormap_t const colorMap)413 dumpColorMap(colormap_t const colorMap) {
414 
415     unsigned int i;
416     const char * typeName;
417 
418     switch (colorMap.type) {
419     case RMT_NONE:      typeName = "NONE";      break;
420     case RMT_EQUAL_RGB: typeName = "EQUAL_RGB"; break;
421     case RMT_RAW:       typeName = "RAW";       break;
422     default:            typeName = "???";
423     }
424 
425     pm_message("color map type = %s (%u)", typeName, colorMap.type);
426 
427     pm_message("color map size = %u", colorMap.length);
428 
429     for (i = 0; i < colorMap.length; ++i)
430         pm_message("color %u: (%u, %u, %u)", i,
431                    (unsigned char)colorMap.map[0][i],
432                    (unsigned char)colorMap.map[1][i],
433                    (unsigned char)colorMap.map[2][i]);
434 }
435 
436 
437 
438 int
main(int argc,const char ** const argv)439 main(int argc, const char ** const argv) {
440 
441     struct cmdlineInfo cmdline;
442     FILE * ifP;
443     struct rasterfile header;
444     colormap_t colorMap;
445     bool grayscale;
446     struct pixrect * pr;
447     int format;
448     xelval maxval;
449     xel zero, one;
450     int rc;
451 
452     pm_proginit(&argc, argv);
453 
454     parseCommandLine(argc, argv, &cmdline);
455 
456     ifP = pm_openr(cmdline.inputFileName);
457 
458     rc = pr_load_header(ifP, &header);
459     if (rc != 0 )
460         pm_error("unable to read in rasterfile header");
461 
462     if (cmdline.dumpheader)
463         dumpHeader(header);
464 
465     if (header.ras_maplength != 0) {
466         int rc;
467 
468         rc = pr_load_colormap(ifP, &header, &colorMap);
469 
470         if (rc != 0 )
471             pm_error("unable to read colormap from RAST file");
472 
473         if (cmdline.dumpcolormap)
474             dumpColorMap(colorMap);
475     }
476 
477     analyzeImage(header, colorMap, &format, &maxval, &grayscale, &zero, &one);
478 
479     if (cmdline.dumpheader)
480         dumpHeaderAnalysis(grayscale, header.ras_depth, zero, one);
481 
482     pr = pr_load_image(ifP, &header, NULL);
483     if (pr == NULL )
484         pm_error("unable to read in the image from the rasterfile" );
485 
486     if (cmdline.index && header.ras_maplength == 0)
487         pm_error("You requested to use color map indices as colors (-index), "
488                  "but this is not a color mapped image");
489 
490     writePnm(stdout, pr, header.ras_width, header.ras_height, maxval, format,
491              header.ras_depth, header.ras_type, grayscale,
492              header.ras_maplength > 0, colorMap, zero, one, cmdline.index);
493 
494     pm_close(ifP);
495     pm_close(stdout);
496 
497     return 0;
498 }
499