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
17 i64 fg_start
, bg_start
;
18 i64 pdwidth
, mask_pdwidth
;
20 int has_alpha_channel
;
28 typedef struct localctx_struct
{
30 int extract_unused_masks
;
33 static void do_extract_png(deark
*c
, lctx
*d
, i64 pos
, i64 len
)
38 // Peek at the PNG data, to figure out the dimensions.
39 w
= de_getu32be(pos
+16);
40 h
= de_getu32be(pos
+20);
42 de_snprintf(ext
, sizeof(ext
), "%dx%d.png", (int)w
, (int)h
);
44 // TODO?: Might be nice to edit the PNG file to add an htSP chunk for the
45 // hotspot, but it seems like more trouble than it's worth.
46 dbuf_create_file_from_slice(c
->infile
, pos
, len
, ext
, NULL
, 0);
49 static u32
get_inv_bkgd_replacement_clr(i64 i
, i64 j
)
52 return DE_MAKE_RGBA(255,0,128,128);
54 return DE_MAKE_RGBA(128,0,255,128);
57 static void warn_inv_bkgd(deark
*c
)
59 de_warn(c
, "This image contains inverse background pixels, which are not "
63 static void decode_fg_image_default(deark
*c
, lctx
*d
, struct page_ctx
*pg
)
66 de_bitmap
*img
= pg
->img
;
68 // Read the main image
69 if(pg
->bi
.bitcount
==24) {
70 de_convert_image_rgb(c
->infile
, pg
->fg_start
, pg
->bi
.rowspan
, 3,
71 img
, DE_GETRGBFLAG_BGR
);
73 else if(pg
->bi
.bitcount
<=8) {
74 de_convert_image_paletted(c
->infile
, pg
->fg_start
, pg
->bi
.bitcount
,
75 pg
->bi
.rowspan
, pg
->pal
, img
, 0);
81 // Apply the mask (already read) to the main image
82 for(j
=0; j
<img
->height
; j
++) {
83 for(i
=0; i
<pg
->bi
.width
; i
++) {
87 maskclr
= DE_COLOR_K(de_bitmap_getpixel(pg
->mask_img
, i
, j
));
88 if(maskclr
==0) continue; // normal opaque pixel
90 fgclr
= de_bitmap_getpixel(pg
->img
, i
, j
);
91 if((fgclr
& 0x00ffffffU
)==0) {
92 // normal transparent pixel
93 de_bitmap_setpixel_rgba(pg
->img
, i
, j
, DE_STOCKCOLOR_TRANSPARENT
);
99 newclr
= get_inv_bkgd_replacement_clr(i
, j
);
100 de_bitmap_setpixel_rgba(pg
->img
, i
, j
, newclr
);
106 static void decode_fg_image_32(deark
*c
, lctx
*d
, struct page_ctx
*pg
)
110 de_bitmap
*img
= pg
->img
;
112 for(j
=0; j
<img
->height
; j
++) {
113 pos
= pg
->fg_start
+ pg
->bi
.rowspan
*j
;
115 for(i
=0; i
<pg
->pdwidth
; i
++) {
118 cb
= de_getbyte_p(&pos
);
119 cg
= de_getbyte_p(&pos
);
120 cr
= de_getbyte_p(&pos
);
121 ca
= de_getbyte_p(&pos
);
122 de_bitmap_setpixel_rgba(img
, i
, j
, DE_MAKE_RGBA(cr
,cg
,cb
,ca
));
127 static void do_image_data(deark
*c
, lctx
*d
, struct page_ctx
*pg
)
130 char filename_token
[32];
131 i64 pos1
= pg
->data_offset
;
132 i64 len
= pg
->data_size
;
134 if(pos1
+len
> c
->infile
->len
) goto done
;
136 if(!fmtutil_get_bmpinfo(c
, c
->infile
, &pg
->bi
, pos1
, len
, DE_BMPINFO_ICO_FORMAT
)) {
137 de_err(c
, "Invalid bitmap");
141 if(pg
->bi
.file_format
== DE_BMPINFO_FMT_PNG
) {
142 do_extract_png(c
, d
, pos1
, len
);
146 switch(pg
->bi
.bitcount
) {
147 case 1: case 2: case 4: case 8: case 24: case 32:
150 de_err(c
, "(image #%d) Unsupported bit count (%d)", (int)pg
->img_num
, (int)pg
->bi
.bitcount
);
153 de_err(c
, "(image #%d) Invalid bit count (%d)", (int)pg
->img_num
, (int)pg
->bi
.bitcount
);
157 if(pg
->bi
.compression_field
!=0) {
158 // TODO: Support BITFIELDS
159 de_err(c
, "Compression / BITFIELDS not supported");
163 if(pg
->bi
.bitcount
==32) {
164 // 32bpp images have both an alpha channel, and a 1bpp "mask".
165 // We never use a 32bpp image's mask (although we may extract it
167 // I'm not sure that's necessarily the best thing to do. I think that
168 // in theory the mask could be used to get inverted-background-color
169 // pixels, though I don't know if Windows allows that.
171 pg
->has_alpha_channel
= 1;
177 de_snprintf(filename_token
, sizeof(filename_token
), "%dx%dx%d",
178 (int)pg
->bi
.width
, (int)pg
->bi
.height
, (int)pg
->bi
.bitcount
);
180 pg
->pdwidth
= (pg
->bi
.rowspan
* 8)/pg
->bi
.bitcount
;
181 pg
->mask_pdwidth
= pg
->bi
.mask_rowspan
* 8;
183 pg
->img
= de_bitmap_create2(c
, pg
->bi
.width
, pg
->pdwidth
, pg
->bi
.height
, 4);
186 if (pg
->bi
.pal_entries
> 0) {
187 if(pg
->bi
.pal_entries
>256) goto done
;
189 de_read_palette_rgb(c
->infile
,
190 pos1
+pg
->bi
.infohdrsize
, pg
->bi
.pal_entries
, pg
->bi
.bytes_per_pal_entry
,
191 pg
->pal
, 256, DE_GETRGBFLAG_BGR
);
194 pg
->fg_start
= pos1
+ pg
->bi
.size_of_headers_and_pal
;
195 pg
->bg_start
= pos1
+ pg
->bi
.size_of_headers_and_pal
+ pg
->bi
.foreground_size
;
197 de_dbg(c
, "foreground at %d, mask at %d", (int)pg
->fg_start
, (int)pg
->bg_start
);
199 // Foreground padding pixels exist if the width times the bitcount is not a
200 // multiple of 32. This is rare.
201 // Mask padding pixels exist if the width is not a multiple of 32. This is
202 // common (when width=16 or 48).
204 // Note: For the -padpix feature, we never combine the mask image's padding
205 // pixels with the foreground image's padding pixels.
207 // (1) There may be more mask padding pixels than image padding pixels.
208 // (2) Inverse background pixels, and the warning about them.
209 // The mask's padding will be normally be ignored, but the padded mask will
210 // be written to a separate file if d->extract_unused_masks is enabled.
212 pg
->mask_img
= de_bitmap_create2(c
, pg
->bi
.width
, pg
->mask_pdwidth
, pg
->bi
.height
, 1);
213 de_convert_image_bilevel(c
->infile
, pg
->bg_start
, pg
->bi
.mask_rowspan
, pg
->mask_img
, 0);
215 if(pg
->bi
.bitcount
==32) {
216 decode_fg_image_32(c
, d
, pg
);
219 decode_fg_image_default(c
, d
, pg
);
222 if(pg
->has_inv_bkgd
) {
226 de_bitmap_optimize_alpha(pg
->img
, (pg
->bi
.bitcount
==32)?0x1:0x0);
228 fi
= de_finfo_create(c
);
230 de_finfo_set_name_from_sz(c
, fi
, filename_token
, 0, DE_ENCODING_ASCII
);
234 fi
->hotspot_x
= pg
->hotspot_x
;
235 fi
->hotspot_y
= pg
->hotspot_y
;
238 de_bitmap_write_to_file_finfo(pg
->img
, fi
, DE_CREATEFLAG_FLIP_IMAGE
);
240 if(d
->extract_unused_masks
&& (!pg
->use_mask
|| (c
->padpix
&& pg
->mask_pdwidth
>pg
->bi
.width
))) {
241 char maskname_token
[32];
243 de_snprintf(maskname_token
, sizeof(maskname_token
), "%dx%dx%dmask",
244 (int)pg
->bi
.width
, (int)pg
->bi
.height
, (int)pg
->bi
.bitcount
);
245 de_bitmap_write_to_file(pg
->mask_img
, maskname_token
, DE_CREATEFLAG_IS_AUX
| DE_CREATEFLAG_FLIP_IMAGE
);
250 de_bitmap_destroy(pg
->img
);
252 de_bitmap_destroy(pg
->mask_img
);
255 de_finfo_destroy(c
, fi
);
258 static void do_image_dir_entry(deark
*c
, lctx
*d
, i64 img_num
, i64 pos
)
260 struct page_ctx
*pg
= NULL
;
262 pg
= de_malloc(c
, sizeof(struct page_ctx
));
263 pg
->img_num
= img_num
;
265 de_dbg(c
, "image #%d, index at %d", (int)pg
->img_num
, (int)pos
);
268 pg
->hotspot_x
= (int)de_getu16le(pos
+4);
269 pg
->hotspot_y
= (int)de_getu16le(pos
+6);
270 de_dbg(c
, "hotspot: %d,%d", pg
->hotspot_x
, pg
->hotspot_y
);
272 pg
->data_size
= de_getu32le(pos
+8);
273 pg
->data_offset
= de_getu32le(pos
+12);
274 de_dbg(c
, "offset=%"I64_FMT
", size=%"I64_FMT
, pg
->data_offset
, pg
->data_size
);
276 do_image_data(c
, d
, pg
);
279 de_dbg_indent(c
, -1);
282 static void de_run_ico(deark
*c
, de_module_params
*mparams
)
289 d
= de_malloc(c
, sizeof(lctx
));
290 d
->extract_unused_masks
= (c
->extract_level
>=2);
295 de_declare_fmt(c
, "Windows Icon");
299 de_declare_fmt(c
, "Windows Cursor");
302 de_dbg(c
, "Not an ICO/CUR file");
306 num_images
= de_getu16le(4);
307 de_dbg(c
, "images in file: %d", (int)num_images
);
308 if(!de_good_image_count(c
, num_images
)) {
312 for(i
=0; i
<num_images
; i
++) {
313 do_image_dir_entry(c
, d
, i
, 6+16*i
);
320 // Windows icons and cursors don't have a distinctive signature. This
321 // function tries to screen out other formats.
322 static int is_windows_ico_or_cur(deark
*c
)
330 if(de_memcmp(buf
, "\x00\x00\x01\x00", 4) &&
331 de_memcmp(buf
, "\x00\x00\x02\x00", 4))
336 numicons
= de_getu16le(4);
338 // Each icon must use at least 16 bytes for the directory, 40 for the
339 // info header, 4 for the foreground, and 4 for the mask.
340 if(numicons
<1 || (6+numicons
*64)>c
->infile
->len
) return 0;
342 // Examine the first few icon index entries.
343 for(i
=0; i
<numicons
&& i
<8; i
++) {
344 size
= de_getu32le(6+16*i
+8);
345 offset
= de_getu32le(6+16*i
+12);
346 if(size
<48) return 0;
347 if(offset
< 6+numicons
*16) return 0;
348 if(offset
+size
> c
->infile
->len
) return 0;
353 static int de_identify_ico(deark
*c
)
355 if(is_windows_ico_or_cur(c
)) {
361 void de_module_ico(deark
*c
, struct deark_module_info
*mi
)
364 mi
->desc
= "Windows icon/cursor";
365 mi
->run_fn
= de_run_ico
;
366 mi
->identify_fn
= de_identify_ico
;
369 ////////////////////////////////////////////////////////////////
371 typedef struct win1ctx_struct
{
372 unsigned int type_code
;
374 const char *type_name
;
378 static int decode_win1_icon(deark
*c
, win1ctx
*d
, i64 pos1
)
380 de_bitmap
*mask
= NULL
;
381 de_bitmap
*img
= NULL
;
388 int has_inv_bkgd
= 0;
392 int saved_indent_level
;
394 de_dbg_indent_save(c
, &saved_indent_level
);
395 if(pos1
+12 > c
->infile
->len
) goto done
;
397 de_dbg(c
, "%s at %"I64_FMT
, d
->type_name
, pos
);
401 hotspot_x
= (int)de_getu16le(pos
);
402 hotspot_y
= (int)de_getu16le(pos
+2);
403 de_dbg(c
, "hotspot: %d,%d", hotspot_x
, hotspot_y
);
407 npwidth
= de_getu16le_p(&pos
);
408 h
= de_getu16le_p(&pos
);
409 de_dbg_dimensions(c
, npwidth
, h
);
410 if(!de_good_image_dimensions(c
, npwidth
, h
)) goto done
;
412 rowspan
= de_getu16le_p(&pos
);
413 de_dbg(c
, "bytes/row: %d", (int)rowspan
);
417 unsigned int csColor
;
418 csColor
= (unsigned int)de_getu16le(pos
);
419 de_dbg(c
, "csColor: 0x%04x", csColor
);
423 mask
= de_bitmap_create2(c
, npwidth
, pdwidth
, h
, 1);
424 img
= de_bitmap_create2(c
, npwidth
, pdwidth
, h
, 4);
425 de_dbg(c
, "mask at %"I64_FMT
, pos
);
426 de_convert_image_bilevel(c
->infile
, pos
, rowspan
, mask
, 0);
428 de_dbg(c
, "foreground at %"I64_FMT
, pos
);
429 de_convert_image_bilevel(c
->infile
, pos
, rowspan
, img
, 0);
432 // This whole loop does nothing, except handle inverse-background-color
433 // pixels. But we have to do something, because such pixels are not
436 for(i
=0; i
<pdwidth
; i
++) {
440 maskclr
= DE_COLOR_K(de_bitmap_getpixel(mask
, i
, j
));
441 if(maskclr
==0) continue;
442 fgclr
= DE_COLOR_K(de_bitmap_getpixel(img
, i
, j
));
443 if(fgclr
==0) continue;
445 newclr
= get_inv_bkgd_replacement_clr(i
, j
);
446 de_bitmap_setpixel_gray(mask
, i
, j
, 255-DE_COLOR_A(newclr
));
447 de_bitmap_setpixel_rgb(img
, i
, j
, DE_MAKE_OPAQUE(newclr
));
457 de_bitmap_apply_mask(img
, mask
, DE_BITMAPFLAG_WHITEISTRNS
);
459 fi
= de_finfo_create(c
);
463 fi
->hotspot_x
= hotspot_x
;
464 fi
->hotspot_y
= hotspot_y
;
467 de_bitmap_write_to_file_finfo(img
, fi
, DE_CREATEFLAG_OPT_IMAGE
);
468 d
->bytes_consumed
= pos
- pos1
;
472 de_bitmap_destroy(img
);
473 de_bitmap_destroy(mask
);
474 de_finfo_destroy(c
, fi
);
475 de_dbg_indent_restore(c
, saved_indent_level
);
479 static void de_run_win1ico(deark
*c
, de_module_params
*mparams
)
484 d
= de_malloc(c
, sizeof(win1ctx
));
485 d
->type_code
= (unsigned int)de_getu16le_p(&pos
);
486 de_dbg(c
, "type code: 0x%04x", d
->type_code
);
487 if(d
->type_code
==0x0003 || d
->type_code
==0x0103 || d
->type_code
==0x0203) {
489 d
->type_name
= "cursor";
491 else if(d
->type_code
==0x0001 || d
->type_code
==0x0101 || d
->type_code
==0x0201) {
492 d
->type_name
= "icon";
495 de_err(c
, "Not a Windows 1.0 icon/cursor");
498 de_declare_fmtf(c
, "Windows 1.0 %s", d
->type_name
);
500 if(!decode_win1_icon(c
, d
, pos
)) goto done
;
501 pos
+= d
->bytes_consumed
;
502 if((d
->type_code
& 0xff00)==0x0200) {
503 // In this case there are supposed to be two icons (this is untested).
504 if(!decode_win1_icon(c
, d
, pos
)) goto done
;
511 static int de_identify_win1ico(deark
*c
)
517 tclo
= de_getbyte(0);
518 tchi
= de_getbyte(1);
519 if((tclo
==1 || tclo
==3) && (tchi
<=2)) {
528 wb
= de_getu16le(10);
529 if(w
<16 || h
<16 || w
>256 || h
>256) return 0;
530 if(wb
!= ((w
+15)/16)*2) return 0;
531 has_ext
= de_input_file_has_ext(c
, (tclo
==3)?"cur":"ico");
532 if((w
==32 || w
==64) && h
==w
&& has_ext
) return 100;
533 return has_ext
? 70 : 6;
536 void de_module_win1ico(deark
*c
, struct deark_module_info
*mi
)
539 mi
->desc
= "Windows 1.0 icon/cursor";
540 mi
->run_fn
= de_run_win1ico
;
541 mi
->identify_fn
= de_identify_win1ico
;