1 /*=============================================================================
2                              winicontopam
3 ===============================================================================
4   Convert from Windows icon format to PAM
5 =============================================================================*/
6 
7 /*
8   Here are some references for the Windows icon format:
9 
10   ICO (file format) - Wikipedia
11   https://en.wikipedia.org/wiki/ICO_(file_format)
12 
13   ICO - Just Solve the File Format Problem
14   http://fileformats.archiveteam.org/wiki/ICO
15   (Has links to example icon file collections)
16 
17   GFF Format Summary: Microsoft Windows Cursor and Icon
18   https://web.archive.org/web/20050421161512/http:/www.oreilly.com/www/centers/gff/formats/miccur/index.htm
19 
20 
21 */
22 
23 #include <assert.h>
24 #include <stdio.h>
25 #include <string.h>
26 
27 #include "netpbm/pm_config.h"
28 #include "netpbm/pm_c_util.h"
29 #include "netpbm/mallocvar.h"
30 #include "netpbm/nstring.h"
31 #include "netpbm/shhopt.h"
32 #include "netpbm/pam.h"
33 #include "netpbm/pm_system.h"
34 
35 #include "winicon.h"
36 
37 #define RED   0
38 #define GRN   1
39 #define BLU   2
40 #define ALPHA 3
41 #define CHANNEL_CHARS "RGBA"
42 
43 
44 
45 static bool verbose;
46 
47 
48 
49 struct CmdlineInfo {
50 
51     const char * inputFileName;
52     unsigned int allimages;
53     unsigned int imageSpec;
54     unsigned int image;
55     unsigned int andmasks;
56     unsigned int headerdump;
57     unsigned int verbose;
58 };
59 
60 
61 
62 static void
parseCommandLine(int argc,const char ** argv,struct CmdlineInfo * const cmdlineP)63 parseCommandLine(int argc, const char **argv,
64                  struct CmdlineInfo * const cmdlineP) {
65 
66     optEntry *   option_def;
67     unsigned int option_def_index;
68     optStruct3   opt3;
69 
70     MALLOCARRAY_NOFAIL(option_def, 100);
71 
72     option_def_index = 0;
73 
74     OPTENT3(0, "allimages",   OPT_FLAG,   NULL,
75             &cmdlineP->allimages,         0);
76     OPTENT3(0, "image",     OPT_UINT,   &cmdlineP->image,
77             &cmdlineP->imageSpec,         0);
78     OPTENT3(0, "andmasks",  OPT_FLAG,   NULL,
79             &cmdlineP->andmasks,          0);
80     OPTENT3(0, "headerdump",   OPT_FLAG,   NULL,
81             &cmdlineP->headerdump,        0);
82     OPTENT3(0, "verbose",   OPT_FLAG,   NULL,
83             &cmdlineP->verbose,           0);
84 
85     opt3.opt_table     = option_def;
86     opt3.short_allowed = false;
87     opt3.allowNegNum   = false;
88 
89     pm_optParseOptions3(&argc, (char **)argv, opt3, sizeof(opt3), 0);
90 
91     if (cmdlineP->allimages && cmdlineP->imageSpec)
92         pm_error("You cannot specify both -allimages and -image");
93 
94     if (argc-1 < 1)
95         cmdlineP->inputFileName = "-";
96     else {
97         cmdlineP->inputFileName = argv[1];
98 
99         if (argc-1 > 1)
100             pm_error("Too many arguments.  The only possible "
101                      "non-option argument is the input file name");
102     }
103 
104     free(option_def);
105 }
106 
107 
108 
109 static unsigned char const pngHeader[] = PNG_HEADER;
110 
111 
112 
113 struct File {
114 
115     FILE *       fileP;
116     const char * name;
117     pm_filepos   pos;
118 
119 };
120 
121 
122 
123 static uint32_t
u8_le(const unsigned char * const buf,size_t const offset)124 u8_le(const unsigned char * const buf,
125       size_t                const offset) {
126 
127     return buf[offset + 0];
128 }
129 
130 
131 
132 static uint32_t
u16_le(const unsigned char * const buf,size_t const offset)133 u16_le(const unsigned char * const buf,
134        size_t                const offset) {
135 
136     return
137         ((uint32_t)buf[offset + 0] << 0) +
138         ((uint32_t)buf[offset + 1] << 8);
139 }
140 
141 
142 
143 static uint32_t
u32_le(const unsigned char * const buf,size_t const offset)144 u32_le(const unsigned char * const buf,
145        size_t                const offset) {
146 
147     return
148         ((uint32_t)buf[offset + 0] <<  0) +
149         ((uint32_t)buf[offset + 1] <<  8) +
150         ((uint32_t)buf[offset + 2] << 16) +
151         ((uint32_t)buf[offset + 3] << 24);
152 }
153 
154 
155 
156 static uint32_t
s32_le(const unsigned char * const buf,size_t const offset)157 s32_le(const unsigned char * const buf,
158        size_t                const offset) {
159 
160     return
161         ((uint32_t)buf[offset + 0] <<  0) +
162         ((uint32_t)buf[offset + 1] <<  8) +
163         ((uint32_t)buf[offset + 2] << 16) +
164         ((uint32_t)buf[offset + 3] << 24);
165 }
166 
167 
168 
169 static uint32_t
u8_be(const unsigned char * const buf,size_t const offset)170 u8_be(const unsigned char * const buf,
171       size_t                const offset) {
172 
173     return buf[offset + 0];
174 }
175 
176 
177 
178 static uint32_t
u32_be(const unsigned char * const buf,size_t const offset)179 u32_be(const unsigned char * const buf,
180        size_t                const offset) {
181 
182     return
183         ((uint32_t)buf[offset + 0] << 24) +
184         ((uint32_t)buf[offset + 1] << 16) +
185         ((uint32_t)buf[offset + 2] <<  8) +
186         ((uint32_t)buf[offset + 3] <<  0);
187 }
188 
189 
190 
191 static uint32_t
u32_xx(const unsigned char * const buf,size_t const offset)192 u32_xx(const unsigned char * const buf,
193        size_t                const offset) {
194 
195     uint32_t u32;
196 
197     ((uint8_t*) &u32)[0] = buf[offset + 0];
198     ((uint8_t*) &u32)[1] = buf[offset + 1];
199     ((uint8_t*) &u32)[2] = buf[offset + 2];
200     ((uint8_t*) &u32)[3] = buf[offset + 3];
201 
202     return (u32);
203 }
204 
205 
206 
207 #ifndef LITERAL_FN_DEF_MATCH
208 static qsort_comparison_fn cmpfn;
209 #endif
210 
211 static int
cmpfn(const void * const aP,const void * const bP)212 cmpfn(const void * const aP,
213       const void * const bP) {
214 
215     const struct IconDirEntry * const dirEntryAP = aP;
216     const struct IconDirEntry * const dirEntryBP = bP;
217 
218     if (dirEntryAP->offset < dirEntryBP->offset)
219         return -1;
220     else if (dirEntryAP->offset > dirEntryBP->offset)
221         return +1;
222     else
223         return 0;
224 }
225 
226 
227 
228 static void
dumpIconDir(const struct IconDir * const dirP)229 dumpIconDir(const struct IconDir * const dirP) {
230 
231     unsigned int i;
232 
233     pm_message("Type: %u", dirP->type);
234     pm_message("Icon directory has %u images:", dirP->count);
235 
236     for (i = 0; i < dirP->count; ++i) {
237         const struct IconDirEntry * const dirEntryP = &dirP->entries[i];
238 
239         pm_message("width: %u", dirEntryP->width);
240         pm_message("height: %u", dirEntryP->height);
241         pm_message("color count: %u", dirEntryP->color_count);
242         pm_message("# color planes: %u", dirEntryP->color_planes);
243         pm_message("bits per pixel: %u", dirEntryP->bits_per_pixel);
244         pm_message("offset in file of image: %u", dirEntryP->offset);
245         pm_message("size of image: %u", dirEntryP->size);
246         pm_message("zero field: %u", dirEntryP->zero);
247     }
248 }
249 
250 
251 
252 static struct IconDir *
readIconDir(struct File * const fP,bool const needHeaderDump)253 readIconDir(struct File * const fP,
254             bool          const needHeaderDump) {
255 
256     struct IconDir head;
257     struct IconDir * dirP;
258     uint32_t  imageIndex; /* more bits than dir.count */
259 
260     pm_readlittleshortu(fP->fileP, &head.zero);
261     pm_readlittleshortu(fP->fileP, &head.type);
262     pm_readlittleshortu(fP->fileP, &head.count);
263     fP->pos += 6;
264 
265     if (head.zero != 0 || head.type != ICONDIR_TYPE_ICO)
266         pm_error("Not a valid windows icon file");
267 
268     MALLOCVAR(dirP);
269 
270     if (dirP == NULL)
271         pm_error("Couldn't allocate memory for Icon directory");
272 
273     MALLOCARRAY(dirP->entries, head.count);
274 
275     if (dirP->entries == NULL)
276         pm_error("Could not allocate memory for %u entries in icon directory",
277                  head.count);
278 
279     dirP->zero           = head.zero;
280     dirP->type           = head.type;
281     dirP->count          = head.count;
282     dirP->entriesAllocCt = head.count;
283 
284     for (imageIndex = 0; imageIndex < head.count; ++imageIndex) {
285         struct IconDirEntry * const dirEntryP = &dirP->entries[imageIndex];
286 
287         unsigned char widthField, heightField;
288 
289         unsigned long ul;
290 
291         pm_readcharu(fP->fileP, &widthField);
292         dirEntryP->width  = (widthField == 0 ? 256 : widthField);
293 
294         pm_readcharu(fP->fileP, &heightField);
295         dirEntryP->height = (heightField == 0 ? 256 : heightField);
296 
297         pm_readcharu(fP->fileP, &dirEntryP->color_count);
298 
299         pm_readcharu(fP->fileP, &dirEntryP->zero);
300 
301         pm_readlittleshortu(fP->fileP, &dirEntryP->color_planes);
302 
303         pm_readlittleshortu(fP->fileP, &dirEntryP->bits_per_pixel);
304 
305         pm_readlittlelongu(fP->fileP, &ul); dirEntryP->size = ul;
306 
307         pm_readlittlelongu(fP->fileP, &ul); dirEntryP->offset = ul;
308 
309         fP->pos += 16;
310 
311         dirEntryP->index = imageIndex;
312     }
313 
314     /* The following is paranoia code only:
315 
316        I've never seen a windows icon file in the wild with having the entries
317        in the directory stored in a different order than the images
318        themselves.  However, the file format allows for it ...
319      */
320     qsort(dirP->entries, dirP->count, sizeof(struct IconDirEntry), cmpfn);
321 
322     if (verbose) {
323         pm_message("%s icon directory (%u image%s):",
324                    fP->name,
325                    dirP->count, dirP->count == 1 ? "" : "s");
326 
327         for (imageIndex = 0; imageIndex < dirP->count; ++imageIndex) {
328             const struct IconDirEntry * const dirEntryP =
329                 &dirP->entries[imageIndex];
330 
331             uint32_t colorCt;
332 
333             if (dirEntryP->bits_per_pixel == 0)
334                 colorCt = 0;
335             else if (dirEntryP->bits_per_pixel >= 32)
336                 colorCt = 1u << 24;
337             else
338                 colorCt = 1u << dirEntryP->bits_per_pixel;
339 
340             if (dirEntryP->color_count != 0 &&
341                 colorCt > dirEntryP->color_count) {
342                 colorCt = dirEntryP->color_count;
343             }
344             pm_message ("%5u: %3u x %3u, %8u colors, %5u bytes",
345                         dirEntryP->index,
346                         dirEntryP->width,
347                         dirEntryP->height,
348                         colorCt,
349                         dirEntryP->size);
350         }
351     }
352 
353     if (needHeaderDump)
354         dumpIconDir(dirP);
355 
356     return dirP;
357 }
358 
359 
360 
361 static void
freeIconDir(struct IconDir * const dirP)362 freeIconDir(struct IconDir * const dirP) {
363 
364     free(dirP->entries);
365     free(dirP);
366 }
367 
368 
369 
370 static const unsigned char *
readImage(struct File * const fP,struct IconDirEntry * const dirEntryP)371 readImage(struct File *         const fP,
372           struct IconDirEntry * const dirEntryP) {
373 
374     size_t rc;
375     unsigned char * image;
376     uint32_t skippedCt;
377 
378     /*  Don't try to read an image that is smaller than the
379         BITMAPINFOHEADER of BMP images (40 bytes).
380 
381         PNG compressed images can't be smaller than that either, as the
382         PNG header plus the mandantory IHDR and IEND chunks already take
383         8 + 25 + 12 = 35 bytes, and there is to be a IDAT chunk too.
384      */
385     if (dirEntryP->size < 40) {
386         pm_error("image %2u: format violation: too small as an image.",
387                   dirEntryP->index);
388     }
389     if ((pm_filepos) dirEntryP->offset < fP->pos)
390         pm_error("image %2u: format violation: invalid offset.",
391                  dirEntryP->index);
392 
393     /* The following is paranoia code only:
394 
395        I've never seen a windows icon file in the wild with gaps between
396        the images, but the file format allows for it, and Microsoft
397        expects the user to fseek() to the start of each image.
398      */
399     skippedCt = 0;
400 
401     while ((pm_filepos) dirEntryP->offset > fP->pos) {
402         if (getc(fP->fileP) == EOF) {
403             pm_error("seeking to image %u: unexpected EOF", dirEntryP->index);
404         }
405         ++fP->pos;
406         ++skippedCt;
407     }
408 
409     /*  The additional four bytes are for purify and friends, as the
410         routines reading BMP XOR and AND masks might read (but not
411         evaluate) some bytes beyond the image data.
412      */
413     image = malloc(dirEntryP->size + sizeof(uint32_t));
414     if (image == NULL)
415         pm_error("out of memory.");
416 
417     rc = fread (image, 1, dirEntryP->size, fP->fileP);
418     if (rc != dirEntryP->size) {
419         pm_error("reading image %2u: unexpected EOF", dirEntryP->index);
420     }
421     fP->pos += dirEntryP->size;
422 
423     return image;
424 }
425 
426 
427 
428 static uint8_t
getIdx1(const unsigned char * const bitmap,uint32_t const offset,int16_t const col)429 getIdx1(const unsigned char * const bitmap,
430         uint32_t              const offset,
431         int16_t               const col) {
432 
433     return u8_le(bitmap, offset + (col >> 3)) >> (7 - (col & 0x07)) & 0x1;
434 }
435 
436 
437 
438 static uint8_t
getIdx4(const unsigned char * const bitmap,uint32_t const offset,int16_t const col)439 getIdx4(const unsigned char * const bitmap,
440         uint32_t              const offset,
441         int16_t               const col) {
442 
443     if ((col & 1) == 0x0000)
444         return u8_le(bitmap, offset + (col >> 1)) >> 4 & 0x0F;
445     else
446         return u8_le(bitmap, offset + (col >> 1)) >> 0 & 0x0F;
447 }
448 
449 
450 
451 static uint8_t
getIdx8(const unsigned char * const bitmap,uint32_t const offset,int16_t const col)452 getIdx8(const unsigned char * const bitmap,
453         uint32_t              const offset,
454         int16_t               const col) {
455 
456     return u8_le(bitmap, offset + col);
457 }
458 
459 
460 
461 typedef unsigned char PaletteEntry[4];
462 
463 
464 
465 static void
dumpPalette(const PaletteEntry * const palette,unsigned int const colorCt)466 dumpPalette(const PaletteEntry * const palette,
467             unsigned int         const colorCt) {
468 
469     unsigned int i;
470 
471     for (i = 0; i < colorCt; ++i) {
472         pm_message("Color %u: (%u, %u, %u)",
473                    i, palette[i][2], palette[i][1], palette[i][0]);
474     }
475 }
476 
477 
478 
479 static void
readXorPalette(struct BitmapInfoHeader * const hdrP,const unsigned char * const bitmap,uint32_t const maxSize,tuple ** const tuples,uint16_t const index,bool const needHeaderDump,uint32_t * const bytesConsumedP)480 readXorPalette(struct BitmapInfoHeader * const hdrP,
481                const unsigned char *     const bitmap,
482                uint32_t                  const maxSize,
483                tuple **                  const tuples,
484                uint16_t                  const index,
485                bool                      const needHeaderDump,
486                uint32_t *                const bytesConsumedP) {
487 
488     uint32_t paletteSize;
489 
490     int16_t     row;
491     const PaletteEntry * palette;
492     uint32_t    truncatedXorSize;
493     uint32_t    bytesConsumed;
494     uint32_t    bytesPerRow;
495     const unsigned char * bitmapCursor;
496     uint32_t sizeRemaining;
497 
498     uint8_t (*getIdx) (const unsigned char * bitmap,
499                        uint32_t rowOffset,
500                        int16_t col);
501 
502     if (hdrP->compression_method != BI_RGB)
503         pm_error("image %2u: invalid compression method %u.",
504                  index, hdrP->compression_method);
505 
506     assert(hdrP->bits_per_pixel < 16);
507 
508     switch (hdrP->bits_per_pixel) {
509     case 1:
510         if (hdrP->colors_in_palette == 0)
511             hdrP->colors_in_palette = 2;
512         getIdx = getIdx1;
513         break;
514 
515     case 4:
516         if (hdrP->colors_in_palette == 0)
517             hdrP->colors_in_palette = 16;
518         getIdx = getIdx4;
519         break;
520 
521     case 8:
522         if (hdrP->colors_in_palette == 0)
523             hdrP->colors_in_palette = 256;
524         getIdx = getIdx8;
525         break;
526 
527     default:
528         pm_error("image %2u: "
529                  "bits per pixel is a value we don't understand: %u",
530                   index, hdrP->bits_per_pixel);
531         getIdx = NULL;
532     }
533 
534     bitmapCursor = &bitmap[0];  /* initial value */
535     sizeRemaining = maxSize;    /* initial value */
536     bytesConsumed = 0;          /* initial value */
537 
538     paletteSize = hdrP->colors_in_palette * 4;
539 
540     if (sizeRemaining < paletteSize)
541         pm_error("image %2u: "
542                  "reading palette: image truncated.", index);
543 
544     palette = (const PaletteEntry *) bitmapCursor;
545 
546     if (needHeaderDump)
547         dumpPalette(palette, hdrP->colors_in_palette);
548 
549     bitmapCursor  += paletteSize;
550     sizeRemaining -= paletteSize;
551     bytesConsumed += paletteSize;
552 
553     {
554         uint32_t const xorSize = (uint32_t)
555             (((hdrP->bits_per_pixel * hdrP->bm_width + 31) / 32)
556              * 4 * hdrP->bm_height / 2);
557 
558         if (sizeRemaining < xorSize) {
559             pm_message("image %2u: "
560                        "reading XOR mask: image truncated.", index);
561             truncatedXorSize = sizeRemaining;
562         } else
563             truncatedXorSize = xorSize;
564     }
565 
566     bytesPerRow = ((hdrP->bits_per_pixel * hdrP->bm_width + 31) / 32) * 4;
567 
568     for (row = 0; hdrP->bm_height / 2 > row; ++row) {
569         uint32_t rowOffset;
570 
571         if (hdrP->top_down)
572             rowOffset = row * bytesPerRow;
573         else
574             rowOffset = (hdrP->bm_height / 2 - row - 1) * bytesPerRow;
575 
576         if (rowOffset + bytesPerRow <= truncatedXorSize) {
577             int16_t col;
578             for (col = 0; hdrP->bm_width > col; ++col) {
579                 uint8_t const idx = getIdx(bitmapCursor, rowOffset, col);
580 
581                 if (idx > hdrP->colors_in_palette)
582                     pm_error("invalid palette index in row %u, column %u.",
583                              row, col);
584 
585                 /*  The palette is an array of little-endian 32-bit values,
586                     where the RGB value is encoded as follows:
587 
588                     red:   bits 2^16..2^23
589                     green: bits 2^8 ..2^15
590                     blue:  bits 2^0 ..2^7
591                  */
592                 tuples[row][col][PAM_RED_PLANE] = palette[idx][2];
593                 tuples[row][col][PAM_GRN_PLANE] = palette[idx][1];
594                 tuples[row][col][PAM_BLU_PLANE] = palette[idx][0];
595             }
596         }
597     }
598 
599     bitmapCursor  += truncatedXorSize;
600     sizeRemaining -= truncatedXorSize;
601     bytesConsumed += truncatedXorSize;
602 
603     *bytesConsumedP = bytesConsumed;
604 }
605 
606 
607 
608 static void
readXorBitfields(struct BitmapInfoHeader * const hdrP,const unsigned char * const bitmap,uint32_t const maxSize,tuple ** const tuples,uint16_t const index,bool * const haveAlphaP,uint32_t * const bytesConsumedP)609 readXorBitfields(struct BitmapInfoHeader * const hdrP,
610                  const unsigned char *     const bitmap,
611                  uint32_t                  const maxSize,
612                  tuple **                  const tuples,
613                  uint16_t                  const index,
614                  bool *                    const haveAlphaP,
615                  uint32_t *                const bytesConsumedP) {
616 
617     uint32_t   bitfields[4];
618     uint8_t    shift    [4];
619     sample     maxval   [4];
620 
621     int16_t      row;
622     uint32_t     bytesConsumed;
623     uint32_t     bytesPerSample;
624     uint32_t     bytesPerRow;
625     const unsigned char * bitmapCursor;
626     uint32_t     sizeRemaining;
627     uint32_t     truncatedXorSize;
628 
629     static uint8_t alphas [256];
630     bool         allOpaque;
631     bool         allTransparent;
632 
633     bytesConsumed = 0;
634 
635     if (hdrP->compression_method != BI_RGB
636         && hdrP->compression_method != BI_BITFIELDS)
637         pm_error("image %2u: invalid compression method %u.",
638                  index, hdrP->compression_method);
639 
640     assert(hdrP->bits_per_pixel >= 16);
641 
642     switch (hdrP->bits_per_pixel) {
643     case 16:
644         bytesPerSample = 2;
645         bitfields[RED]   = 0x7C00;
646         bitfields[GRN]   = 0x03E0;
647         bitfields[BLU]   = 0x001F;
648         bitfields[ALPHA] = 0x0000;
649         break;
650 
651     case 24:
652         bytesPerSample = 3;
653         bitfields[RED]   = 0xFF0000;
654         bitfields[GRN]   = 0x00FF00;
655         bitfields[BLU]   = 0x0000FF;
656         bitfields[ALPHA] = 0x000000;
657         break;
658 
659     case 32:
660         bytesPerSample = 4;
661         bitfields[RED]   = 0x00FF0000;
662         bitfields[GRN]   = 0x0000FF00;
663         bitfields[BLU]   = 0x000000FF;
664         bitfields[ALPHA] = 0xFF000000;
665         break;
666 
667     default:
668         pm_error("image %2u: bits per pixel is one we don't understand: %u.",
669                  index, hdrP->bits_per_pixel);
670         bytesPerSample = 0;
671     }
672 
673     bitmapCursor = &bitmap[0]; /* initial value */
674     sizeRemaining = maxSize;  /* initial value */
675 
676     /*  read bit fields from image data  */
677     if (hdrP->compression_method == BI_BITFIELDS) {
678         if (sizeRemaining < 12)
679             pm_error("image %2u: "
680                      "reading bit fields: image truncated.", index);
681 
682         bitfields[RED]   = u32_le(bitmapCursor, 0);
683         bitfields[GRN]   = u32_le(bitmapCursor, 4);
684         bitfields[BLU]   = u32_le(bitmapCursor, 8);
685         bitfields[ALPHA] = 0;
686 
687         bitmapCursor  += 12;
688         sizeRemaining -= 12;
689         bytesConsumed += 12;
690     }
691 
692     /*  determine shift and maxval from bit field for each channel */
693     {
694         unsigned int i;
695 
696         for (i = 0; 4 > i; ++i) {
697             if (bitfields[i] == 0) {
698                 maxval[i] = 1;
699                 shift [i] = 0;
700             } else {
701                 unsigned int j;
702 
703                 maxval[i] = bitfields[i];
704 
705                 for (j = 0; 32 > j; ++j) {
706                     if ((maxval[i] & 0x1) != 0)
707                         break;
708                     maxval[i] >>= 1;
709                 }
710                 shift[i] = j;
711             }
712 
713         }
714     }
715 
716     /*  read the XOR mask */
717     {
718         uint32_t const xorSize = (uint32_t)
719             (((hdrP->bits_per_pixel * hdrP->bm_width + 31) / 32)
720              * 4 * hdrP->bm_height / 2);
721 
722         if (sizeRemaining < xorSize) {
723             pm_message("image %2u: "
724                        "reading XOR mask: image truncated.", index);
725             truncatedXorSize = sizeRemaining;
726         } else
727             truncatedXorSize = xorSize;
728     }
729 
730     bytesPerRow = ((hdrP->bits_per_pixel * hdrP->bm_width + 31) / 32) * 4;
731     MEMSZERO(alphas);
732 
733     for (row = 0, allOpaque = true, allTransparent = true;
734          hdrP->bm_height / 2 > row;
735          ++row) {
736 
737         uint32_t offset;
738 
739         if (hdrP->top_down)
740             offset = row * bytesPerRow;
741         else
742             offset = (hdrP->bm_height / 2 - row - 1) * bytesPerRow;
743 
744         if (offset + bytesPerRow <= truncatedXorSize) {
745             unsigned int col;
746             for (col = 0; col < hdrP->bm_width; ++col) {
747                 uint32_t const pixel = u32_le(bitmapCursor, offset);
748                 offset += bytesPerSample;
749 
750                 tuples[row][col][PAM_RED_PLANE] =
751                     pnm_scalesample((pixel & bitfields[RED]) >> shift[RED],
752                                     maxval[RED], 255);
753                 tuples[row][col][PAM_GRN_PLANE] =
754                     pnm_scalesample((pixel & bitfields[GRN]) >> shift[GRN],
755                                     maxval[GRN], 255);
756                 tuples [row][col][PAM_BLU_PLANE]
757                     = pnm_scalesample((pixel & bitfields[BLU]) >> shift[BLU],
758                                       maxval[BLU], 255);
759 
760                 if (bitfields [ALPHA] != 0) {
761                     tuples[row][col][PAM_TRN_PLANE]
762                         = pnm_scalesample(
763                             (pixel & bitfields[ALPHA]) >> shift[ALPHA],
764                             maxval[ALPHA], 255);
765 
766                     if (tuples[row][col][PAM_TRN_PLANE] != 0)
767                         allTransparent = false;
768 
769                     if (tuples [row][col][PAM_TRN_PLANE] != 255)
770                         allOpaque = false;
771 
772                     alphas[tuples[row][col][PAM_TRN_PLANE]] = !0;
773                 }
774             }
775         }
776     }
777 
778     bitmapCursor  += truncatedXorSize;
779     sizeRemaining -= truncatedXorSize;
780     bytesConsumed += truncatedXorSize;
781 
782     /*  A fully transparent alpha channel (all zero) in XOR mask is
783         defined to be void by Microsoft, and a fully opaque alpha
784         channel (all maxval) is trivial and will be dropped.
785     */
786     *haveAlphaP = !allTransparent && !allOpaque;
787 
788     if (!allTransparent && verbose) {
789         unsigned int i;
790         unsigned int c;
791 
792         for (i = 0, c = 0; 256 > i; ++i) {
793             if (alphas[i] != 0)
794                 ++c;
795         }
796         pm_message("image %2u: %u distinct transparency value%s",
797                    index, c, (c == 1) ? "": "s");
798     }
799     *bytesConsumedP = bytesConsumed;
800 }
801 
802 
803 
804 static void
readAnd(struct BitmapInfoHeader * const hdrP,const unsigned char * const bitmap,uint32_t const maxSize,tuple ** const tuples,uint16_t const index,unsigned int const plane,sample const maxval)805 readAnd(struct BitmapInfoHeader * const hdrP,
806         const unsigned char *     const bitmap,
807         uint32_t                  const maxSize,
808         tuple **                  const tuples,
809         uint16_t                  const index,
810         unsigned int              const plane,
811         sample                    const maxval) {
812 
813     int16_t  row;
814     uint32_t bytesConsumed;
815     uint32_t bytesPerRow;
816     uint32_t sizeRemaining;
817     uint32_t truncatedAndSize;
818 
819     sizeRemaining = maxSize;  /* initial value */
820     bytesConsumed = 0;  /* initial value */
821 
822     {
823         uint32_t const andSize = (uint32_t)
824             (((1 * hdrP->bm_width + 31) / 32) * 4 * hdrP->bm_height / 2);
825 
826         if (sizeRemaining < andSize) {
827             pm_message ("image %2u: "
828                         "Input image ends %u bytes into the %u-byte "
829                         "AND mask.  Implying remainder of mask",
830                         index, sizeRemaining, andSize);
831             truncatedAndSize = sizeRemaining;
832         } else
833             truncatedAndSize = andSize;
834     }
835 
836     bytesPerRow = ((1 * hdrP->bm_width + 31) / 32) * 4;
837 
838     for (row = 0; row < hdrP->bm_height / 2; ++row) {
839         uint32_t offset;
840 
841         if (hdrP->top_down)
842             offset = row * bytesPerRow;
843         else
844             offset = (hdrP->bm_height / 2 - row - 1) * bytesPerRow;
845 
846         if (offset + bytesPerRow <= sizeRemaining) {
847             unsigned int col;
848 
849             for (col = 0; col < hdrP->bm_width; ++col) {
850                 tuples[row][col][plane] =
851                     ((u8_le(bitmap, offset + col/8)
852                       & (1 << (7 - (col & 0x7)))) == 0x00) ?
853                     maxval : 0
854                 ;
855             }
856         }
857     }
858     sizeRemaining -= truncatedAndSize;
859     bytesConsumed += truncatedAndSize;
860 }
861 
862 
863 
864 static void
dumpBmpHeader(struct BitmapInfoHeader const hdr,unsigned int const imageIndex)865 dumpBmpHeader(struct BitmapInfoHeader const hdr,
866               unsigned int            const imageIndex) {
867 
868     pm_message("BMP header for Image %u:", imageIndex);
869 
870     pm_message("header size: %u", hdr.header_size);
871     pm_message("bitmap width: %u", hdr.bm_width);
872     pm_message("bitmap height * 2: %u", hdr.bm_height);
873     pm_message("row order: %s", hdr.top_down ? "top down" : "bottom up");
874     pm_message("# color planes: %u", hdr.color_planes);
875     pm_message("bits per pixel: %u", hdr.bits_per_pixel);
876     pm_message("image size: %u", hdr.image_size);
877     pm_message("horizontal resolution: %u", hdr.horizontal_resolution);
878     pm_message("vertical resolution: %u", hdr.vertical_resolution);
879     pm_message("# colors in palette: %u", hdr.colors_in_palette);
880     pm_message("# important colors: %u", hdr.important_colors);
881 }
882 
883 
884 
885 static void
readBmpHeader(const unsigned char * const image,uint32_t const size,unsigned int const imageIndex,bool const needHeaderDump,struct BitmapInfoHeader * const hdrP)886 readBmpHeader(const unsigned char *     const image,
887               uint32_t                  const size,
888               unsigned int              const imageIndex,
889               bool                      const needHeaderDump,
890               struct BitmapInfoHeader * const hdrP) {
891 
892     /*  BITMAPINFOHEADER structure */
893 
894     if (size < 40)
895         pm_error("image %2u: reading BITMAPINFOHEADER: not enough data.",
896                  imageIndex);
897 
898     hdrP->header_size           = u32_le(image,  0);
899     hdrP->bm_width              = s32_le(image,  4);
900     hdrP->bm_height             = s32_le(image,  8);
901     hdrP->color_planes          = u16_le(image, 12);
902     hdrP->bits_per_pixel        = u16_le(image, 14);
903     hdrP->compression_method    = u32_le(image, 16);
904     hdrP->image_size            = u32_le(image, 20);
905     hdrP->horizontal_resolution = s32_le(image, 24);
906     hdrP->vertical_resolution   = s32_le(image, 28);
907     hdrP->colors_in_palette     = u32_le(image, 32);
908     hdrP->important_colors      = u32_le(image, 36);
909 
910     if (hdrP->bm_height > 0) {
911         hdrP->top_down = false;
912     } else {
913         hdrP->top_down   = true;
914         hdrP->bm_height *= -1;
915     }
916 
917     if (hdrP->header_size < 36
918         || hdrP->bm_width == 0 || hdrP->bm_height == 0
919         || (hdrP->bm_height & 1) != 0x0000) {
920         pm_error("image %2u: format violation: invalid BMP header.",
921                  imageIndex);
922     }
923 
924     if (needHeaderDump)
925         dumpBmpHeader(*hdrP, imageIndex);
926 }
927 
928 
929 
930 static void
readXorMask(struct BitmapInfoHeader * const hdrP,const unsigned char * const imageCursor,uint32_t const imageSize,tuple ** const tuples,uint16_t const index,bool const needHeaderDump,bool * const haveAlphaP,uint32_t * const bytesConsumedP)931 readXorMask(struct BitmapInfoHeader * const hdrP,
932             const unsigned char *     const imageCursor,
933             uint32_t                  const imageSize,
934             tuple **                  const tuples,
935             uint16_t                  const index,
936             bool                      const needHeaderDump,
937             bool *                    const haveAlphaP,
938             uint32_t *                const bytesConsumedP) {
939 /*----------------------------------------------------------------------------
940    Read the so-called XOR mask (for non-monochrome images, this is the
941    color pixmap)
942 -----------------------------------------------------------------------------*/
943     /*  preset the PAM with fully opaque black (just in case the image
944         is truncated and not all pixels are filled in below).
945     */
946     {
947         unsigned int row;
948 
949         for (row = 0; row < hdrP->bm_height / 2; ++row) {
950             unsigned int col;
951             for (col = 0; col < hdrP->bm_width; ++col) {
952                 tuples[row][col][PAM_RED_PLANE] = 0;
953                 tuples[row][col][PAM_GRN_PLANE] = 0;
954                 tuples[row][col][PAM_BLU_PLANE] = 0;
955                 tuples[row][col][PAM_TRN_PLANE] = 255;
956             }
957         }
958     }
959 
960     if (hdrP->bits_per_pixel < 16) {
961         readXorPalette(hdrP, imageCursor, imageSize, tuples, index,
962                        needHeaderDump,
963                        bytesConsumedP);
964         *haveAlphaP = false;
965     } else
966         readXorBitfields(hdrP, imageCursor, imageSize, tuples, index,
967                          haveAlphaP, bytesConsumedP);
968 }
969 
970 
971 
972 static void
reportImage(unsigned int const imageIndex,struct BitmapInfoHeader const hdr,bool const haveAlpha)973 reportImage(unsigned int            const imageIndex,
974             struct BitmapInfoHeader const hdr,
975             bool                    const haveAlpha) {
976 
977     const char * const style =
978         haveAlpha ? "RGB +alpha" :
979         hdr.bits_per_pixel < 16 ? "RGB/palette" :
980         "RGB"
981         ;
982 
983     pm_message("image %2u: "
984                "BMP %3u x %3u x %2u (%s)",
985                imageIndex,
986                hdr.bm_width, hdr.bm_height / 2, hdr.bits_per_pixel,
987                style);
988 }
989 
990 
991 
992 static void
convertBmp(const unsigned char * const image,FILE * const ofP,struct IconDirEntry * const dirEntryP,bool const needHeaderDump,bool const wantAndMaskPlane)993 convertBmp(const unsigned char * const image,
994            FILE *                const ofP,
995            struct IconDirEntry * const dirEntryP,
996            bool                  const needHeaderDump,
997            bool                  const wantAndMaskPlane) {
998 
999     struct BitmapInfoHeader hdr;
1000     uint32_t                offset;
1001     bool                    haveAlpha;
1002     uint32_t                xorByteCt;
1003 
1004     struct pam outpam;
1005     tuple **   tuples;
1006 
1007     readBmpHeader(image, dirEntryP->size, dirEntryP->index, needHeaderDump,
1008                   &hdr);
1009 
1010     offset = hdr.header_size;  /* Start after header */
1011 
1012     if ((dirEntryP->width != hdr.bm_width)
1013         || (dirEntryP->height != hdr.bm_height / 2)) {
1014         pm_message("image %2u: "
1015                    "mismatch in header and image dimensions "
1016                    "(%u x %u vs. %u x %u)",
1017                    dirEntryP->index,
1018                    dirEntryP->width,
1019                    dirEntryP->height,
1020                    hdr.bm_width,
1021                    hdr.bm_height / 2);
1022     }
1023 
1024     if ((dirEntryP->bits_per_pixel != 0)
1025         && (dirEntryP->bits_per_pixel != hdr.bits_per_pixel)) {
1026         pm_message("image %2u "
1027                    "mismatch in header and image bpp value"
1028                    "(%u vs. %u)",
1029                    dirEntryP->index,
1030                    dirEntryP->bits_per_pixel,
1031                    hdr.bits_per_pixel);
1032     }
1033 
1034     outpam.size   = sizeof(struct pam);
1035     outpam.len    = PAM_STRUCT_SIZE(allocation_depth);
1036     outpam.file   = ofP;
1037     outpam.format = PAM_FORMAT;
1038     outpam.width  = hdr.bm_width;
1039     outpam.height = hdr.bm_height / 2;
1040     outpam.maxval = 255;
1041     outpam.allocation_depth = 5;
1042     outpam.depth  = 0;
1043         /* Just for tuple array allocation; we set the value for the actual
1044            output image below.
1045         */
1046 
1047     tuples = pnm_allocpamarray(&outpam);
1048 
1049     readXorMask(&hdr, &image[offset],
1050                 dirEntryP->size - offset,
1051                 tuples, dirEntryP->index, needHeaderDump,
1052                 &haveAlpha, &xorByteCt);
1053 
1054     offset += xorByteCt;
1055 
1056     {
1057         /* If there is no alpha channel in XOR mask, store the AND mask to
1058            the transparency plane.  Else, here are two transparency
1059            maps. If requested, store the AND mask to a fifth PAM plane
1060         */
1061         bool haveAnd;
1062         unsigned int andPlane;
1063 
1064         if (!haveAlpha) {
1065             haveAnd = true;
1066             andPlane = PAM_TRN_PLANE;
1067             strcpy (outpam.tuple_type, "RGB_ALPHA");
1068             outpam.depth  = 4;
1069         } else if (wantAndMaskPlane) {
1070             haveAnd = true;
1071             andPlane = PAM_TRN_PLANE + 1;
1072             outpam.depth  = 5;
1073             strcpy(outpam.tuple_type, "RGB_ALPHA_ANDMASK");
1074         } else {
1075             haveAnd = false;
1076             strcpy (outpam.tuple_type, "RGB_ALPHA");
1077             outpam.depth  = 4;
1078         }
1079         if (haveAnd) {
1080             readAnd(&hdr, &image[offset], dirEntryP->size - offset,
1081                     tuples, dirEntryP->index, andPlane, outpam.maxval);
1082         }
1083     }
1084     pnm_writepam(&outpam, tuples);
1085     pnm_freepamarray(tuples, &outpam);
1086 
1087     reportImage(dirEntryP->index, hdr, haveAlpha);
1088 }
1089 
1090 
1091 
1092 static void
reportPngInfo(const unsigned char * const image,struct IconDirEntry * const dirEntryP)1093 reportPngInfo(const unsigned char * const image,
1094               struct IconDirEntry * const dirEntryP) {
1095 
1096     struct PngIhdr ihdr;
1097 
1098     ihdr.length      = u32_be (image, sizeof(pngHeader)  +0);
1099     ihdr.signature   = u32_xx (image, sizeof(pngHeader)  +4);
1100     ihdr.width       = u32_be (image, sizeof(pngHeader)  +8);
1101     ihdr.height      = u32_be (image, sizeof(pngHeader) +12);
1102     ihdr.bit_depth   = u8_be  (image, sizeof(pngHeader) +16);
1103     ihdr.color_type  = u8_be  (image, sizeof(pngHeader) +17);
1104     ihdr.compression = u8_be  (image, sizeof(pngHeader) +18);
1105     ihdr.filter      = u8_be  (image, sizeof(pngHeader) +19);
1106     ihdr.interlace   = u8_be  (image, sizeof(pngHeader) +20);
1107 
1108     if ((ihdr.length != 13)
1109         || ihdr.signature != *(uint32_t*)"IHDR") {
1110         pm_message("image %2u: PNG (uncommonly formatted)",
1111                    dirEntryP->index);
1112     } else {
1113         uint32_t depth;
1114         const char * colorType;
1115 
1116         switch (ihdr.color_type) {
1117         case 0:
1118             colorType = "grayscale";
1119             depth     = ihdr.bit_depth;
1120             break;
1121 
1122         case 2:
1123             colorType = "RGB";
1124             depth     = ihdr.bit_depth * 3;
1125             break;
1126 
1127         case 3:
1128             colorType = "RGB/palette";
1129             depth     = 8;
1130             break;
1131 
1132         case 4:
1133             colorType = "grayscale + alpha";
1134             depth     = ihdr.bit_depth * 2;
1135             break;
1136 
1137         case 6:
1138             colorType = "RGB + alpha";
1139             depth     = ihdr.bit_depth * 4;
1140             break;
1141 
1142         default:
1143             colorType = "unknown color system";
1144             depth     = 0;
1145             break;
1146         }
1147         pm_message("image %2u: PNG %3u x %3u x %2u (%s)",
1148                    dirEntryP->index,
1149                    ihdr.width, ihdr.height, depth, colorType);
1150 
1151         if ((dirEntryP->width != ihdr.width)
1152             || (dirEntryP->height != ihdr.height)) {
1153             pm_message("image %2u:"
1154                        " mismatch in header and image dimensions"
1155                        " (%u x %u vs %u x %u)",
1156                        dirEntryP->index, dirEntryP->width, dirEntryP->height,
1157                        ihdr.width, ihdr.height);
1158         }
1159         /* Mismatch between dirEntryP->bits_per_pixel and 'depth' is
1160            normal, because the creator of the winicon file doesn't necessarily
1161            know the true color resolution.
1162         */
1163     }
1164 }
1165 
1166 
1167 
1168 static void
convertPng(const unsigned char * const image,FILE * const ofP,struct IconDirEntry * const dirEntryP)1169 convertPng(const unsigned char * const image,
1170            FILE *                const ofP,
1171            struct IconDirEntry * const dirEntryP) {
1172 
1173     pm_bufferDesc imageBuffer;
1174 
1175     reportPngInfo(image, dirEntryP);
1176 
1177     imageBuffer.size              = dirEntryP->size;
1178     imageBuffer.buffer            = (unsigned char *)image;
1179     imageBuffer.bytesTransferredP = NULL;
1180 
1181     fflush(stdout);
1182 
1183     pm_system_lp("pngtopam", pm_feed_from_memory, &imageBuffer,
1184                  NULL /* stdout accepter */, NULL,
1185                  "pngtopam", "-alphapam", NULL);
1186 }
1187 
1188 
1189 
1190 static uint32_t
bestImage(struct IconDir * const dirP)1191 bestImage(struct IconDir * const dirP) {
1192 
1193     uint32_t imageIndex;
1194     uint32_t bestPixelCt;
1195     uint32_t bestColorCt;
1196     uint16_t best;
1197 
1198     bestPixelCt = 0;  /* initial value */
1199     bestColorCt = 0;  /* initial value */
1200     best        = 0;  /* initial value */
1201 
1202     for (imageIndex = 0; dirP->count > imageIndex; ++imageIndex) {
1203         struct IconDirEntry * const dirEntryP = &dirP->entries[imageIndex];
1204 
1205         uint32_t const pixelCt = dirEntryP->width * dirEntryP->height;
1206 
1207         uint32_t colorCt;
1208 
1209         /*  32-bit icons have 24 bit color information only.
1210 
1211             Since NT 5.1 (aka WinXP), it is allowed to place 8-bit
1212             transparency information in the remaining bits (to check,
1213             you have to read all these bits in the image!), so I prefer
1214             32-bit images over 24-bit images (which violate the
1215             spec. anyway).
1216         */
1217         if (dirEntryP->bits_per_pixel > 24)
1218             colorCt = 1u << 25;
1219         else
1220             colorCt = 1u << dirEntryP->bits_per_pixel;
1221 
1222         if (dirEntryP->color_count != 0 && colorCt > dirEntryP->color_count)
1223             colorCt = dirEntryP->color_count;
1224 
1225         if ((pixelCt > bestPixelCt)
1226             || ((pixelCt == bestPixelCt) && (colorCt > bestColorCt))) {
1227             /* This is a new best */
1228             bestPixelCt = pixelCt;
1229             bestColorCt = colorCt;
1230             best        = imageIndex;
1231         }
1232     }
1233     return best;
1234 }
1235 
1236 
1237 
1238 static void
convertImage(struct File * const icoP,struct IconDirEntry * const dirEntryP,FILE * const ofP,bool const needHeaderDump,bool const wantAndMaskPlane)1239 convertImage(struct File *         const icoP,
1240              struct IconDirEntry * const dirEntryP,
1241              FILE *                const ofP,
1242              bool                  const needHeaderDump,
1243              bool                  const wantAndMaskPlane) {
1244 
1245     const unsigned char * image;  /* malloced */
1246 
1247     image = readImage(icoP, dirEntryP);
1248 
1249     if (MEMEQ(image, pngHeader, sizeof (pngHeader)))
1250         convertPng(image, ofP, dirEntryP);
1251     else
1252         convertBmp(image, ofP, dirEntryP, needHeaderDump, wantAndMaskPlane);
1253 
1254     free((void *)image);
1255 }
1256 
1257 
1258 
1259 int
main(int argc,const char * argv[])1260 main (int argc, const char *argv []) {
1261 
1262     struct File ico;
1263     struct IconDir * dirP;
1264     struct CmdlineInfo cmdline;
1265 
1266     pm_proginit (&argc, argv);
1267 
1268     parseCommandLine(argc, argv, &cmdline);
1269 
1270     verbose = cmdline.verbose;
1271 
1272     ico.name =
1273         streq(cmdline.inputFileName, "-") ?  "<stdin>" : cmdline.inputFileName;
1274     ico.pos   = 0;
1275     ico.fileP = pm_openr(cmdline.inputFileName);
1276 
1277     dirP = readIconDir(&ico, cmdline.headerdump);
1278 
1279     if (cmdline.allimages) {
1280         unsigned int i;
1281         for (i = 0; i < dirP->count; ++i)
1282             convertImage(&ico, &dirP->entries[i], stdout,
1283                          cmdline.headerdump, cmdline.andmasks);
1284     } else if (cmdline.imageSpec) {
1285         unsigned int i;
1286         bool found;
1287         for (i = 0, found = false; i < dirP->count; ++i) {
1288             if (dirP->entries[i].index == cmdline.image) {
1289                 found = true;
1290                 convertImage(&ico, &dirP->entries[i], stdout,
1291                              cmdline.headerdump, cmdline.andmasks);
1292             }
1293         }
1294         if (!found)
1295             pm_error("no image index %u in.input", cmdline.image);
1296     } else {
1297         convertImage(&ico, &dirP->entries[bestImage(dirP)], stdout,
1298                      cmdline.headerdump, cmdline.andmasks);
1299     }
1300 
1301     freeIconDir(dirP);
1302 
1303     if (ico.fileP != stdin)
1304         pm_close(ico.fileP);
1305 
1306     return 0;
1307 }
1308 
1309 
1310 
1311