Refactoring to use de_read_simple_palette
[deark.git] / modules / ico.c
blob70ed3ac58c534073fea4fddd322db175561d6c7a
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);
12 struct page_ctx {
13 i64 img_num;
14 i64 data_size;
15 i64 data_offset;
16 int hotspot_x, hotspot_y; // Valid if lctx::is_cur
19 typedef struct localctx_struct {
20 int is_cur;
21 int extract_unused_masks;
22 } lctx;
24 static void do_extract_png(deark *c, lctx *d, i64 pos, i64 len)
26 char ext[64];
27 i64 w, h;
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)
42 if((i+j)%2) {
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 "
51 "fully supported.");
54 static void do_image_data(deark *c, lctx *d, struct page_ctx *pg)
56 struct de_bmpinfo bi;
57 i64 fg_start, bg_start;
58 i64 i, j;
59 u32 pal[256];
60 i64 p;
61 de_bitmap *img = NULL;
62 de_bitmap *mask_img = NULL;
63 de_finfo *fi = NULL;
64 u8 x;
65 u8 cr=0, cg=0, cb=0, ca=0;
66 int has_inv_bkgd = 0;
67 int use_mask;
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");
78 goto done;
81 if(bi.file_format == DE_BMPINFO_FMT_PNG) {
82 do_extract_png(c, d, pos1, len);
83 goto done;
86 switch(bi.bitcount) {
87 case 1: case 2: case 4: case 8: case 24: case 32:
88 break;
89 case 16:
90 de_err(c, "(image #%d) Unsupported bit count (%d)", (int)pg->img_num, (int)bi.bitcount);
91 goto done;
92 default:
93 de_err(c, "(image #%d) Invalid bit count (%d)", (int)pg->img_num, (int)bi.bitcount);
94 goto done;
97 if(bi.compression_field!=0) {
98 // TODO: Support BITFIELDS
99 de_err(c, "Compression / BITFIELDS not supported");
100 goto done;
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
106 // separately).
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.
110 use_mask = 0;
111 has_alpha_channel = 1;
113 else {
114 use_mask = 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);
125 // Read palette
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.
147 // Issues:
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++) {
158 ca = 0xff;
160 if(bi.bitcount<=8) {
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) {
168 // // TODO
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) {
187 u8 maskclr;
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)) {
197 u32 newclr;
199 has_inv_bkgd = 1;
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));
212 if(has_inv_bkgd) {
213 warn_inv_bkgd(c);
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);
222 if(d->is_cur) {
223 fi->has_hotspot = 1;
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);
238 done:
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);
252 de_dbg_indent(c, 1);
253 if(d->is_cur) {
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);
264 de_free(c, pg);
265 de_dbg_indent(c, -1);
268 static void de_run_ico(deark *c, de_module_params *mparams)
270 lctx *d = NULL;
271 i64 x;
272 i64 num_images;
273 i64 i;
275 d = de_malloc(c, sizeof(lctx));
276 d->extract_unused_masks = (c->extract_level>=2);
278 x = de_getu16le(2);
279 if(x==1) {
280 d->is_cur=0;
281 de_declare_fmt(c, "Windows Icon");
283 else if(x==2) {
284 d->is_cur=1;
285 de_declare_fmt(c, "Windows Cursor");
287 else {
288 de_dbg(c, "Not an ICO/CUR file");
289 goto done;
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)) {
295 goto done;
298 for(i=0; i<num_images; i++) {
299 do_image_dir_entry(c, d, i, 6+16*i);
302 done:
303 de_free(c, d);
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)
310 i64 numicons;
311 i64 i;
312 i64 size, offset;
313 u8 buf[4];
315 de_read(buf, 0, 4);
316 if(de_memcmp(buf, "\x00\x00\x01\x00", 4) &&
317 de_memcmp(buf, "\x00\x00\x02\x00", 4))
319 return 0;
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;
336 return 1;
339 static int de_identify_ico(deark *c)
341 if(is_windows_ico_or_cur(c)) {
342 return 80;
344 return 0;
347 void de_module_ico(deark *c, struct deark_module_info *mi)
349 mi->id = "ico";
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;
359 int is_cur;
360 const char *type_name;
361 i64 bytes_consumed;
362 } win1ctx;
364 static int decode_win1_icon(deark *c, win1ctx *d, i64 pos1)
366 de_bitmap *mask = NULL;
367 de_bitmap *img = NULL;
368 de_finfo *fi = NULL;
369 i64 npwidth, h;
370 i64 pdwidth;
371 i64 rowspan;
372 i64 i, j;
373 i64 pos = pos1;
374 int has_inv_bkgd = 0;
375 int hotspot_x = 0;
376 int hotspot_y = 0;
377 int retval = 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);
384 de_dbg_indent(c, 1);
386 if(d->is_cur) {
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);
391 pos += 4;
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);
400 pdwidth = rowspan*8;
402 if(d->is_cur) {
403 unsigned int csColor;
404 csColor = (unsigned int)de_getu16le(pos);
405 de_dbg(c, "csColor: 0x%04x", csColor);
407 pos += 2;
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);
413 pos += rowspan*h;
414 de_dbg(c, "foreground at %"I64_FMT, pos);
415 de_convert_image_bilevel(c->infile, pos, rowspan, img, 0);
416 pos += rowspan*h;
418 // This whole loop does nothing, except handle inverse-background-color
419 // pixels. But we have to do something, because such pixels are not
420 // uncommon.
421 for(j=0; j<h; j++) {
422 for(i=0; i<pdwidth; i++) {
423 u8 fgclr, maskclr;
424 u32 newclr;
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));
434 if(i<npwidth) {
435 has_inv_bkgd = 1;
439 if(has_inv_bkgd) {
440 warn_inv_bkgd(c);
443 de_bitmap_apply_mask(img, mask, DE_BITMAPFLAG_WHITEISTRNS);
445 fi = de_finfo_create(c);
447 if(d->is_cur) {
448 fi->has_hotspot = 1;
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;
455 retval = 1;
457 done:
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);
462 return retval;
465 static void de_run_win1ico(deark *c, de_module_params *mparams)
467 win1ctx *d = NULL;
468 i64 pos = 0;
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) {
474 d->is_cur = 1;
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";
480 else {
481 de_err(c, "Not a Windows 1.0 icon/cursor");
482 goto done;
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;
493 done:
494 de_free(c, d);
497 static int de_identify_win1ico(deark *c)
499 u8 tclo, tchi;
500 i64 w, h, wb;
501 int has_ext;
503 tclo = de_getbyte(0);
504 tchi = de_getbyte(1);
505 if((tclo==1 || tclo==3) && (tchi<=2)) {
508 else {
509 return 0;
512 w = de_getu16le(6);
513 h = de_getu16le(8);
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)
524 mi->id = "win1ico";
525 mi->desc = "Windows 1.0 icon/cursor";
526 mi->run_fn = de_run_win1ico;
527 mi->identify_fn = de_identify_win1ico;