Refactoring the iff decoder
[deark.git] / modules / wpg.c
blob1fcd843fec4e0311fec7a142fd53e20cd2d40312
1 // This file is part of Deark.
2 // Copyright (C) 2017 Jason Summers
3 // See the file COPYING for terms of use.
5 // WordPerfect Graphics
7 #include <deark-config.h>
8 #include <deark-private.h>
9 DE_DECLARE_MODULE(de_module_wpg);
11 typedef struct localctx_struct {
12 i64 start_of_data;
13 u8 ver_major, ver_minor;
14 int opt_fixpal;
15 int has_pal;
16 u32 pal[256];
18 // Fields used only by the "summary" debug line:
19 i64 num_pal_entries; // 0 if no palette
20 int start_wpg_data_record_ver; // Highest "Start of WPG data" record type
21 int bitmap_record_ver; // Highest "Bitmap" record type
22 i64 bitmap_count;
23 i64 bpp_of_first_bitmap;
24 i64 width_of_first_bitmap;
25 i64 height_of_first_bitmap;
26 u8 has_eps1, has_eps2;
27 } lctx;
29 static int do_read_header(deark *c, lctx *d, i64 pos1)
31 i64 pos = pos1;
33 de_dbg(c, "header at %d", (int)pos);
34 de_dbg_indent(c, 1);
36 pos += 4; // FileId
38 d->start_of_data = de_getu32le(pos);
39 de_dbg(c, "start of data: %u", (unsigned int)d->start_of_data);
40 pos += 4;
42 pos++; // ProductType
43 pos++; // FileType
45 d->ver_major = de_getbyte(pos++);
46 d->ver_minor = de_getbyte(pos++);
47 de_dbg(c, "version: %d.%d", (int)d->ver_major, (int)d->ver_minor);
49 de_dbg_indent(c, -1);
50 return 1;
53 typedef void (*record_handler_fn)(deark *c, lctx *d, u8 rectype, i64 dpos,
54 i64 dlen);
56 struct wpg_rectype_info {
57 u8 rectype;
58 const char *name;
59 record_handler_fn fn;
62 static int do_uncompress_rle(deark *c, lctx *d, dbuf *f, i64 pos1, i64 len,
63 i64 rowspan, dbuf *unc_pixels)
65 i64 pos;
66 u8 b, b2;
67 i64 count;
68 i64 endpos;
69 i64 k;
71 pos = pos1;
72 endpos = pos1+len;
74 while(1) {
75 if(pos>=endpos) {
76 break; // Reached the end of source data
78 b = dbuf_getbyte(f, pos++);
80 if(b==0x00) { // repeat scanline
81 i64 src_line_pos;
83 count = dbuf_getbyte(f, pos++);
85 // Make 'count' more copies of the previous scanline
86 src_line_pos = unc_pixels->len - rowspan;
87 for(k=0; k<count; k++) {
88 // (It is allowed to copy from a membuf to itself.)
89 dbuf_copy(unc_pixels, src_line_pos, rowspan, unc_pixels);
92 else if(b<=0x7f) { // uncompressed run
93 count = (i64)b;
94 dbuf_copy(f, pos, count, unc_pixels);
95 pos += count;
97 else if(b==0x80) { // Special 0xff compression
98 count = (i64)dbuf_getbyte(f, pos++);
99 dbuf_write_run(unc_pixels, 0xff, count);
101 else { // byte RLE compression
102 count = (i64)(b&0x7f);
103 b2 = dbuf_getbyte(f, pos++);
104 dbuf_write_run(unc_pixels, b2, count);
108 return 1;
111 // Make a copy of the global palette, possibly adjusting it in some way.
112 // Caller supplies finalpal[256].
113 static void get_final_palette(deark *c, lctx *d, u32 *finalpal, i64 bpp)
115 i64 k;
116 u8 cr, cg, cb;
117 int has_3plusbitpal = 0;
118 int has_5plusbitpal = 0;
119 int has_nonblack_color = 0;
120 int fixpal2_flag = 0;
121 int fixpal4_flag = 0;
123 if(bpp==2 && !d->has_pal) {
124 // I'm not sure what I'm supposed to do here. The first 4 colors of
125 // the default palette do not really constitute a usable 4-color
126 // palette.
127 // The images of this type that I've seen look correct if I use a
128 // particular CGA palette. So...
129 de_warn(c, "4-color image with no palette. Using a CGA palette.");
130 for(k=0; k<4; k++) {
131 finalpal[k] = de_palette_pcpaint_cga4(2, (int)k);
133 return;
136 for(k=0; k<256; k++) {
137 finalpal[k] = d->pal[k];
139 if(d->opt_fixpal && bpp==4 && k<16) {
140 cr = DE_COLOR_R(d->pal[k]);
141 cg = DE_COLOR_G(d->pal[k]);
142 cb = DE_COLOR_B(d->pal[k]);
144 if((cr&0x0f)!=0 || (cg&0x0f)!=0 || (cb&0x0f)!=0) {
145 has_5plusbitpal = 1;
147 if((cr&0x3f)!=0 || (cg&0x3f)!=0 || (cb&0x3f)!=0) {
148 has_3plusbitpal = 1;
151 if(cr || cg || cb) {
152 has_nonblack_color = 1;
157 if(d->opt_fixpal && bpp==4 && !has_3plusbitpal && has_nonblack_color) {
158 de_dbg(c, "Palette seems to have 2 bits of precision. Rescaling palette.");
159 fixpal2_flag = 1;
161 else if(d->opt_fixpal && bpp==4 && !has_5plusbitpal && has_nonblack_color) {
162 de_dbg(c, "Palette seems to have 4 bits of precision. Rescaling palette.");
163 fixpal4_flag = 1;
166 if(fixpal2_flag) {
167 for(k=0; k<16; k++) {
168 cr = DE_COLOR_R(finalpal[k]);
169 cg = DE_COLOR_G(finalpal[k]);
170 cb = DE_COLOR_B(finalpal[k]);
171 cr = 85*(cr>>6);
172 cg = 85*(cg>>6);
173 cb = 85*(cb>>6);
174 finalpal[k] = DE_MAKE_RGB(cr, cg, cb);
177 else if(fixpal4_flag) {
178 for(k=0; k<16; k++) {
179 cr = DE_COLOR_R(finalpal[k]);
180 cg = DE_COLOR_G(finalpal[k]);
181 cb = DE_COLOR_B(finalpal[k]);
182 cr = 17*(cr>>4);
183 cg = 17*(cg>>4);
184 cb = 17*(cb>>4);
185 finalpal[k] = DE_MAKE_RGB(cr, cg, cb);
190 static void handler_bitmap(deark *c, lctx *d, u8 rectype, i64 dpos1, i64 dlen)
192 i64 w, h;
193 i64 xdens, ydens;
194 i64 bpp;
195 i64 pos = dpos1;
196 i64 rowspan;
197 int is_bilevel;
198 int is_grayscale;
199 int output_bypp;
200 int record_version;
201 dbuf *unc_pixels = NULL;
202 de_bitmap *img = NULL;
203 de_finfo *fi = NULL;
204 u32 finalpal[256];
206 d->bitmap_count++;
208 if(rectype==0x14) {
209 record_version = 2;
210 pos += 10;
212 else {
213 record_version = 1;
216 // Keep track of the highest bitmap record version found.
217 if(record_version > d->bitmap_record_ver) {
218 d->bitmap_record_ver = record_version;
221 w = de_getu16le(pos);
222 pos += 2;
223 h = de_getu16le(pos);
224 pos += 2;
225 de_dbg_dimensions(c, w, h);
227 bpp = de_getu16le(pos);
228 de_dbg(c, "bits/pixel: %d", (int)bpp);
229 pos += 2;
231 xdens = de_getu16le(pos);
232 pos += 2;
233 ydens = de_getu16le(pos);
234 pos += 2;
235 de_dbg(c, "density: %d"DE_CHAR_TIMES"%d dpi", (int)xdens, (int)ydens);
237 if(d->bitmap_count==1) {
238 d->bpp_of_first_bitmap = bpp;
239 d->width_of_first_bitmap = w;
240 d->height_of_first_bitmap = h;
243 if(bpp!=1 && bpp!=2 && bpp!=4 && bpp!=8) {
244 de_err(c, "Unsupported bitmap depth: %d", (int)bpp);
245 goto done;
247 if(!de_good_image_dimensions(c, w, h)) goto done;
249 // Evidence suggests the palette is to be ignored if bpp==1.
250 // (Or maybe you're supposed to use pal[0] and pal[15]?)
251 is_bilevel = (bpp==1);
253 if(is_bilevel) {
254 is_grayscale = 1;
256 else {
257 get_final_palette(c, d, finalpal, bpp);
258 is_grayscale = de_is_grayscale_palette(finalpal, (i64)1<<bpp);
261 if(is_bilevel || is_grayscale)
262 output_bypp = 1;
263 else
264 output_bypp = 3;
266 rowspan = (bpp * w + 7)/8;
268 unc_pixels = dbuf_create_membuf(c, h*rowspan, 0x1);
270 if(!do_uncompress_rle(c, d, c->infile, pos, dpos1+dlen-pos, rowspan, unc_pixels)) {
271 goto done;
274 img = de_bitmap_create2(c, w, (rowspan*8)/bpp, h, output_bypp);
276 fi = de_finfo_create(c);
278 if(xdens>0 && ydens>0) {
279 fi->density.code = DE_DENSITY_DPI;
280 fi->density.xdens = (double)xdens;
281 fi->density.ydens = (double)ydens;
284 if(is_bilevel) {
285 de_convert_image_bilevel(unc_pixels, 0, rowspan, img, 0);
287 else {
288 if(!d->has_pal && bpp!=2) {
289 // TODO: Figure out what the default palette is.
290 de_err(c, "Paletted images with no palette are not supported");
291 goto done;
294 de_convert_image_paletted(unc_pixels, 0, bpp, rowspan, finalpal, img, 0);
297 de_bitmap_write_to_file_finfo(img, fi, 0);
299 done:
300 de_bitmap_destroy(img);
301 de_finfo_destroy(c, fi);
302 dbuf_close(unc_pixels);
305 static void handler_colormap(deark *c, lctx *d, u8 rectype, i64 dpos1, i64 dlen)
307 i64 start_index;
308 i64 num_entries;
309 i64 pos = dpos1;
311 d->has_pal = 1;
312 start_index = de_getu16le(pos);
313 de_dbg(c, "start index: %d", (int)start_index);
314 pos += 2;
316 num_entries = de_getu16le(pos);
317 de_dbg(c, "num entries: %d", (int)num_entries);
318 pos += 2;
320 if(start_index+num_entries>256) num_entries = 256 - start_index;
321 if(start_index<0 || start_index+num_entries>256) return;
323 if(num_entries > d->num_pal_entries) {
324 d->num_pal_entries = num_entries;
327 de_read_palette_rgb(c->infile, pos, num_entries, 3, &d->pal[start_index],
328 256-start_index, 0);
331 static void handler_start_of_wpg_data(deark *c, lctx *d, u8 rectype, i64 dpos1, i64 dlen)
333 int record_version;
335 if(rectype==0x19) {
336 record_version = 2;
338 else {
339 record_version = 1;
342 // Keep track of the highest record version found.
343 if(record_version > d->start_wpg_data_record_ver) {
344 d->start_wpg_data_record_ver = record_version;
348 static void handler_eps_type_1(deark *c, lctx *d, u8 rectype, i64 dpos1, i64 dlen)
350 d->has_eps1 = 1;
351 if(dlen<=8) return;
352 dbuf_create_file_from_slice(c->infile, dpos1+8, dlen-8, "eps", NULL, 0);
355 static void handler_eps_type_2(deark *c, lctx *d, u8 rectype, i64 dpos1, i64 dlen)
357 d->has_eps2 = 1;
358 // TODO
361 static const struct wpg_rectype_info wpg_rectype_info_arr[] = {
362 { 0x01, "Fill attributes", NULL },
363 { 0x02, "Line attributes", NULL },
364 { 0x03, "Marker attributes", NULL },
365 { 0x04, "Polymarker", NULL },
366 { 0x05, "Line", NULL },
367 { 0x06, "Polyline", NULL },
368 { 0x07, "Rectangle", NULL },
369 { 0x08, "Polygon", NULL },
370 { 0x09, "Ellipse", NULL },
371 { 0x0b, "Bitmap, Type 1", handler_bitmap },
372 { 0x0c, "Graphics text, Type 1", NULL },
373 { 0x0d, "Graphics text attributes", NULL },
374 { 0x0e, "Color map", handler_colormap },
375 { 0x0f, "Start of WPG data", handler_start_of_wpg_data },
376 { 0x10, "End of WPG data", NULL },
377 { 0x11, "PostScript data, Type 1", handler_eps_type_1 },
378 { 0x12, "Output attributes", NULL },
379 { 0x13, "Curved polyline", NULL },
380 { 0x14, "Bitmap, Type 2", handler_bitmap },
381 { 0x15, "Start figure", NULL },
382 { 0x16, "Start chart", NULL },
383 { 0x17, "PlanPerfect data", NULL },
384 { 0x18, "Graphics text, Type 2", NULL },
385 { 0x19, "Start of WPG data, Type 2", handler_start_of_wpg_data },
386 { 0x1a, "Graphics text, Type 3", NULL },
387 { 0x1b, "PostScript data, Type 2", handler_eps_type_2 }
390 static const struct wpg_rectype_info *find_wpg_rectype_info(u8 rectype)
392 i64 i;
393 for(i=0; i<(i64)DE_ARRAYCOUNT(wpg_rectype_info_arr); i++) {
394 if(wpg_rectype_info_arr[i].rectype == rectype) {
395 return &wpg_rectype_info_arr[i];
398 return NULL;
401 static int do_record(deark *c, lctx *d, i64 pos1, i64 *bytes_consumed)
403 i64 pos = pos1;
404 u8 rectype;
405 i64 rec_dlen;
406 int retval = 0;
407 const char *name;
408 const struct wpg_rectype_info *wri;
410 rectype = de_getbyte(pos++);
411 wri = find_wpg_rectype_info(rectype);
412 if(wri) name = wri->name;
413 else name="?";
414 de_dbg(c, "record type 0x%02x (%s) at %d", (unsigned int)rectype, name, (int)pos1);
415 de_dbg_indent(c, 1);
417 rec_dlen = (i64)de_getbyte(pos++);
419 // As far as I can tell, the variable-length integer works as follows.
420 // An integer uses either 1, 3, or 5 bytes.
422 // number = d c b a (d = most-significant bits, ...)
423 // value byte0 byte1 byte2 byte3 byte4
424 // -------------- -------- -------- -------- -------- --------
425 // (0-32767) : 11111111 aaaaaaaa 0bbbbbbb
426 // (0-2147483647): 11111111 cccccccc 1ddddddd aaaaaaaa bbbbbbbb
427 // (0-254) : aaaaaaaa [where the a's are not all 1's]
429 if(rec_dlen==0xff) {
430 // Not an 8-bit value. Could be 16-bit or 32-bit.
431 rec_dlen = de_getu16le(pos);
432 pos += 2;
434 if(rec_dlen & 0x8000) { // A 32-bit value
435 i64 n;
437 n = de_getu16le(pos);
438 pos += 2;
439 rec_dlen = ((rec_dlen&0x7fff)<<16) | n;
443 de_dbg(c, "rec dpos=%d, dlen=[%d]%d", (int)pos, (int)(pos-(pos1+1)), (int)rec_dlen);
445 if(wri && wri->fn) {
446 wri->fn(c, d, rectype, pos, rec_dlen);
449 *bytes_consumed = (pos-pos1) + rec_dlen;
450 retval = 1;
452 de_dbg_indent(c, -1);
453 return retval;
456 static int do_record_area(deark *c, lctx *d, i64 pos)
458 de_dbg(c, "record area at %d", (int)pos);
459 de_dbg_indent(c, 1);
460 while(1) {
461 i64 bytes_consumed = 0;
462 int ret;
464 if(pos >= c->infile->len) break;
466 ret = do_record(c, d, pos, &bytes_consumed);
467 if(!ret || bytes_consumed<1) break;
469 pos += bytes_consumed;
472 de_dbg_indent(c, -1);
473 return 1;
476 static void do_set_default_palette(deark *c, lctx *d)
478 int k;
480 if(d->ver_major>1) return; // TODO: v2 files have a different palette
482 for(k=0; k<256; k++) {
483 d->pal[k] = de_palette_vga256(k);
487 static void de_run_wpg(deark *c, de_module_params *mparams)
489 lctx *d = NULL;
490 const char *s;
491 i64 pos;
493 d = de_malloc(c, sizeof(lctx));
495 d->opt_fixpal = 1;
496 s = de_get_ext_option(c, "wpg:fixpal");
497 if(s) d->opt_fixpal = de_atoi(s);
499 pos = 0;
500 if(!do_read_header(c, d, pos)) goto done;
501 pos = d->start_of_data;
503 do_set_default_palette(c, d);
505 if(!do_record_area(c, d, pos)) goto done;
507 // This debug line is mainly to help find interesting WPG files.
508 de_dbg(c, "summary: ver=%d.%d dataver=%d pal=%d bitmaps=%d "
509 "bitmapver=%d bpp=%d dimensions=%d"DE_CHAR_TIMES"%d%s%s",
510 (int)d->ver_major, (int)d->ver_minor, d->start_wpg_data_record_ver,
511 (int)d->num_pal_entries,
512 (int)d->bitmap_count, d->bitmap_record_ver,
513 (int)d->bpp_of_first_bitmap,
514 (int)d->width_of_first_bitmap, (int)d->height_of_first_bitmap,
515 d->has_eps1?" eps1":"", d->has_eps2?" eps2":"");
517 done:
518 de_free(c, d);
521 static int de_identify_wpg(deark *c)
523 u8 buf[10];
524 de_read(buf, 0, 10);
525 if(!de_memcmp(buf, "\xff\x57\x50\x43", 4) &&
526 buf[8]==0x01 && buf[9]==0x16)
528 return 100;
530 return 0;
533 void de_module_wpg(deark *c, struct deark_module_info *mi)
535 mi->id = "wpg";
536 mi->desc = "WordPerfect Graphics";
537 mi->run_fn = de_run_wpg;
538 mi->identify_fn = de_identify_wpg;