1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // Convert OS/2 Icon and OS/2 Pointer format.
6 // Extract files in a BA (Bitmap Array) container.
8 #include <deark-config.h>
9 #include <deark-private.h>
10 #include <deark-fmtutil.h>
11 DE_DECLARE_MODULE(de_module_os2bmp
);
14 DE_OS2FMT_UNKNOWN
= 0,
28 // This struct represents a raw source bitmap (it uses BMP format).
29 // Two of them (the foreground and the mask) will be combined to make the
40 const char *fmtname
; // Short name, like "CP"
42 struct srcbitmap
*srcbmp
;
43 struct srcbitmap
*maskbmp
;
46 static const char *get_fmt_shortname_from_code(enum fmtcode fmt
)
49 case DE_OS2FMT_IC
: return "IC";
50 case DE_OS2FMT_PT
: return "PT";
51 case DE_OS2FMT_CI
: return "CI";
52 case DE_OS2FMT_CP
: return "CP";
53 case DE_OS2FMT_BM
: return "BM";
67 static enum fmtcode
bytes_to_fmtcode(u8 b0
, u8 b1
)
69 if(b0
=='C' && b1
=='I') {
72 else if(b0
=='C' && b1
=='P') {
75 else if(b0
=='I' && b1
=='C') {
78 else if(b0
=='P' && b1
=='T') {
81 else if(b0
=='B' && b1
=='M') {
84 else if(b0
=='B' && b1
=='A') {
87 return DE_OS2FMT_UNKNOWN
;
90 static void do_free_srcbmp(deark
*c
, struct srcbitmap
*srcbmp
)
94 de_bitmap_destroy(srcbmp
->img
);
99 // Populates srcbmp with information about a bitmap.
100 // Does not read the palette.
101 static int get_bitmap_info(deark
*c
, struct srcbitmap
*srcbmp
, enum fmtcode fmt
,
102 const char *fmtname
, i64 pos
)
107 flags
= DE_BMPINFO_HAS_FILEHEADER
;
108 if(fmt
==DE_OS2FMT_CP
|| fmt
==DE_OS2FMT_PT
) {
109 srcbmp
->has_hotspot
= 1;
110 flags
|= DE_BMPINFO_HAS_HOTSPOT
;
112 if(!fmtutil_get_bmpinfo(c
, c
->infile
, &srcbmp
->bi
, pos
, c
->infile
->len
- pos
, flags
)) {
113 de_err(c
, "Invalid or unsupported bitmap");
117 if(srcbmp
->bi
.is_compressed
) {
118 if(srcbmp
->bi
.sizeImage_field
) {
119 srcbmp
->bitssize
= srcbmp
->bi
.sizeImage_field
;
122 de_err(c
, "Cannot determine bits size");
127 srcbmp
->bitssize
= srcbmp
->bi
.rowspan
* srcbmp
->bi
.height
;
135 // Read the header and palette.
136 // Caller allocates srcbmp.
137 // On failure, prints an error, and returns 0.
138 static int do_bitmap_header(deark
*c
, struct os2icoctx
*d
, struct srcbitmap
*srcbmp
,
139 i64 pos
, const char *bitmapname
)
142 int saved_indent_level
;
145 de_dbg_indent_save(c
, &saved_indent_level
);
146 de_dbg(c
, "%s %s bitmap header at %"I64_FMT
, d
->fmtname
, bitmapname
, pos
);
149 if(!get_bitmap_info(c
, srcbmp
, d
->fmt
, d
->fmtname
, pos
))
153 if (srcbmp
->bi
.pal_entries
> 0) {
154 pal_start
= pos
+14+srcbmp
->bi
.infohdrsize
;
155 de_dbg(c
, "palette at %d", (int)pal_start
);
157 de_read_palette_rgb(c
->infile
, pal_start
, srcbmp
->bi
.pal_entries
, srcbmp
->bi
.bytes_per_pal_entry
,
158 srcbmp
->pal
, 256, DE_GETRGBFLAG_BGR
);
159 de_dbg_indent(c
, -1);
162 if(srcbmp
->bi
.size_of_headers_and_pal
<26) {
163 de_err(c
, "Bad %s image", d
->fmtname
);
170 de_dbg_indent_restore(c
, saved_indent_level
);
174 // Uses d->srcbmp, d->maskbmp (which can be NULL).
175 static void do_write_final_image(deark
*c
, struct os2icoctx
*d
, de_bitmap
*img
)
179 fi
= de_finfo_create(c
);
181 if(d
->srcbmp
->has_hotspot
) {
183 fi
->hotspot_x
= d
->srcbmp
->bi
.hotspot_x
;
184 fi
->hotspot_y
= (int)img
->height
- 1 - d
->srcbmp
->bi
.hotspot_y
;
187 else if(d
->maskbmp
) {
188 if(d
->maskbmp
->has_hotspot
) {
190 fi
->hotspot_x
= d
->maskbmp
->bi
.hotspot_x
;
191 fi
->hotspot_y
= (int)img
->height
- 1 - d
->maskbmp
->bi
.hotspot_y
;
195 de_bitmap_write_to_file_finfo(img
, fi
, DE_CREATEFLAG_OPT_IMAGE
| DE_CREATEFLAG_FLIP_IMAGE
);
197 de_finfo_destroy(c
, fi
);
200 static u32
get_inv_bkgd_replacement_clr(i64 i
, i64 j
)
203 return DE_MAKE_RGBA(255,0,128,128);
205 return DE_MAKE_RGBA(128,0,255,128);
208 // Applies mask to fg. Modifies fg.
209 static void do_apply_os2bmp_mask(deark
*c
, de_bitmap
*fg
, de_bitmap
*mask
, int is_color
)
213 int inverse_used
= 0;
215 mask_adj_height
= mask
->height
/ 2;
217 for(j
=0; j
<fg
->height
&& j
<mask_adj_height
; j
++) {
218 for(i
=0; i
<fg
->width
&& i
<mask
->width
; i
++) {
224 oldclr
= de_bitmap_getpixel(fg
, i
, j
);
226 xormaskclr
= DE_COLOR_K(de_bitmap_getpixel(mask
, i
, j
));
227 andmaskclr
= DE_COLOR_K(de_bitmap_getpixel(mask
, i
, mask_adj_height
+j
));
231 // For color bitmaps, the XOR bit is not used when the AND bit is 0.
232 // Always use foreground color.
237 newclr
= DE_STOCKCOLOR_BLACK
;
240 newclr
= DE_STOCKCOLOR_WHITE
;
245 if(xormaskclr
==0) { // transparent
246 newclr
= DE_SET_ALPHA(oldclr
, 0);
248 else { // inverse background
249 newclr
= get_inv_bkgd_replacement_clr(i
, j
);
255 de_bitmap_setpixel_rgb(fg
, i
, j
, newclr
);
261 de_warn(c
, "This image contains inverse background pixels, which are not fully supported.");
265 // Allocates srcbmp->img.
266 static int do_read_bitmap(deark
*c
, struct srcbitmap
*srcbmp
, int mask_mode
, const char *bitmapname
)
270 if(!srcbmp
) goto done
;
271 if(srcbmp
->img
) goto done
;
273 if(mask_mode
&& srcbmp
->bi
.bitcount
!=1) {
277 srcbmp
->img
= de_bitmap_create(c
, srcbmp
->bi
.width
, srcbmp
->bi
.height
, mask_mode
?1:4);
279 de_dbg(c
, "%s pixel data at %"I64_FMT
, bitmapname
, srcbmp
->bi
.bitsoffset
);
282 de_convert_image_bilevel(c
->infile
, srcbmp
->bi
.bitsoffset
, srcbmp
->bi
.rowspan
,
285 else if(srcbmp
->bi
.bitcount
<=8) {
286 de_convert_image_paletted(c
->infile
, srcbmp
->bi
.bitsoffset
, srcbmp
->bi
.bitcount
,
287 srcbmp
->bi
.rowspan
, srcbmp
->pal
, srcbmp
->img
, 0);
289 else if(srcbmp
->bi
.bitcount
==24) {
290 de_convert_image_rgb(c
->infile
, srcbmp
->bi
.bitsoffset
, srcbmp
->bi
.rowspan
, 3,
291 srcbmp
->img
, DE_GETRGBFLAG_BGR
);
302 static void do_decode_CI_or_CP(deark
*c
, struct os2icoctx
*d
, i64 pos
)
304 int saved_indent_level
;
306 de_dbg_indent_save(c
, &saved_indent_level
);
307 de_dbg(c
, "%s image at %"I64_FMT
, d
->fmtname
, pos
);
310 d
->maskbmp
= de_malloc(c
, sizeof(struct srcbitmap
));
311 if(!do_bitmap_header(c
, d
, d
->maskbmp
, pos
, "mask")) {
314 pos
+= d
->maskbmp
->bi
.size_of_headers_and_pal
;
316 d
->srcbmp
= de_malloc(c
, sizeof(struct srcbitmap
));
317 if(!do_bitmap_header(c
, d
, d
->srcbmp
, pos
, "foreground")) {
321 if(!do_read_bitmap(c
, d
->maskbmp
, 1, "mask")) goto done
;
322 if(!do_read_bitmap(c
, d
->srcbmp
, 0, "foreground")) goto done
;
323 do_apply_os2bmp_mask(c
, d
->srcbmp
->img
, d
->maskbmp
->img
, 1);
324 do_write_final_image(c
, d
, d
->srcbmp
->img
);
327 de_dbg_indent_restore(c
, saved_indent_level
);
330 static void do_decode_IC_or_PT(deark
*c
, struct os2icoctx
*d
, i64 pos
)
332 de_bitmap
*img_main
= NULL
;
334 d
->maskbmp
= de_malloc(c
, sizeof(struct srcbitmap
));
335 if(!do_bitmap_header(c
, d
, d
->maskbmp
, pos
, "mask-like")) {
339 if(!do_read_bitmap(c
, d
->maskbmp
, 1, "mask-like")) goto done
;
341 // There is no "main" image, so manufacture one.
342 img_main
= de_bitmap_create(c
, d
->maskbmp
->bi
.width
, d
->maskbmp
->bi
.height
/2, 4);
344 do_apply_os2bmp_mask(c
, img_main
, d
->maskbmp
->img
, 0);
345 do_write_final_image(c
, d
, img_main
);
348 de_bitmap_destroy(img_main
);
351 static void do_decode_icon_or_cursor(deark
*c
, enum fmtcode fmt
)
353 struct os2icoctx
*d
= NULL
;
355 d
= de_malloc(c
, sizeof(struct os2icoctx
));
357 d
->fmtname
= get_fmt_shortname_from_code(d
->fmt
);
362 do_decode_IC_or_PT(c
, d
, 0);
366 do_decode_CI_or_CP(c
, d
, 0);
373 do_free_srcbmp(c
, d
->srcbmp
);
374 do_free_srcbmp(c
, d
->maskbmp
);
379 static void do_extract_CI_or_CP(deark
*c
, enum fmtcode fmt
, const char *fmtname
, i64 pos
)
381 struct de_bmpinfo
*bi
= NULL
;
389 int saved_indent_level
;
391 de_dbg_indent_save(c
, &saved_indent_level
);
392 de_dbg(c
, "%s image at %d", fmtname
, (int)pos
);
395 bi
= de_malloc(c
, sizeof(struct de_bmpinfo
));
397 if(fmt
==DE_OS2FMT_CP
) {
398 f
= dbuf_create_output_file(c
, "ptr", NULL
, 0);
401 f
= dbuf_create_output_file(c
, "os2.ico", NULL
, 0);
405 de_dbg(c
, "bitmap at %d", (int)pos
);
408 if(!fmtutil_get_bmpinfo(c
, c
->infile
, bi
, pos
, c
->infile
->len
- pos
,
409 DE_BMPINFO_HAS_FILEHEADER
))
411 de_err(c
, "Unsupported image type");
414 if(bi
->compression_field
!=0) {
415 de_err(c
, "Unsupported compression type (%d)", (int)bi
->compression_field
);
419 de_dbg(c
, "bits size: %d", (int)bi
->foreground_size
);
422 hdrsize
[i
] = bi
->size_of_headers_and_pal
;
423 oldbitsoffs
[i
] = bi
->bitsoffset
;
424 bitssize
[i
] = bi
->foreground_size
;
426 pos
+= bi
->size_of_headers_and_pal
;
428 de_dbg_indent(c
, -1);
431 newbitsoffs
[0] = hdrsize
[0] + hdrsize
[1];
432 newbitsoffs
[1] = newbitsoffs
[0] + bitssize
[0];
434 // Write all the headers.
436 // Copy the first 10 bytes of the fileheader.
437 dbuf_copy(c
->infile
, hdrpos
[i
], 10, f
);
438 // Update the bits offset.
439 dbuf_writeu32le(f
, newbitsoffs
[i
]);
440 // Copy the rest of the headers (+palette).
441 dbuf_copy(c
->infile
, hdrpos
[i
]+14, hdrsize
[i
]-14, f
);
443 // Write all the bitmaps.
445 dbuf_copy(c
->infile
, oldbitsoffs
[i
], bitssize
[i
], f
);
449 de_dbg_indent_restore(c
, saved_indent_level
);
454 // A BM/IC/PT image inside a BA container.
455 // Don't convert the image to another format; just extract it as-is in
456 // BMP/ICO/PTR format. Unfortunately, this requires collecting the various pieces
457 // of it, and adjusting pointers.
458 static void do_extract_one_image(deark
*c
, i64 pos
, enum fmtcode fmt
,
459 const char *fmtname
, const char *ext
)
461 struct srcbitmap
*srcbmp
= NULL
;
464 de_dbg(c
, "%s image at %d", fmtname
, (int)pos
);
467 srcbmp
= de_malloc(c
, sizeof(struct srcbitmap
));
469 if(!get_bitmap_info(c
, srcbmp
, fmt
, fmtname
, pos
))
472 f
= dbuf_create_output_file(c
, ext
, NULL
, 0);
474 // First 10 bytes of the FILEHEADER can be copied unchanged.
475 dbuf_copy(c
->infile
, pos
, 10, f
);
477 // The "bits offset" is probably the only thing we need to adjust.
478 dbuf_writeu32le(f
, srcbmp
->bi
.size_of_headers_and_pal
);
480 // Copy the infoheader & palette
481 dbuf_copy(c
->infile
, pos
+14, srcbmp
->bi
.size_of_headers_and_pal
-14, f
);
484 if(srcbmp
->bi
.bitsoffset
+srcbmp
->bitssize
> c
->infile
->len
) goto done
;
485 dbuf_copy(c
->infile
, srcbmp
->bi
.bitsoffset
, srcbmp
->bitssize
, f
);
488 de_dbg_indent(c
, -1);
490 do_free_srcbmp(c
, srcbmp
);
493 static void do_BA_segment(deark
*c
, i64 pos
, i64
*pnextoffset
)
498 int saved_indent_level
;
500 de_dbg_indent_save(c
, &saved_indent_level
);
501 de_dbg(c
,"BA segment at %d", (int)pos
);
506 b0
= de_getbyte(pos
+0);
507 b1
= de_getbyte(pos
+1);
508 if(b0
!='B' || b1
!='A') {
509 de_err(c
, "Not a BA segment");
513 *pnextoffset
= de_getu32le(pos
+6);
514 de_dbg(c
, "offset of next segment: %d", (int)*pnextoffset
);
516 // Peek at the next two bytes
517 b0
= de_getbyte(pos
+14+0);
518 b1
= de_getbyte(pos
+14+1);
519 fmt
= bytes_to_fmtcode(b0
, b1
);
520 fmtname
= get_fmt_shortname_from_code(fmt
);
525 do_extract_CI_or_CP(c
, fmt
, fmtname
, pos
+14);
528 do_extract_one_image(c
, pos
+14, fmt
, fmtname
, "bmp");
531 do_extract_one_image(c
, pos
+14, fmt
, fmtname
, "os2.ico");
534 do_extract_one_image(c
, pos
+14, fmt
, fmtname
, "ptr");
537 de_err(c
, "Not BM/IC/PT/CI/CP format. Not supported.");
542 de_dbg_indent_restore(c
, saved_indent_level
);
545 static void do_BA_file(deark
*c
)
550 // The file contains a linked list of BA segments. There's nothing special
551 // about the first segment, but it can be used to identify the file type.
555 do_BA_segment(c
, pos
, &nextoffset
);
556 if(nextoffset
==0) break;
557 if(nextoffset
<=pos
) {
558 de_err(c
, "Invalid BA segment offset");
565 static enum fmtcode
de_identify_os2bmp_internal(deark
*c
)
572 fmt
= bytes_to_fmtcode(b
[0], b
[1]);
573 if(fmt
==DE_OS2FMT_BA
) {
576 // A Bitmap Array file can contain a mixture of different image types,
577 // but for the purposes of identifying the file type, we only look at
578 // the first one. This is not ideal, but it really doesn't matter.
579 ba_fmt
= bytes_to_fmtcode(b
[14], b
[15]);
580 if(ba_fmt
==DE_OS2FMT_IC
) return DE_OS2FMT_BA_IC
;
581 if(ba_fmt
==DE_OS2FMT_PT
) return DE_OS2FMT_BA_PT
;
582 if(ba_fmt
==DE_OS2FMT_CI
) return DE_OS2FMT_BA_CI
;
583 if(ba_fmt
==DE_OS2FMT_CP
) return DE_OS2FMT_BA_CP
;
584 if(ba_fmt
==DE_OS2FMT_BM
) return DE_OS2FMT_BA_BM
;
587 if(fmt
==DE_OS2FMT_IC
) return DE_OS2FMT_IC
;
588 if(fmt
==DE_OS2FMT_PT
) return DE_OS2FMT_PT
;
589 if(fmt
==DE_OS2FMT_CI
) return DE_OS2FMT_CI
;
590 if(fmt
==DE_OS2FMT_CP
) return DE_OS2FMT_CP
;
591 return DE_OS2FMT_UNKNOWN
;
594 static const char* get_fmt_longname_from_code(enum fmtcode fmt
)
597 case DE_OS2FMT_IC
: return "OS/2 Icon";
598 case DE_OS2FMT_PT
: return "OS/2 Pointer";
599 case DE_OS2FMT_CI
: return "OS/2 Color Icon";
600 case DE_OS2FMT_CP
: return "OS/2 Color Pointer";
601 case DE_OS2FMT_BA
: return "OS/2 Bitmap Array";
602 case DE_OS2FMT_BA_IC
:
603 case DE_OS2FMT_BA_CI
:
604 return "OS/2 Bitmap Array of Icons";
605 case DE_OS2FMT_BA_PT
:
606 case DE_OS2FMT_BA_CP
:
607 return "OS/2 Bitmap Array of Pointers";
608 case DE_OS2FMT_BA_BM
:
609 return "OS/2 Bitmap Array of Bitmaps";
616 static void de_run_os2bmp(deark
*c
, de_module_params
*mparams
)
621 fmt
= de_identify_os2bmp_internal(c
);
623 name
= get_fmt_longname_from_code(fmt
);
625 de_declare_fmt(c
, name
);
633 do_decode_icon_or_cursor(c
, fmt
);
636 case DE_OS2FMT_BA_IC
:
637 case DE_OS2FMT_BA_PT
:
638 case DE_OS2FMT_BA_CI
:
639 case DE_OS2FMT_BA_CP
:
640 case DE_OS2FMT_BA_BM
:
644 de_err(c
, "Format not supported");
648 static int de_identify_os2bmp(deark
*c
)
652 // TODO: We could do a better job of identifying these formats.
653 fmt
= de_identify_os2bmp_internal(c
);
655 case DE_OS2FMT_BA_IC
:
656 case DE_OS2FMT_BA_PT
:
657 case DE_OS2FMT_BA_CI
:
658 case DE_OS2FMT_BA_CP
:
659 case DE_OS2FMT_BA_BM
:
662 case DE_OS2FMT_CP
: // Note that Corel Photo-Paint is similar
674 void de_module_os2bmp(deark
*c
, struct deark_module_info
*mi
)
677 mi
->desc
= "OS/2 icon (.ICO), cursor (.PTR), bitmap array";
678 mi
->run_fn
= de_run_os2bmp
;
679 mi
->identify_fn
= de_identify_os2bmp
;