1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // "GROB" image format for HP48/49 calculators.
7 #include <deark-config.h>
8 #include <deark-private.h>
9 DE_DECLARE_MODULE(de_module_grob
);
11 typedef struct localctx_struct
{
15 int grayscale_lsb
; // Does the plane of least-significant bits come first?
18 static void grob_read_binary_bitmap(deark
*c
, lctx
*d
, dbuf
*inf
, i64 pos
)
28 de_bitmap
*img
= NULL
;
30 if(d
->num_planes
<=1) {
31 de_convert_and_write_image_bilevel2(inf
, pos
, d
->w
, d
->h_phys
, (d
->w
+7)/8,
32 DE_CVTF_WHITEISZERO
|DE_CVTF_LSBFIRST
, NULL
, 0);
36 if((d
->h_phys
% d
->num_planes
) != 0) {
37 de_warn(c
, "Number of rows is not divisible by number of planes. The grob:planes "
38 "setting is probably not correct.");
40 h_logical
= d
->h_phys
/d
->num_planes
;
42 if(!de_good_image_dimensions(c
, d
->w
, h_logical
))
45 de_dbg(c
, "logical dimensions: %d"DE_CHAR_TIMES
"%d", (int)d
->w
, (int)h_logical
);
48 pdwidth
= rowspan
* 8;
49 img
= de_bitmap_create2(c
, d
->w
, pdwidth
, h_logical
, 1);
51 for(j
=0; j
<h_logical
; j
++) {
52 for(i
=0; i
<pdwidth
; i
++) {
54 for(plane
=0; plane
<d
->num_planes
; plane
++) {
55 b
= de_get_bits_symbol_lsb(inf
, 1,
56 pos
+rowspan
*(h_logical
*(i64
)plane
+j
), i
);
58 v
|= b
<<(unsigned int)plane
;
62 v2
= 255-de_sample_nbit_to_8bit(d
->num_planes
, v
);
63 de_bitmap_setpixel_gray(img
, i
, j
, v2
);
67 de_bitmap_write_to_file_finfo(img
, NULL
, 0);
69 de_bitmap_destroy(img
);
72 static void de_run_grob_binary(deark
*c
, lctx
*d
)
78 de_declare_fmt(c
, "HP GROB, binary encoded");
82 // Next 4 fields are packed 20-bit integers, 2.5 bytes each.
84 obj_id
= (hdr
[10]&0x0f)<<16 | hdr
[9]<<8 | hdr
[8];
85 length
= hdr
[12]<<12 | hdr
[11]<<4 | hdr
[10]>>4;
86 de_dbg(c
, "object id: 0x%05x", (unsigned int)obj_id
);
87 if(obj_id
!= 0x02b1e) {
88 de_warn(c
, "Unexpected object identifier (0x%05x, expected 0x02b1e)", (unsigned int)obj_id
);
90 de_dbg(c
, "object length in nibbles: %d", (int)length
);
92 d
->h_phys
= (hdr
[15]&0x0f)<<16 | hdr
[14]<<8 | hdr
[13];
93 d
->w
= hdr
[17]<<12 | hdr
[16]<<4 | hdr
[15]>>4;
94 de_dbg(c
, "%sdimensions: %d"DE_CHAR_TIMES
"%d", (d
->num_planes
==1)?"":"physical ",
95 (int)d
->w
, (int)d
->h_phys
);
97 grob_read_binary_bitmap(c
, d
, c
->infile
, 18);
100 // On return, sets d->bytes_consumed
101 static void grob_text_1_image(deark
*c
, lctx
*d
, i64 pos1
)
107 dbuf
*bin_bmp
= NULL
; // Binary version of the bitmap
114 // We assume the GROB text format starts with
115 // "GROB" <zero or more spaces> <width> <one or more spaces>
116 // <height> <one or more spaces> <data>.
118 // TODO: This parser is pretty clumsy.
120 pos
+= 4; // Skip over "GROB"
122 while(de_getbyte(pos
)==' ')
124 while((x
=de_getbyte(pos
))!=' ') {
125 d
->w
= d
->w
*10 + (x
-'0');
129 while(de_getbyte(pos
)==' ')
131 while((x
=de_getbyte(pos
))!=' ') {
132 d
->h_phys
= d
->h_phys
*10 + (x
-'0');
136 while(de_getbyte(pos
)==' ')
140 de_dbg(c
, "%sdimensions: %d"DE_CHAR_TIMES
"%d", (d
->num_planes
==1)?"":"physical ",
141 (int)d
->w
, (int)d
->h_phys
);
143 // FIXME: This should really be testing the logical height, not the
145 if(!de_good_image_dimensions(c
, d
->w
, d
->h_phys
))
148 // Decode the quasi-hex-encoded data into a memory buffer, then use the
149 // same decoder as for binary format.
151 bin_bmp
= dbuf_create_membuf(c
, d
->h_phys
* (d
->w
+7)/8, 0);
154 while(pos
< c
->infile
->len
) {
155 b0
= de_getbyte(pos
);
156 b1
= de_getbyte(pos
+1);
158 // Apparently, we've reached the end of the bitmap data.
164 x
= de_decode_hex_digit(b0
,NULL
) | (de_decode_hex_digit(b1
,NULL
)<<4);
165 dbuf_writebyte(bin_bmp
, x
);
168 d
->bytes_consumed
= pos
- pos1
;
170 grob_read_binary_bitmap(c
, d
, bin_bmp
, 0);
176 static void de_run_grob_text(deark
*c
, lctx
*d
)
183 de_declare_fmt(c
, "HP GROB, text encoded");
185 // Though some text GROB files begin with "GROB", we also want to support files
186 // that have "%%HP" headers, and other files that have one or more GROB data objects
191 while(pos
< c
->infile
->len
) {
192 // TODO: Ideally, we should be more careful about what we search for.
193 // Maybe we should make sure "GROB" is the first nonwhitespace on the line,
194 // but even that isn't enough.
196 ret
= dbuf_search(c
->infile
, (const u8
*)"GROB", 4, pos
, c
->infile
->len
-pos
, &img_pos
);
198 // No more images in this file.
202 de_dbg(c
, "GROB format found at %d", (int)img_pos
);
205 if(!de_good_image_count(c
, img_count
)) break;
206 grob_text_1_image(c
, d
, img_pos
);
208 if(d
->bytes_consumed
<1) break;
209 pos
= img_pos
+ d
->bytes_consumed
;
213 de_err(c
, "Unknown or unsupported GROB format");
217 static void de_run_grob(deark
*c
, de_module_params
*mparams
)
223 d
= de_malloc(c
, sizeof(lctx
));
225 s
= de_get_ext_option(c
, "grob:planes");
227 d
->num_planes
= de_atoi64(s
);
229 if(d
->num_planes
<1) d
->num_planes
=1;
230 if(d
->num_planes
>8) {
231 de_err(c
, "Unsupported grob:planes option");
235 s
= de_get_ext_option(c
, "grob:planeorder");
237 d
->grayscale_lsb
= 1;
242 if(!de_memcmp(buf
, "HPHP", 4)) {
243 de_run_grob_binary(c
, d
);
246 de_run_grob_text(c
, d
);
253 static int de_identify_grob(deark
*c
)
258 if(buf
[0]=='H' && buf
[1]=='P' && buf
[2]=='H' && buf
[3]=='P' &&
259 buf
[4]=='4' && (buf
[5]=='8' || buf
[5]=='9') &&
260 buf
[8]==0x1e && buf
[9]==0x2b)
265 if(buf
[0]=='G' && buf
[1]=='R' && buf
[2]=='O' && buf
[3]=='B') {
272 static void de_help_grob(deark
*c
)
274 de_msg(c
, "-opt grob:planes=<n> : Treat image as grayscale");
275 de_msg(c
, "-opt grob:planeorder=l : Least-significant plane comes first");
278 void de_module_grob(deark
*c
, struct deark_module_info
*mi
)
281 mi
->desc
= "GROB - HP48/49 calculator image";
282 mi
->run_fn
= de_run_grob
;
283 mi
->identify_fn
= de_identify_grob
;
284 mi
->help_fn
= de_help_grob
;