bmp: Rewrote the RLE decompressor
[deark.git] / modules / storyboard.c
bloba3ad85689303a32cb3244d70e5ef7409df208221
1 // This file is part of Deark.
2 // Copyright (C) 2022 Jason Summers
3 // See the file COPYING for terms of use.
5 // IBM Storyboard .PIC/.CAP
6 // - Old "EP_CAP" format
7 // - Some newer formats may be partially supported.
9 #include <deark-private.h>
10 DE_DECLARE_MODULE(de_module_storyboard);
12 struct storyboard_ctx {
13 de_encoding input_encoding;
14 u8 mode;
15 u8 is_text;
16 u8 need_errmsg;
17 i64 bpp; // bits per pixel
18 i64 width, height;
19 i64 rowspan;
20 i64 width_in_chars, height_in_chars;
21 i64 max_unc_size;
22 i64 attribs_pos;
23 i64 img_endpos;
25 i64 sm_width, sm_height; // target screen mode
26 i64 nstrips_per_plane;
27 i64 planespan;
29 i64 pixels_start;
30 u8 cmpr_meth; // 1 to enable "long" codes
31 de_color pal[256];
32 de_color paltmp[256];
35 static int decompress_oldfmt(deark *c, struct storyboard_ctx *d, i64 pos1,
36 dbuf *outf)
38 i64 pos = pos1;
39 i64 nbytes_written = 0;
40 i64 img_seg_size;
41 int found_attribs = 0;
42 int element_count = 0;
43 int retval = 0;
44 int saved_indent_level;
46 de_dbg_indent_save(c, &saved_indent_level);
48 de_dbg(c, "compressed data segment at %"I64_FMT, pos1);
49 de_dbg_indent(c, 1);
51 img_seg_size = de_getu16le_p(&pos);
52 d->img_endpos = pos + img_seg_size;
53 de_dbg(c, "segment size: %"I64_FMT" (ends at %"I64_FMT")", img_seg_size, d->img_endpos);
54 if(img_seg_size<2) {
55 d->need_errmsg = 1;
56 goto done;
59 while(1) {
60 UI n;
61 i64 count;
63 if(nbytes_written >= d->max_unc_size) break;
64 if(pos >= c->infile->len) break;
66 n = (UI)de_getu16le_p(&pos);
67 if(n == 0x0000) { // Seems to be a special stop/separator code
68 element_count++;
69 if(element_count==1 && d->is_text) {
70 // End of foreground, start of attributes.
71 // Kind of a hack, but it's easiest just to decompress everything
72 // in one go.
73 dbuf_flush(outf);
74 d->attribs_pos = outf->len;
75 found_attribs = 1;
77 else {
78 break;
81 else if(n < 0x8000) {
82 count = (i64)n;
83 dbuf_copy(c->infile, pos, count, outf);
84 pos += count;
85 nbytes_written += count;
87 else {
88 u8 v;
90 count = (i64)(n-0x8000);
91 v = de_getbyte_p(&pos);
92 dbuf_write_run(outf, v, count);
93 nbytes_written += count;
97 if(d->is_text && !found_attribs) {
98 d->need_errmsg = 1;
100 else {
101 retval = 1;
103 de_dbg(c, "decompressed to %"I64_FMT" bytes", nbytes_written);
105 done:
106 de_dbg_indent_restore(c, saved_indent_level);
107 return retval;
110 static void do_oldfmt_text_main(deark *c, struct storyboard_ctx *d, dbuf *unc_data,
111 struct de_char_context *charctx)
113 i64 i, j;
114 u8 ccode, acode;
115 u8 fgcol, bgcol;
116 struct de_char_screen *screen;
117 struct de_encconv_state es;
119 charctx->nscreens = 1;
120 charctx->screens = de_mallocarray(c, charctx->nscreens, sizeof(struct de_char_screen*));
121 charctx->screens[0] = de_malloc(c, sizeof(struct de_char_screen));
122 screen = charctx->screens[0];
123 screen->width = d->width_in_chars;
124 screen->height = d->height_in_chars;
125 screen->cell_rows = de_mallocarray(c, d->height_in_chars, sizeof(struct de_char_cell*));
126 de_encconv_init(&es, d->input_encoding);
128 for(j=0; j<d->height_in_chars; j++) {
129 screen->cell_rows[j] = de_mallocarray(c, d->width_in_chars, sizeof(struct de_char_cell));
131 for(i=0; i<d->width_in_chars; i++) {
132 ccode = dbuf_getbyte(unc_data, j*d->width_in_chars + i);
133 acode = dbuf_getbyte(unc_data, d->attribs_pos + j*d->width_in_chars + i);
135 fgcol = (acode & 0x0f);
136 bgcol = acode >> 4;
138 screen->cell_rows[j][i].fgcol = (u32)fgcol;
139 screen->cell_rows[j][i].bgcol = (u32)bgcol;
140 screen->cell_rows[j][i].codepoint = (i32)ccode;
141 screen->cell_rows[j][i].codepoint_unicode = de_char_to_unicode_ex((i32)ccode, &es);
145 de_char_output_to_file(c, charctx);
148 static void do_oldfmt_text(deark *c, struct storyboard_ctx *d, i64 pos)
150 dbuf *unc_data = NULL;
151 struct de_char_context *charctx = NULL;
153 if(d->mode != 3) goto done;
154 d->max_unc_size = 65536;
156 unc_data = dbuf_create_membuf(c, 4000, 0);
157 dbuf_enable_wbuffer(unc_data);
159 if(!decompress_oldfmt(c, d, pos, unc_data)) goto done;
160 dbuf_flush(unc_data);
162 // Not sure how to figure out the dimensions. The files in the distribution
163 // seem to contain this information, but the ones I capture myself contain
164 // nonsense. (Maybe mode=3 implies 80x25, so we could just assume that.)
165 if(d->rowspan>=80 && d->rowspan<=400 && d->height>=20 && d->height<=100 &&
166 (d->rowspan*d->height == unc_data->len))
168 d->width_in_chars = d->rowspan/2;
169 d->height_in_chars = d->height;
171 else {
172 d->width_in_chars = 80;
173 d->height_in_chars = 25;
176 charctx = de_create_charctx(c, 0);
177 de_char_decide_output_format(c, charctx);
178 de_copy_std_palette(DE_PALID_PC16, 0, 0, charctx->pal, 16, 0);
179 do_oldfmt_text_main(c, d, unc_data, charctx);
181 done:
182 if(charctx) {
183 de_free_charctx_screens(c, charctx);
184 de_destroy_charctx(c, charctx);
186 dbuf_close(unc_data);
189 static void do_oldfmt_image(deark *c, struct storyboard_ctx *d, i64 pos)
191 dbuf *unc_data = NULL;
192 de_bitmap *img = NULL;
193 de_finfo *fi = NULL;
195 d->max_unc_size = d->height * d->rowspan;
196 d->width = d->rowspan * (8/d->bpp);
197 de_dbg_dimensions(c, d->width, d->height);
198 if(!de_good_image_dimensions(c, d->width, d->height)) {
199 goto done;
202 unc_data = dbuf_create_membuf(c, d->max_unc_size, 0x1);
203 dbuf_enable_wbuffer(unc_data);
205 if(!decompress_oldfmt(c, d, pos, unc_data)) goto done;
206 dbuf_flush(unc_data);
208 fi = de_finfo_create(c);
210 if(d->mode == 0x06) {
211 fi->density.code = DE_DENSITY_UNK_UNITS;
212 fi->density.xdens = 12.0;
213 fi->density.ydens = 5.0;
214 d->pal[0] = DE_STOCKCOLOR_BLACK;
215 d->pal[1] = DE_STOCKCOLOR_WHITE;
217 else { // assuming mode = 0x04
218 fi->density.code = DE_DENSITY_UNK_UNITS;
219 fi->density.xdens = 6.0;
220 fi->density.ydens = 5.0;
221 // TODO? In PC Storyboard 1.0 Picture Maker, images can be displayed using
222 // different CGA palettes (F3/F4 keys). But that information is not stored
223 // in the file.
224 // Maybe we should have a command-line option to select the palette.
225 // Also, maybe we should have a CGA composite color mode.
226 de_copy_std_palette(DE_PALID_CGA, 3, 0, d->pal, 4, 0);
229 img = de_bitmap_create(c, d->width, d->height, ((d->bpp==1)?1:3));
230 de_convert_image_paletted(unc_data, 0, d->bpp, d->rowspan, d->pal, img, 0);
231 de_bitmap_write_to_file_finfo(img, fi, 0);
233 dbuf_close(unc_data);
234 de_bitmap_destroy(img);
235 de_finfo_destroy(c, fi);
236 done:
240 static void de_run_storyboard_oldfmt(deark *c, struct storyboard_ctx *d,
241 de_module_params *mparams)
243 i64 pos;
245 pos = 6;
246 if(de_getbyte_p(&pos) != 0) {
247 d->need_errmsg = 1;
248 goto done;
251 d->mode = de_getbyte_p(&pos);
252 de_dbg(c, "mode: %u", (UI)d->mode);
253 pos += 3; // ?
255 d->rowspan = de_getu16le_p(&pos);
256 de_dbg(c, "bytes per row: %u", (UI)d->rowspan);
257 d->height = de_getu16le_p(&pos);
258 de_dbg(c, "height: %u", (UI)d->height);
260 switch(d->mode) {
261 case 3:
262 d->is_text = 1;
263 break;
264 case 4:
265 d->bpp = 2;
266 break;
267 case 6:
268 d->bpp = 1;
269 break;
270 default:
271 de_err(c, "Unsupported screen mode: %u", (UI)d->mode);
272 goto done;
275 if(d->is_text) {
276 do_oldfmt_text(c, d, pos);
278 else {
279 do_oldfmt_image(c, d, pos);
281 // TODO: Is it possible for a file to contain multiple images?
283 done:
287 static int newfmt_copy_from_prev_row(deark *c, struct storyboard_ctx *d,
288 dbuf *outf, i64 count)
290 dbuf_flush(outf);
291 if(count > d->width) return 0;
292 dbuf_copy(outf, outf->len - d->width, count, outf);
293 return 1;
296 static int do_decompress_newfmt(deark *c, struct storyboard_ctx *d,
297 dbuf *outf)
299 i64 pos = 0;
300 i64 ntotal = 0;
301 int failure_flag = 0;
302 u8 max_ucode, max_rcode;
303 i64 endpos = c->infile->len;
305 // There seem to be two compression schemes, one of which supports
306 // "long" codes and other things.
307 // I don't know how to decide which one to use -- maybe long codes
308 // are only used by 640x480x256 files?
310 if(d->cmpr_meth==1) {
311 max_ucode = 0x7e;
312 max_rcode = 0xfc;
314 else {
315 max_ucode = 0x7f;
316 max_rcode = 0xff;
319 if(d->pixels_start>0) {
320 pos = d->pixels_start;
322 else {
323 pos = 2048;
326 while(1) {
327 i64 count;
328 u8 b, b2;
329 u8 uflag = 0;
330 u8 rflag = 0;
331 u8 cflag = 0;
333 if(ntotal >= d->max_unc_size) break;
334 if(pos>=endpos) break;
336 b = de_getbyte_p(&pos);
338 if(b==0x00) {
339 // I don't know if 0x00 is legal, but it seems to (?) disable compression
340 // for the rest of the file.
341 uflag = 1;
342 count = endpos - pos;
343 if(count<0) count = 0;
345 else if(b>=0x01 && b<=max_ucode) {
346 uflag = 1;
347 count = (i64)(b & 0x7f);
349 else if(b==0x7f) {
350 uflag = 1;
351 count = de_getu16le_p(&pos);
353 else if(b>=0x81 && b<=max_rcode) {
354 rflag = 1;
355 count = (i64)(b & 0x7f);
357 else if(b==0xfd) {
358 cflag = 1;
359 count = de_getu16le_p(&pos);
361 else if(b==0xfe) {
362 cflag = 1;
363 count = de_getbyte_p(&pos);
365 else if(b==0xff) {
366 rflag = 1;
367 count = de_getu16le_p(&pos);
369 else {
370 d->need_errmsg = 1;
371 failure_flag = 1;
372 goto done;
375 if(rflag) { // compressed run
376 b2 = de_getbyte_p(&pos);
377 dbuf_write_run(outf, b2, count);
378 ntotal += count;
380 else if(uflag) { // uncompressed run
381 dbuf_copy(c->infile, pos, count, outf);
382 pos += count;
383 ntotal += count;
385 else if(cflag) {
386 if(!newfmt_copy_from_prev_row(c, d, outf, count)) {
387 failure_flag = 1;
388 d->need_errmsg = 1;
389 goto done;
394 done:
395 if(d->need_errmsg) {
396 de_err(c, "Decompression failed (near %"I64_FMT")", pos);
397 d->need_errmsg = 0;
399 return !failure_flag;
402 static void do_newfmt_render_planar(deark *c, struct storyboard_ctx *d,
403 dbuf *unc_data, de_bitmap *img)
405 i64 n;
406 #define SBPIC_MAXPLANES 4
407 u8 pbit[SBPIC_MAXPLANES];
409 de_zeromem(pbit, sizeof(pbit));
411 for(n=0; n<d->planespan; n++) {
412 UI k;
413 UI pn;
414 i64 xpos, ypos;
416 for(pn=0; pn<(UI)d->bpp; pn++) {
417 pbit[pn] = dbuf_getbyte(unc_data, pn*d->planespan + n);
420 ypos = n%d->height;
421 for(k=0; k<8; k++) {
422 UI palent;
424 xpos = 8*(n/d->height) + (i64)(7-k);
425 palent = 0;
426 for(pn=0; pn<(UI)d->bpp; pn++) {
427 if((pbit[pn] & (1U<<k))!=0) {
428 palent |= 1U<<pn;
432 de_bitmap_setpixel_rgb(img, xpos, ypos, d->pal[palent]);
437 static void newfmt_readpal256(deark *c, struct storyboard_ctx *d)
439 i64 pos = 128;
440 u8 cr1, cg1, cb1;
441 u8 cr2, cg2, cb2;
442 i64 k;
443 char tmps[64];
445 for(k=0; k<256; k++) {
446 cr1 = de_getbyte(pos);
447 cg1 = de_getbyte(pos+256);
448 cb1 = de_getbyte(pos+512);
449 pos++;
451 cr2 = de_scale_63_to_255(cr1);
452 cg2 = de_scale_63_to_255(cg1);
453 cb2 = de_scale_63_to_255(cb1);
454 d->pal[k] = DE_MAKE_RGB(cr2, cg2, cb2);
455 de_snprintf(tmps, sizeof(tmps), "(%2d,%2d,%2d) "DE_CHAR_RIGHTARROW" ",
456 (int)cr1, (int)cg1, (int)cb1);
457 de_dbg_pal_entry2(c, k, d->pal[k], tmps, NULL, NULL);
461 static void do_newfmt_main(deark *c, struct storyboard_ctx *d)
463 dbuf *unc_data = NULL;
464 de_bitmap *img = NULL;
465 de_finfo *fi = NULL;
466 UI i;
467 char tmps[32];
468 // I can't figure out how the image bits are mapped to palette indices.
469 // These maps make no sense to me, but it's one way to do it.
470 static const u8 palmap_cga[4] = { 3, 1, 2, 0 };
471 static const u8 palmap_ega[16] = {
472 0, 11, 6, 4, 13, 15, 2, 9, 8, 3, 14, 12, 5, 7, 10, 1 };
473 static const u8 palmap_16[16] = {
474 0, 11, 13, 15, 6, 4, 2, 9, 8, 3, 5, 7, 14, 12, 10, 1 };
476 de_dbg(c, "screen mode: %u"DE_CHAR_TIMES"%u %u-color", (UI)d->sm_width,
477 (UI)d->sm_height, (UI)(1U<<d->bpp));
478 // The SHOWPIC utility from SB-Live ignores the reported dimensions, but
479 // the Picture Maker utility respects them.
480 d->width = de_getu16le(5);
481 d->height = de_getu16le(7);
482 de_dbg_dimensions(c, d->width, d->height);
483 if(!de_good_image_dimensions(c, d->width, d->height)) goto done;
485 if(d->bpp==2) {
486 u8 x;
487 u8 bg;
489 x = de_getbyte(13) & 0x0f;
490 if(x==0x06) {
491 de_copy_std_palette(DE_PALID_CGA, 4, 0, d->paltmp, 4, 0);
493 else {
494 de_copy_std_palette(DE_PALID_CGA, 3, 0, d->paltmp, 4, 0);
496 bg = de_getbyte(14) & 0x0f;
497 d->paltmp[0] = de_get_std_palette_entry(DE_PALID_PC16, 0, (int)bg);
499 for(i=0; i<4; i++) {
500 d->pal[i] = d->paltmp[(UI)palmap_cga[i]];
503 else if(d->bpp==4) {
504 for(i=0; i<16; i++) {
505 int idx, idx_adj;
507 idx = (int)de_getbyte(13+(i64)i);
508 if(idx==6 && d->sm_height==200) {
509 idx_adj = 20; // ?? hack - dark yellow vs. brown issue
511 else {
512 idx_adj = idx;
514 d->paltmp[i] = de_get_std_palette_entry(DE_PALID_EGA64, 0, idx_adj);
515 de_snprintf(tmps, sizeof(tmps), "%2d ", (int)idx);
516 de_dbg_pal_entry2(c, i, d->paltmp[i], tmps, NULL, NULL);
518 for(i=0; i<16; i++) {
519 if(d->sm_height==200) {
520 d->pal[i] = d->paltmp[(UI)palmap_16[i]];
522 else {
523 d->pal[i] = d->paltmp[(UI)palmap_ega[i]];
527 else if(d->bpp==8) {
528 newfmt_readpal256(c, d);
531 if(d->bpp<8) {
532 d->nstrips_per_plane = de_pad_to_n(d->width, 8) / 8;
533 d->planespan = d->nstrips_per_plane * d->height;
534 d->max_unc_size = d->planespan*d->bpp;
536 else {
537 d->max_unc_size = d->width * d->height * (d->bpp/8);
540 unc_data = dbuf_create_membuf(c, d->max_unc_size, 0x1);
541 dbuf_enable_wbuffer(unc_data);
543 if(!do_decompress_newfmt(c, d, unc_data)) goto done;
544 dbuf_flush(unc_data);
546 fi = de_finfo_create(c);
547 img = de_bitmap_create(c, d->width, d->height, 3);
549 if(d->bpp<8) {
550 do_newfmt_render_planar(c, d, unc_data, img);
552 else {
553 de_convert_image_paletted(unc_data, 0, 8, d->width, d->pal, img, 0);
556 if(d->sm_width==320 && d->sm_height==200) {
557 fi->density.code = DE_DENSITY_UNK_UNITS;
558 fi->density.xdens = 6.0;
559 fi->density.ydens = 5.0;
561 else if(d->sm_width==640 && d->sm_height!=480) {
562 fi->density.code = DE_DENSITY_UNK_UNITS;
563 fi->density.xdens = 480.0;
564 fi->density.ydens = (double)d->sm_height;
567 de_bitmap_write_to_file_finfo(img, fi, 0);
569 done:
570 dbuf_close(unc_data);
571 de_bitmap_destroy(img);
572 de_finfo_destroy(c, fi);
575 // Support for "new" format is highly experimental. I don't know what most of
576 // the first 2048 bytes in the file are for.
577 static void de_run_storyboard_newfmt(deark *c, struct storyboard_ctx *d,
578 de_module_params *mparams)
580 u8 hdrbytes[4];
582 dbuf_read(c->infile, hdrbytes, 0, sizeof(hdrbytes));
584 if(hdrbytes[1]==0x84 && hdrbytes[3]==0x08) {
585 d->sm_width = 320;
586 d->sm_height = 200;
587 d->bpp = 2;
589 else if(hdrbytes[1]==0x84 && hdrbytes[3]==0x01) {
590 d->sm_width = 640;
591 d->sm_height = 200;
592 d->bpp = 4;
594 else if(hdrbytes[1]==0x84 && hdrbytes[3]==0x03) {
595 d->sm_width = 640;
596 d->sm_height = 350;
597 d->bpp = 4;
599 else if(hdrbytes[1]==0x84 && hdrbytes[3]==0x07) {
600 d->sm_width = 640;
601 d->sm_height = 480;
602 d->bpp = 4;
604 else if(hdrbytes[1]==0x85) {
605 d->sm_width = 320;
606 d->sm_height = 200;
607 d->bpp = 8;
608 d->pixels_start = 1856;
610 else if(hdrbytes[1]==0x86) {
611 d->sm_width = 640;
612 d->sm_height = 480;
613 d->bpp = 8;
614 d->pixels_start = 2816;
615 d->cmpr_meth = 1;
618 if(d->sm_width>0) {
619 do_newfmt_main(c, d);
621 else {
622 de_err(c, "Not a supported Storyboard format");
626 static void de_run_storyboard(deark *c, de_module_params *mparams)
628 struct storyboard_ctx *d = NULL;
629 u8 b;
631 d = de_malloc(c, sizeof(struct storyboard_ctx));
632 d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_CP437);
634 b = de_getbyte(2);
635 if(b=='_') {
636 de_declare_fmt(c, "Storyboard picture (old)");
637 de_run_storyboard_oldfmt(c, d, mparams);
639 else if(b==0xc1) {
640 de_declare_fmt(c, "Storyboard picture (new)");
641 de_run_storyboard_newfmt(c, d, mparams);
643 else {
644 d->need_errmsg = 1;
647 if(d) {
648 if(d->need_errmsg) {
649 de_err(c, "Bad or unsupported Storyboard image");
651 de_free(c, d);
655 static int de_identify_storyboard(deark *c)
657 u8 b[6];
658 int has_ext;
660 de_read(b, 0, sizeof(b));
662 if(!de_memcmp(b, (const void*)"EP_CAP", 6)) {
663 return 100;
666 if(c->infile->len<1856) return 0;
667 if(b[2]!=0xc1) return 0;
668 if(b[0]!=0x00 && b[0]!=0x54) return 0;
670 has_ext = de_input_file_has_ext(c, "pic") ||
671 de_input_file_has_ext(c, "cap");
673 if(b[1]==0x84) {
674 if(b[3]==0x01 || b[3]==0x03 || b[3]==0x07 || b[3]==0x08) {
675 return has_ext ? 90 : 50;
678 else if(b[1]==0x85 || b[1]==0x86) {
679 return has_ext ? 51 : 11;
682 return 0;
685 void de_module_storyboard(deark *c, struct deark_module_info *mi)
687 mi->id = "storyboard";
688 mi->desc = "Storyboard PIC/CAP";
689 mi->run_fn = de_run_storyboard;
690 mi->identify_fn = de_identify_storyboard;