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