1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // Windows ICO and CUR formats
7 #include <deark-private.h>
8 #include <deark-fmtutil.h>
9 DE_DECLARE_MODULE(de_module_ico
);
10 DE_DECLARE_MODULE(de_module_win1ico
);
16 int hotspot_x
, hotspot_y
; // Valid if lctx::is_cur
19 typedef struct localctx_struct
{
21 int extract_unused_masks
;
24 static void do_extract_png(deark
*c
, lctx
*d
, i64 pos
, i64 len
)
29 // Peek at the PNG data, to figure out the dimensions.
30 w
= de_getu32be(pos
+16);
31 h
= de_getu32be(pos
+20);
33 de_snprintf(ext
, sizeof(ext
), "%dx%d.png", (int)w
, (int)h
);
35 // TODO?: Might be nice to edit the PNG file to add an htSP chunk for the
36 // hotspot, but it seems like more trouble than it's worth.
37 dbuf_create_file_from_slice(c
->infile
, pos
, len
, ext
, NULL
, 0);
40 static u32
get_inv_bkgd_replacement_clr(i64 i
, i64 j
)
43 return DE_MAKE_RGBA(255,0,128,128);
45 return DE_MAKE_RGBA(128,0,255,128);
48 static void warn_inv_bkgd(deark
*c
)
50 de_warn(c
, "This image contains inverse background pixels, which are not "
54 static void do_image_data(deark
*c
, lctx
*d
, struct page_ctx
*pg
)
57 i64 fg_start
, bg_start
;
61 de_bitmap
*img
= NULL
;
62 de_bitmap
*mask_img
= NULL
;
65 u8 cr
=0, cg
=0, cb
=0, ca
=0;
68 int has_alpha_channel
= 0;
69 i64 pdwidth
, mask_pdwidth
;
70 char filename_token
[32];
71 i64 pos1
= pg
->data_offset
;
72 i64 len
= pg
->data_size
;
74 if(pos1
+len
> c
->infile
->len
) goto done
;
76 if(!fmtutil_get_bmpinfo(c
, c
->infile
, &bi
, pos1
, len
, DE_BMPINFO_ICO_FORMAT
)) {
77 de_err(c
, "Invalid bitmap");
81 if(bi
.file_format
== DE_BMPINFO_FMT_PNG
) {
82 do_extract_png(c
, d
, pos1
, len
);
87 case 1: case 2: case 4: case 8: case 24: case 32:
90 de_err(c
, "(image #%d) Unsupported bit count (%d)", (int)pg
->img_num
, (int)bi
.bitcount
);
93 de_err(c
, "(image #%d) Invalid bit count (%d)", (int)pg
->img_num
, (int)bi
.bitcount
);
97 if(bi
.compression_field
!=0) {
98 // TODO: Support BITFIELDS
99 de_err(c
, "Compression / BITFIELDS not supported");
103 if(bi
.bitcount
==32) {
104 // 32bpp images have both an alpha channel, and a 1bpp "mask".
105 // We never use a 32bpp image's mask (although we may extract it
107 // I'm not sure that's necessarily the best thing to do. I think that
108 // in theory the mask could be used to get inverted-background-color
109 // pixels, though I don't know if Windows allows that.
111 has_alpha_channel
= 1;
117 de_snprintf(filename_token
, sizeof(filename_token
), "%dx%dx%d",
118 (int)bi
.width
, (int)bi
.height
, (int)bi
.bitcount
);
120 pdwidth
= (bi
.rowspan
* 8)/bi
.bitcount
;
121 mask_pdwidth
= bi
.mask_rowspan
* 8;
123 img
= de_bitmap_create2(c
, bi
.width
, pdwidth
, bi
.height
, 4);
126 de_zeromem(pal
, sizeof(pal
));
127 if (bi
.pal_entries
> 0) {
128 if(bi
.pal_entries
>256) goto done
;
130 de_read_palette_rgb(c
->infile
,
131 pos1
+bi
.infohdrsize
, bi
.pal_entries
, bi
.bytes_per_pal_entry
,
132 pal
, 256, DE_GETRGBFLAG_BGR
);
135 fg_start
= pos1
+ bi
.size_of_headers_and_pal
;
136 bg_start
= pos1
+ bi
.size_of_headers_and_pal
+ bi
.foreground_size
;
138 de_dbg(c
, "foreground at %d, mask at %d", (int)fg_start
, (int)bg_start
);
140 // Foreground padding pixels exist if the width times the bitcount is not a
141 // multiple of 32. This is rare.
142 // Mask padding pixels exist if the width is not a multiple of 32. This is
143 // common (when width=16 or 48).
145 // Note: For the -padpix feature, we never combine the mask image's padding
146 // pixels with the foreground image's padding pixels.
148 // (1) There may be more mask padding pixels than image padding pixels.
149 // (2) Inverse background pixels, and the warning about them.
150 // The mask's padding will be normally be ignored, but the padded mask will
151 // be written to a separate file if d->extract_unused_masks is enabled.
153 mask_img
= de_bitmap_create2(c
, bi
.width
, mask_pdwidth
, bi
.height
, 1);
154 de_convert_image_bilevel(c
->infile
, bg_start
, bi
.mask_rowspan
, mask_img
, 0);
156 for(j
=0; j
<img
->height
; j
++) {
157 for(i
=0; i
<pdwidth
; i
++) {
161 p
= fg_start
+ bi
.rowspan
*j
;
162 x
= de_get_bits_symbol(c
->infile
, bi
.bitcount
, p
, i
);
163 cr
= DE_COLOR_R(pal
[x
]);
164 cg
= DE_COLOR_G(pal
[x
]);
165 cb
= DE_COLOR_B(pal
[x
]);
167 //else if(bi.bitcount==16) {
170 else if(bi
.bitcount
==24) {
171 p
= fg_start
+ bi
.rowspan
*j
+ i
*3;
172 cb
= de_getbyte(p
+0);
173 cg
= de_getbyte(p
+1);
174 cr
= de_getbyte(p
+2);
176 else if(bi
.bitcount
==32) {
177 p
= fg_start
+ bi
.rowspan
*j
+ i
*4;
178 cb
= de_getbyte(p
+0);
179 cg
= de_getbyte(p
+1);
180 cr
= de_getbyte(p
+2);
181 if(has_alpha_channel
) {
182 ca
= de_getbyte(p
+3);
186 if(use_mask
&& i
<bi
.width
) {
188 // Refer to the mask, if the main bitmap didn't already
189 // have transparency.
191 maskclr
= DE_COLOR_K(de_bitmap_getpixel(mask_img
, i
, j
));
192 ca
= maskclr
? 0 : 255;
194 // Inverted background pixels
195 // TODO: Should we do this only for cursors, and not icons?
196 if(maskclr
&& (cr
|| cg
|| cb
)) {
200 newclr
= get_inv_bkgd_replacement_clr(i
, j
);
201 cr
= DE_COLOR_R(newclr
);
202 cg
= DE_COLOR_G(newclr
);
203 cb
= DE_COLOR_B(newclr
);
204 ca
= DE_COLOR_A(newclr
);
208 de_bitmap_setpixel_rgba(img
, i
, j
, DE_MAKE_RGBA(cr
,cg
,cb
,ca
));
216 de_bitmap_optimize_alpha(img
, (bi
.bitcount
==32)?0x1:0x0);
218 fi
= de_finfo_create(c
);
220 de_finfo_set_name_from_sz(c
, fi
, filename_token
, 0, DE_ENCODING_ASCII
);
224 fi
->hotspot_x
= pg
->hotspot_x
;
225 fi
->hotspot_y
= pg
->hotspot_y
;
228 de_bitmap_write_to_file_finfo(img
, fi
, DE_CREATEFLAG_FLIP_IMAGE
);
230 if(d
->extract_unused_masks
&& (!use_mask
|| (c
->padpix
&& mask_pdwidth
>bi
.width
))) {
231 char maskname_token
[32];
233 de_snprintf(maskname_token
, sizeof(maskname_token
), "%dx%dx%dmask",
234 (int)bi
.width
, (int)bi
.height
, (int)bi
.bitcount
);
235 de_bitmap_write_to_file(mask_img
, maskname_token
, DE_CREATEFLAG_IS_AUX
| DE_CREATEFLAG_FLIP_IMAGE
);
239 de_bitmap_destroy(img
);
240 de_bitmap_destroy(mask_img
);
241 de_finfo_destroy(c
, fi
);
244 static void do_image_dir_entry(deark
*c
, lctx
*d
, i64 img_num
, i64 pos
)
246 struct page_ctx
*pg
= NULL
;
248 pg
= de_malloc(c
, sizeof(struct page_ctx
));
249 pg
->img_num
= img_num
;
251 de_dbg(c
, "image #%d, index at %d", (int)pg
->img_num
, (int)pos
);
254 pg
->hotspot_x
= (int)de_getu16le(pos
+4);
255 pg
->hotspot_y
= (int)de_getu16le(pos
+6);
256 de_dbg(c
, "hotspot: %d,%d", pg
->hotspot_x
, pg
->hotspot_y
);
258 pg
->data_size
= de_getu32le(pos
+8);
259 pg
->data_offset
= de_getu32le(pos
+12);
260 de_dbg(c
, "offset=%"I64_FMT
", size=%"I64_FMT
, pg
->data_offset
, pg
->data_size
);
262 do_image_data(c
, d
, pg
);
265 de_dbg_indent(c
, -1);
268 static void de_run_ico(deark
*c
, de_module_params
*mparams
)
275 d
= de_malloc(c
, sizeof(lctx
));
276 d
->extract_unused_masks
= (c
->extract_level
>=2);
281 de_declare_fmt(c
, "Windows Icon");
285 de_declare_fmt(c
, "Windows Cursor");
288 de_dbg(c
, "Not an ICO/CUR file");
292 num_images
= de_getu16le(4);
293 de_dbg(c
, "images in file: %d", (int)num_images
);
294 if(!de_good_image_count(c
, num_images
)) {
298 for(i
=0; i
<num_images
; i
++) {
299 do_image_dir_entry(c
, d
, i
, 6+16*i
);
306 // Windows icons and cursors don't have a distinctive signature. This
307 // function tries to screen out other formats.
308 static int is_windows_ico_or_cur(deark
*c
)
316 if(de_memcmp(buf
, "\x00\x00\x01\x00", 4) &&
317 de_memcmp(buf
, "\x00\x00\x02\x00", 4))
322 numicons
= de_getu16le(4);
324 // Each icon must use at least 16 bytes for the directory, 40 for the
325 // info header, 4 for the foreground, and 4 for the mask.
326 if(numicons
<1 || (6+numicons
*64)>c
->infile
->len
) return 0;
328 // Examine the first few icon index entries.
329 for(i
=0; i
<numicons
&& i
<8; i
++) {
330 size
= de_getu32le(6+16*i
+8);
331 offset
= de_getu32le(6+16*i
+12);
332 if(size
<48) return 0;
333 if(offset
< 6+numicons
*16) return 0;
334 if(offset
+size
> c
->infile
->len
) return 0;
339 static int de_identify_ico(deark
*c
)
341 if(is_windows_ico_or_cur(c
)) {
347 void de_module_ico(deark
*c
, struct deark_module_info
*mi
)
350 mi
->desc
= "Windows icon/cursor";
351 mi
->run_fn
= de_run_ico
;
352 mi
->identify_fn
= de_identify_ico
;
355 ////////////////////////////////////////////////////////////////
357 typedef struct win1ctx_struct
{
358 unsigned int type_code
;
360 const char *type_name
;
364 static int decode_win1_icon(deark
*c
, win1ctx
*d
, i64 pos1
)
366 de_bitmap
*mask
= NULL
;
367 de_bitmap
*img
= NULL
;
374 int has_inv_bkgd
= 0;
378 int saved_indent_level
;
380 de_dbg_indent_save(c
, &saved_indent_level
);
381 if(pos1
+12 > c
->infile
->len
) goto done
;
383 de_dbg(c
, "%s at %"I64_FMT
, d
->type_name
, pos
);
387 hotspot_x
= (int)de_getu16le(pos
);
388 hotspot_y
= (int)de_getu16le(pos
+2);
389 de_dbg(c
, "hotspot: %d,%d", hotspot_x
, hotspot_y
);
393 npwidth
= de_getu16le_p(&pos
);
394 h
= de_getu16le_p(&pos
);
395 de_dbg_dimensions(c
, npwidth
, h
);
396 if(!de_good_image_dimensions(c
, npwidth
, h
)) goto done
;
398 rowspan
= de_getu16le_p(&pos
);
399 de_dbg(c
, "bytes/row: %d", (int)rowspan
);
403 unsigned int csColor
;
404 csColor
= (unsigned int)de_getu16le(pos
);
405 de_dbg(c
, "csColor: 0x%04x", csColor
);
409 mask
= de_bitmap_create2(c
, npwidth
, pdwidth
, h
, 1);
410 img
= de_bitmap_create2(c
, npwidth
, pdwidth
, h
, 4);
411 de_dbg(c
, "mask at %"I64_FMT
, pos
);
412 de_convert_image_bilevel(c
->infile
, pos
, rowspan
, mask
, 0);
414 de_dbg(c
, "foreground at %"I64_FMT
, pos
);
415 de_convert_image_bilevel(c
->infile
, pos
, rowspan
, img
, 0);
418 // This whole loop does nothing, except handle inverse-background-color
419 // pixels. But we have to do something, because such pixels are not
422 for(i
=0; i
<pdwidth
; i
++) {
426 maskclr
= DE_COLOR_K(de_bitmap_getpixel(mask
, i
, j
));
427 if(maskclr
==0) continue;
428 fgclr
= DE_COLOR_K(de_bitmap_getpixel(img
, i
, j
));
429 if(fgclr
==0) continue;
431 newclr
= get_inv_bkgd_replacement_clr(i
, j
);
432 de_bitmap_setpixel_gray(mask
, i
, j
, 255-DE_COLOR_A(newclr
));
433 de_bitmap_setpixel_rgb(img
, i
, j
, DE_MAKE_OPAQUE(newclr
));
443 de_bitmap_apply_mask(img
, mask
, DE_BITMAPFLAG_WHITEISTRNS
);
445 fi
= de_finfo_create(c
);
449 fi
->hotspot_x
= hotspot_x
;
450 fi
->hotspot_y
= hotspot_y
;
453 de_bitmap_write_to_file_finfo(img
, fi
, DE_CREATEFLAG_OPT_IMAGE
);
454 d
->bytes_consumed
= pos
- pos1
;
458 de_bitmap_destroy(img
);
459 de_bitmap_destroy(mask
);
460 de_finfo_destroy(c
, fi
);
461 de_dbg_indent_restore(c
, saved_indent_level
);
465 static void de_run_win1ico(deark
*c
, de_module_params
*mparams
)
470 d
= de_malloc(c
, sizeof(win1ctx
));
471 d
->type_code
= (unsigned int)de_getu16le_p(&pos
);
472 de_dbg(c
, "type code: 0x%04x", d
->type_code
);
473 if(d
->type_code
==0x0003 || d
->type_code
==0x0103 || d
->type_code
==0x0203) {
475 d
->type_name
= "cursor";
477 else if(d
->type_code
==0x0001 || d
->type_code
==0x0101 || d
->type_code
==0x0201) {
478 d
->type_name
= "icon";
481 de_err(c
, "Not a Windows 1.0 icon/cursor");
484 de_declare_fmtf(c
, "Windows 1.0 %s", d
->type_name
);
486 if(!decode_win1_icon(c
, d
, pos
)) goto done
;
487 pos
+= d
->bytes_consumed
;
488 if((d
->type_code
& 0xff00)==0x0200) {
489 // In this case there are supposed to be two icons (this is untested).
490 if(!decode_win1_icon(c
, d
, pos
)) goto done
;
497 static int de_identify_win1ico(deark
*c
)
503 tclo
= de_getbyte(0);
504 tchi
= de_getbyte(1);
505 if((tclo
==1 || tclo
==3) && (tchi
<=2)) {
514 wb
= de_getu16le(10);
515 if(w
<16 || h
<16 || w
>256 || h
>256) return 0;
516 if(wb
!= ((w
+15)/16)*2) return 0;
517 has_ext
= de_input_file_has_ext(c
, (tclo
==3)?"cur":"ico");
518 if((w
==32 || w
==64) && h
==w
&& has_ext
) return 100;
519 return has_ext
? 70 : 6;
522 void de_module_win1ico(deark
*c
, struct deark_module_info
*mi
)
525 mi
->desc
= "Windows 1.0 icon/cursor";
526 mi
->run_fn
= de_run_win1ico
;
527 mi
->identify_fn
= de_identify_win1ico
;