fnt: Improved error handling, etc.
[deark.git] / modules / os2bmp.c
blob9235cce0c792747e3a811255da301dcb9b17e2db
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);
13 enum fmtcode {
14 DE_OS2FMT_UNKNOWN = 0,
15 DE_OS2FMT_BA,
16 DE_OS2FMT_BM,
17 DE_OS2FMT_BA_BM,
18 DE_OS2FMT_IC,
19 DE_OS2FMT_BA_IC,
20 DE_OS2FMT_PT,
21 DE_OS2FMT_BA_PT,
22 DE_OS2FMT_CI,
23 DE_OS2FMT_BA_CI,
24 DE_OS2FMT_CP,
25 DE_OS2FMT_BA_CP
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
30 // final image.
31 struct srcbitmap {
32 de_bitmap *img;
33 struct de_bmpinfo bi;
34 u8 has_hotspot;
35 i64 bitssize;
36 u32 pal[256];
39 struct os2icoctx {
40 const char *fmtname; // Short name, like "CP"
41 enum fmtcode fmt;
42 struct srcbitmap *srcbmp;
43 struct srcbitmap *maskbmp;
46 static const char *get_fmt_shortname_from_code(enum fmtcode fmt)
48 switch(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";
54 case DE_OS2FMT_BA:
55 case DE_OS2FMT_BA_IC:
56 case DE_OS2FMT_BA_CI:
57 case DE_OS2FMT_BA_PT:
58 case DE_OS2FMT_BA_CP:
59 case DE_OS2FMT_BA_BM:
60 return "BA";
61 default:
62 break;
64 return "??";
67 static enum fmtcode bytes_to_fmtcode(u8 b0, u8 b1)
69 if(b0=='C' && b1=='I') {
70 return DE_OS2FMT_CI;
72 else if(b0=='C' && b1=='P') {
73 return DE_OS2FMT_CP;
75 else if(b0=='I' && b1=='C') {
76 return DE_OS2FMT_IC;
78 else if(b0=='P' && b1=='T') {
79 return DE_OS2FMT_PT;
81 else if(b0=='B' && b1=='M') {
82 return DE_OS2FMT_BM;
84 else if(b0=='B' && b1=='A') {
85 return DE_OS2FMT_BA;
87 return DE_OS2FMT_UNKNOWN;
90 static void do_free_srcbmp(deark *c, struct srcbitmap *srcbmp)
92 if(!srcbmp) return;
93 if(srcbmp->img) {
94 de_bitmap_destroy(srcbmp->img);
96 de_free(c, srcbmp);
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)
104 int retval = 0;
105 unsigned int flags;
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");
114 goto done;
117 if(srcbmp->bi.is_compressed) {
118 if(srcbmp->bi.sizeImage_field) {
119 srcbmp->bitssize = srcbmp->bi.sizeImage_field;
121 else {
122 de_err(c, "Cannot determine bits size");
123 goto done;
126 else {
127 srcbmp->bitssize = srcbmp->bi.rowspan * srcbmp->bi.height;
130 retval = 1;
131 done:
132 return retval;
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)
141 i64 pal_start;
142 int saved_indent_level;
143 int retval = 0;
145 de_dbg_indent_save(c, &saved_indent_level);
146 de_dbg(c, "%s %s bitmap header at %"I64_FMT, d->fmtname, bitmapname, pos);
147 de_dbg_indent(c, 1);
149 if(!get_bitmap_info(c, srcbmp, d->fmt, d->fmtname, pos))
150 goto done;
152 // read palette
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);
156 de_dbg_indent(c, 1);
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);
164 goto done;
167 retval = 1;
169 done:
170 de_dbg_indent_restore(c, saved_indent_level);
171 return retval;
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)
177 de_finfo *fi = NULL;
179 fi = de_finfo_create(c);
180 if(d->srcbmp) {
181 if(d->srcbmp->has_hotspot) {
182 fi->has_hotspot = 1;
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) {
189 fi->has_hotspot = 1;
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)
202 if((i+j)%2) {
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)
211 i64 i, j;
212 i64 mask_adj_height;
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++) {
219 u8 andmaskclr;
220 u8 xormaskclr;
221 u32 oldclr;
222 u32 newclr;
224 oldclr = de_bitmap_getpixel(fg, i, j);
225 newclr = oldclr;
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));
229 if(andmaskclr==0) {
230 if(is_color) {
231 // For color bitmaps, the XOR bit is not used when the AND bit is 0.
232 // Always use foreground color.
235 else {
236 if(xormaskclr==0) {
237 newclr = DE_STOCKCOLOR_BLACK;
239 else {
240 newclr = DE_STOCKCOLOR_WHITE;
244 else {
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);
250 inverse_used = 1;
254 if(newclr!=oldclr) {
255 de_bitmap_setpixel_rgb(fg, i, j, newclr);
260 if(inverse_used) {
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)
268 int retval = 0;
270 if(!srcbmp) goto done;
271 if(srcbmp->img) goto done;
273 if(mask_mode && srcbmp->bi.bitcount!=1) {
274 mask_mode = 0;
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);
281 if(mask_mode) {
282 de_convert_image_bilevel(c->infile, srcbmp->bi.bitsoffset, srcbmp->bi.rowspan,
283 srcbmp->img, 0);
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);
293 else {
294 goto done;
297 retval = 1;
298 done:
299 return retval;
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);
308 de_dbg_indent(c, 1);
310 d->maskbmp = de_malloc(c, sizeof(struct srcbitmap));
311 if(!do_bitmap_header(c, d, d->maskbmp, pos, "mask")) {
312 goto done;
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")) {
318 goto done;
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);
326 done:
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")) {
336 goto done;
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);
347 done:
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));
356 d->fmt = fmt;
357 d->fmtname = get_fmt_shortname_from_code(d->fmt);
359 switch(fmt) {
360 case DE_OS2FMT_IC:
361 case DE_OS2FMT_PT:
362 do_decode_IC_or_PT(c, d, 0);
363 break;
364 case DE_OS2FMT_CI:
365 case DE_OS2FMT_CP:
366 do_decode_CI_or_CP(c, d, 0);
367 break;
368 default:
369 break;
372 if(d) {
373 do_free_srcbmp(c, d->srcbmp);
374 do_free_srcbmp(c, d->maskbmp);
375 de_free(c, d);
379 static void do_extract_CI_or_CP(deark *c, enum fmtcode fmt, const char *fmtname, i64 pos)
381 struct de_bmpinfo *bi = NULL;
382 i64 i;
383 dbuf *f = NULL;
384 i64 hdrpos[2];
385 i64 hdrsize[2];
386 i64 oldbitsoffs[2];
387 i64 newbitsoffs[2];
388 i64 bitssize[2];
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);
393 de_dbg_indent(c, 1);
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);
400 else {
401 f = dbuf_create_output_file(c, "os2.ico", NULL, 0);
404 for(i=0; i<2; i++) {
405 de_dbg(c, "bitmap at %d", (int)pos);
406 de_dbg_indent(c, 1);
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");
412 goto done;
414 if(bi->compression_field!=0) {
415 de_err(c, "Unsupported compression type (%d)", (int)bi->compression_field);
416 goto done;
419 de_dbg(c, "bits size: %d", (int)bi->foreground_size);
421 hdrpos[i] = pos;
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.
435 for(i=0; i<2; i++) {
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.
444 for(i=0; i<2; i++) {
445 dbuf_copy(c->infile, oldbitsoffs[i], bitssize[i], f);
448 done:
449 de_dbg_indent_restore(c, saved_indent_level);
450 dbuf_close(f);
451 de_free(c, bi);
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;
462 dbuf *f = NULL;
464 de_dbg(c, "%s image at %d", fmtname, (int)pos);
465 de_dbg_indent(c, 1);
467 srcbmp = de_malloc(c, sizeof(struct srcbitmap));
469 if(!get_bitmap_info(c, srcbmp, fmt, fmtname, pos))
470 goto done;
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);
483 // Copy the bitmap
484 if(srcbmp->bi.bitsoffset+srcbmp->bitssize > c->infile->len) goto done;
485 dbuf_copy(c->infile, srcbmp->bi.bitsoffset, srcbmp->bitssize, f);
487 done:
488 de_dbg_indent(c, -1);
489 dbuf_close(f);
490 do_free_srcbmp(c, srcbmp);
493 static void do_BA_segment(deark *c, i64 pos, i64 *pnextoffset)
495 u8 b0, b1;
496 enum fmtcode fmt;
497 const char *fmtname;
498 int saved_indent_level;
500 de_dbg_indent_save(c, &saved_indent_level);
501 de_dbg(c,"BA segment at %d", (int)pos);
502 de_dbg_indent(c, 1);
504 *pnextoffset = 0;
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");
510 goto done;
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);
522 switch(fmt) {
523 case DE_OS2FMT_CI:
524 case DE_OS2FMT_CP:
525 do_extract_CI_or_CP(c, fmt, fmtname, pos+14);
526 break;
527 case DE_OS2FMT_BM:
528 do_extract_one_image(c, pos+14, fmt, fmtname, "bmp");
529 break;
530 case DE_OS2FMT_IC:
531 do_extract_one_image(c, pos+14, fmt, fmtname, "os2.ico");
532 break;
533 case DE_OS2FMT_PT:
534 do_extract_one_image(c, pos+14, fmt, fmtname, "ptr");
535 break;
536 default:
537 de_err(c, "Not BM/IC/PT/CI/CP format. Not supported.");
538 goto done;
541 done:
542 de_dbg_indent_restore(c, saved_indent_level);
545 static void do_BA_file(deark *c)
547 i64 pos;
548 i64 nextoffset = 0;
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.
553 pos = 0;
554 while(1) {
555 do_BA_segment(c, pos, &nextoffset);
556 if(nextoffset==0) break;
557 if(nextoffset<=pos) {
558 de_err(c, "Invalid BA segment offset");
559 break;
561 pos = nextoffset;
565 static enum fmtcode de_identify_os2bmp_internal(deark *c)
567 enum fmtcode fmt;
568 u8 b[16];
570 de_read(b, 0, 16);
572 fmt = bytes_to_fmtcode(b[0], b[1]);
573 if(fmt==DE_OS2FMT_BA) {
574 enum fmtcode ba_fmt;
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;
585 return DE_OS2FMT_BA;
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)
596 switch(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";
610 default:
611 break;
613 return NULL;
616 static void de_run_os2bmp(deark *c, de_module_params *mparams)
618 enum fmtcode fmt;
619 const char *name;
621 fmt = de_identify_os2bmp_internal(c);
623 name = get_fmt_longname_from_code(fmt);
624 if(name) {
625 de_declare_fmt(c, name);
628 switch(fmt) {
629 case DE_OS2FMT_IC:
630 case DE_OS2FMT_PT:
631 case DE_OS2FMT_CI:
632 case DE_OS2FMT_CP:
633 do_decode_icon_or_cursor(c, fmt);
634 break;
635 case DE_OS2FMT_BA:
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:
641 do_BA_file(c);
642 break;
643 default:
644 de_err(c, "Format not supported");
648 static int de_identify_os2bmp(deark *c)
650 enum fmtcode fmt;
652 // TODO: We could do a better job of identifying these formats.
653 fmt = de_identify_os2bmp_internal(c);
654 switch(fmt) {
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:
660 return 90;
661 case DE_OS2FMT_CI:
662 case DE_OS2FMT_CP: // Note that Corel Photo-Paint is similar
663 case DE_OS2FMT_PT:
664 return 20;
665 case DE_OS2FMT_BA:
666 case DE_OS2FMT_IC:
667 return 10;
668 default:
669 break;
671 return 0;
674 void de_module_os2bmp(deark *c, struct deark_module_info *mi)
676 mi->id = "os2bmp";
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;