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
;
17 i64 bpp
; // bits per pixel
20 i64 width_in_chars
, height_in_chars
;
25 i64 sm_width
, sm_height
; // target screen mode
26 i64 nstrips_per_plane
;
30 u8 cmpr_meth
; // 1 to enable "long" codes
35 static int decompress_oldfmt(deark
*c
, struct storyboard_ctx
*d
, i64 pos1
,
39 i64 nbytes_written
= 0;
41 int found_attribs
= 0;
42 int element_count
= 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
);
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
);
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
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
74 d
->attribs_pos
= outf
->len
;
83 dbuf_copy(c
->infile
, pos
, count
, outf
);
85 nbytes_written
+= count
;
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
) {
103 de_dbg(c
, "decompressed to %"I64_FMT
" bytes", nbytes_written
);
106 de_dbg_indent_restore(c
, saved_indent_level
);
110 static void do_oldfmt_text_main(deark
*c
, struct storyboard_ctx
*d
, dbuf
*unc_data
,
111 struct de_char_context
*charctx
)
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);
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
;
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
);
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
;
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
)) {
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
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
);
240 static void de_run_storyboard_oldfmt(deark
*c
, struct storyboard_ctx
*d
,
241 de_module_params
*mparams
)
246 if(de_getbyte_p(&pos
) != 0) {
251 d
->mode
= de_getbyte_p(&pos
);
252 de_dbg(c
, "mode: %u", (UI
)d
->mode
);
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
);
271 de_err(c
, "Unsupported screen mode: %u", (UI
)d
->mode
);
276 do_oldfmt_text(c
, d
, pos
);
279 do_oldfmt_image(c
, d
, pos
);
281 // TODO: Is it possible for a file to contain multiple images?
287 static int newfmt_copy_from_prev_row(deark
*c
, struct storyboard_ctx
*d
,
288 dbuf
*outf
, i64 count
)
291 if(count
> d
->width
) return 0;
292 dbuf_copy(outf
, outf
->len
- d
->width
, count
, outf
);
296 static int do_decompress_newfmt(deark
*c
, struct storyboard_ctx
*d
,
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) {
319 if(d
->pixels_start
>0) {
320 pos
= d
->pixels_start
;
333 if(ntotal
>= d
->max_unc_size
) break;
334 if(pos
>=endpos
) break;
336 b
= de_getbyte_p(&pos
);
339 // I don't know if 0x00 is legal, but it seems to (?) disable compression
340 // for the rest of the file.
342 count
= endpos
- pos
;
343 if(count
<0) count
= 0;
345 else if(b
>=0x01 && b
<=max_ucode
) {
347 count
= (i64
)(b
& 0x7f);
351 count
= de_getu16le_p(&pos
);
353 else if(b
>=0x81 && b
<=max_rcode
) {
355 count
= (i64
)(b
& 0x7f);
359 count
= de_getu16le_p(&pos
);
363 count
= de_getbyte_p(&pos
);
367 count
= de_getu16le_p(&pos
);
375 if(rflag
) { // compressed run
376 b2
= de_getbyte_p(&pos
);
377 dbuf_write_run(outf
, b2
, count
);
380 else if(uflag
) { // uncompressed run
381 dbuf_copy(c
->infile
, pos
, count
, outf
);
386 if(!newfmt_copy_from_prev_row(c
, d
, outf
, count
)) {
396 de_err(c
, "Decompression failed (near %"I64_FMT
")", pos
);
399 return !failure_flag
;
402 static void do_newfmt_render_planar(deark
*c
, struct storyboard_ctx
*d
,
403 dbuf
*unc_data
, de_bitmap
*img
)
406 #define SBPIC_MAXPLANES 4
407 u8 pbit
[SBPIC_MAXPLANES
];
409 de_zeromem(pbit
, sizeof(pbit
));
411 for(n
=0; n
<d
->planespan
; n
++) {
416 for(pn
=0; pn
<(UI
)d
->bpp
; pn
++) {
417 pbit
[pn
] = dbuf_getbyte(unc_data
, pn
*d
->planespan
+ n
);
424 xpos
= 8*(n
/d
->height
) + (i64
)(7-k
);
426 for(pn
=0; pn
<(UI
)d
->bpp
; pn
++) {
427 if((pbit
[pn
] & (1U<<k
))!=0) {
432 de_bitmap_setpixel_rgb(img
, xpos
, ypos
, d
->pal
[palent
]);
437 static void newfmt_readpal256(deark
*c
, struct storyboard_ctx
*d
)
445 for(k
=0; k
<256; k
++) {
446 cr1
= de_getbyte(pos
);
447 cg1
= de_getbyte(pos
+256);
448 cb1
= de_getbyte(pos
+512);
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
;
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
;
489 x
= de_getbyte(13) & 0x0f;
491 de_copy_std_palette(DE_PALID_CGA
, 4, 0, d
->paltmp
, 4, 0);
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
);
500 d
->pal
[i
] = d
->paltmp
[(UI
)palmap_cga
[i
]];
504 for(i
=0; i
<16; i
++) {
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
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
]];
523 d
->pal
[i
] = d
->paltmp
[(UI
)palmap_ega
[i
]];
528 newfmt_readpal256(c
, d
);
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
;
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);
550 do_newfmt_render_planar(c
, d
, unc_data
, img
);
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);
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
)
582 dbuf_read(c
->infile
, hdrbytes
, 0, sizeof(hdrbytes
));
584 if(hdrbytes
[1]==0x84 && hdrbytes
[3]==0x08) {
589 else if(hdrbytes
[1]==0x84 && hdrbytes
[3]==0x01) {
594 else if(hdrbytes
[1]==0x84 && hdrbytes
[3]==0x03) {
599 else if(hdrbytes
[1]==0x84 && hdrbytes
[3]==0x07) {
604 else if(hdrbytes
[1]==0x85) {
608 d
->pixels_start
= 1856;
610 else if(hdrbytes
[1]==0x86) {
614 d
->pixels_start
= 2816;
619 do_newfmt_main(c
, d
);
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
;
631 d
= de_malloc(c
, sizeof(struct storyboard_ctx
));
632 d
->input_encoding
= de_get_input_encoding(c
, NULL
, DE_ENCODING_CP437
);
636 de_declare_fmt(c
, "Storyboard picture (old)");
637 de_run_storyboard_oldfmt(c
, d
, mparams
);
640 de_declare_fmt(c
, "Storyboard picture (new)");
641 de_run_storyboard_newfmt(c
, d
, mparams
);
649 de_err(c
, "Bad or unsupported Storyboard image");
655 static int de_identify_storyboard(deark
*c
)
660 de_read(b
, 0, sizeof(b
));
662 if(!de_memcmp(b
, (const void*)"EP_CAP", 6)) {
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");
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;
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
;