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
{
13 u8 ver_major
, ver_minor
;
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
23 i64 bpp_of_first_bitmap
;
24 i64 width_of_first_bitmap
;
25 i64 height_of_first_bitmap
;
26 u8 has_eps1
, has_eps2
;
29 static int do_read_header(deark
*c
, lctx
*d
, i64 pos1
)
33 de_dbg(c
, "header at %d", (int)pos
);
38 d
->start_of_data
= de_getu32le(pos
);
39 de_dbg(c
, "start of data: %u", (unsigned int)d
->start_of_data
);
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
);
53 typedef void (*record_handler_fn
)(deark
*c
, lctx
*d
, u8 rectype
, i64 dpos
,
56 struct wpg_rectype_info
{
62 static int do_uncompress_rle(deark
*c
, lctx
*d
, dbuf
*f
, i64 pos1
, i64 len
,
63 i64 rowspan
, dbuf
*unc_pixels
)
76 break; // Reached the end of source data
78 b
= dbuf_getbyte(f
, pos
++);
80 if(b
==0x00) { // repeat scanline
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
94 dbuf_copy(f
, pos
, count
, unc_pixels
);
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
);
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
)
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
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.");
131 finalpal
[k
] = de_palette_pcpaint_cga4(2, (int)k
);
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) {
147 if((cr
&0x3f)!=0 || (cg
&0x3f)!=0 || (cb
&0x3f)!=0) {
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.");
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.");
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
]);
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
]);
185 finalpal
[k
] = DE_MAKE_RGB(cr
, cg
, cb
);
190 static void handler_bitmap(deark
*c
, lctx
*d
, u8 rectype
, i64 dpos1
, i64 dlen
)
201 dbuf
*unc_pixels
= NULL
;
202 de_bitmap
*img
= NULL
;
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
);
223 h
= de_getu16le(pos
);
225 de_dbg_dimensions(c
, w
, h
);
227 bpp
= de_getu16le(pos
);
228 de_dbg(c
, "bits/pixel: %d", (int)bpp
);
231 xdens
= de_getu16le(pos
);
233 ydens
= de_getu16le(pos
);
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
);
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);
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
)
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
)) {
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
;
285 de_convert_image_bilevel(unc_pixels
, 0, rowspan
, img
, 0);
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");
294 de_convert_image_paletted(unc_pixels
, 0, bpp
, rowspan
, finalpal
, img
, 0);
297 de_bitmap_write_to_file_finfo(img
, fi
, 0);
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
)
312 start_index
= de_getu16le(pos
);
313 de_dbg(c
, "start index: %d", (int)start_index
);
316 num_entries
= de_getu16le(pos
);
317 de_dbg(c
, "num entries: %d", (int)num_entries
);
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
],
331 static void handler_start_of_wpg_data(deark
*c
, lctx
*d
, u8 rectype
, i64 dpos1
, i64 dlen
)
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
)
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
)
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
)
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
];
401 static int do_record(deark
*c
, lctx
*d
, i64 pos1
, i64
*bytes_consumed
)
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
;
414 de_dbg(c
, "record type 0x%02x (%s) at %d", (unsigned int)rectype
, name
, (int)pos1
);
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]
430 // Not an 8-bit value. Could be 16-bit or 32-bit.
431 rec_dlen
= de_getu16le(pos
);
434 if(rec_dlen
& 0x8000) { // A 32-bit value
437 n
= de_getu16le(pos
);
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
);
446 wri
->fn(c
, d
, rectype
, pos
, rec_dlen
);
449 *bytes_consumed
= (pos
-pos1
) + rec_dlen
;
452 de_dbg_indent(c
, -1);
456 static int do_record_area(deark
*c
, lctx
*d
, i64 pos
)
458 de_dbg(c
, "record area at %d", (int)pos
);
461 i64 bytes_consumed
= 0;
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);
476 static void do_set_default_palette(deark
*c
, lctx
*d
)
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
)
493 d
= de_malloc(c
, sizeof(lctx
));
496 s
= de_get_ext_option(c
, "wpg:fixpal");
497 if(s
) d
->opt_fixpal
= de_atoi(s
);
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":"");
521 static int de_identify_wpg(deark
*c
)
525 if(!de_memcmp(buf
, "\xff\x57\x50\x43", 4) &&
526 buf
[8]==0x01 && buf
[9]==0x16)
533 void de_module_wpg(deark
*c
, struct deark_module_info
*mi
)
536 mi
->desc
= "WordPerfect Graphics";
537 mi
->run_fn
= de_run_wpg
;
538 mi
->identify_fn
= de_identify_wpg
;