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
)
23 de_bitmap
*img
= NULL
;
26 if(d
->num_planes
<=1) {
27 de_convert_and_write_image_bilevel2(inf
, pos
, d
->w
, d
->h_phys
, (d
->w
+7)/8,
28 DE_CVTF_WHITEISZERO
|DE_CVTF_LSBFIRST
, NULL
, 0);
31 if(d
->num_planes
>8) goto done
;
33 if((d
->h_phys
% d
->num_planes
) != 0) {
34 de_warn(c
, "Number of rows is not divisible by number of planes. The grob:planes "
35 "setting is probably not correct.");
37 h_logical
= d
->h_phys
/d
->num_planes
;
39 if(!de_good_image_dimensions(c
, d
->w
, h_logical
))
42 de_dbg(c
, "logical dimensions: %d"DE_CHAR_TIMES
"%d", (int)d
->w
, (int)h_logical
);
45 pdwidth
= rowspan
* 8;
46 img
= de_bitmap_create2(c
, d
->w
, pdwidth
, h_logical
, 1);
48 de_zeromem(pal
, sizeof(pal
));
49 de_make_grayscale_palette(pal
, 1ULL<<d
->num_planes
, 0x1);
51 de_convert_image_paletted_planar(inf
, pos
, d
->num_planes
,
52 rowspan
, h_logical
*rowspan
, pal
, img
, 0x1 | (d
->grayscale_lsb
?0x02:0));
54 de_bitmap_write_to_file_finfo(img
, NULL
, 0);
56 de_bitmap_destroy(img
);
59 static void de_run_grob_binary(deark
*c
, lctx
*d
)
65 de_declare_fmt(c
, "HP GROB, binary encoded");
69 // Next 4 fields are packed 20-bit integers, 2.5 bytes each.
71 obj_id
= (hdr
[10]&0x0f)<<16 | hdr
[9]<<8 | hdr
[8];
72 length
= hdr
[12]<<12 | hdr
[11]<<4 | hdr
[10]>>4;
73 de_dbg(c
, "object id: 0x%05x", (unsigned int)obj_id
);
74 if(obj_id
!= 0x02b1e) {
75 de_warn(c
, "Unexpected object identifier (0x%05x, expected 0x02b1e)", (unsigned int)obj_id
);
77 de_dbg(c
, "object length in nibbles: %d", (int)length
);
79 d
->h_phys
= (hdr
[15]&0x0f)<<16 | hdr
[14]<<8 | hdr
[13];
80 d
->w
= hdr
[17]<<12 | hdr
[16]<<4 | hdr
[15]>>4;
81 de_dbg(c
, "%sdimensions: %d"DE_CHAR_TIMES
"%d", (d
->num_planes
==1)?"":"physical ",
82 (int)d
->w
, (int)d
->h_phys
);
84 grob_read_binary_bitmap(c
, d
, c
->infile
, 18);
87 // On return, sets d->bytes_consumed
88 static void grob_text_1_image(deark
*c
, lctx
*d
, i64 pos1
)
94 dbuf
*bin_bmp
= NULL
; // Binary version of the bitmap
101 // We assume the GROB text format starts with
102 // "GROB" <zero or more spaces> <width> <one or more spaces>
103 // <height> <one or more spaces> <data>.
105 // TODO: This parser is pretty clumsy.
107 pos
+= 4; // Skip over "GROB"
109 while(de_getbyte(pos
)==' ')
111 while((x
=de_getbyte(pos
))!=' ') {
112 d
->w
= d
->w
*10 + (x
-'0');
116 while(de_getbyte(pos
)==' ')
118 while((x
=de_getbyte(pos
))!=' ') {
119 d
->h_phys
= d
->h_phys
*10 + (x
-'0');
123 while(de_getbyte(pos
)==' ')
127 de_dbg(c
, "%sdimensions: %d"DE_CHAR_TIMES
"%d", (d
->num_planes
==1)?"":"physical ",
128 (int)d
->w
, (int)d
->h_phys
);
130 // FIXME: This should really be testing the logical height, not the
132 if(!de_good_image_dimensions(c
, d
->w
, d
->h_phys
))
135 // Decode the quasi-hex-encoded data into a memory buffer, then use the
136 // same decoder as for binary format.
138 bin_bmp
= dbuf_create_membuf(c
, d
->h_phys
* (d
->w
+7)/8, 0);
141 while(pos
< c
->infile
->len
) {
142 b0
= de_getbyte(pos
);
143 b1
= de_getbyte(pos
+1);
145 // Apparently, we've reached the end of the bitmap data.
151 x
= de_decode_hex_digit(b0
,NULL
) | (de_decode_hex_digit(b1
,NULL
)<<4);
152 dbuf_writebyte(bin_bmp
, x
);
155 d
->bytes_consumed
= pos
- pos1
;
157 grob_read_binary_bitmap(c
, d
, bin_bmp
, 0);
163 static void de_run_grob_text(deark
*c
, lctx
*d
)
170 de_declare_fmt(c
, "HP GROB, text encoded");
172 // Though some text GROB files begin with "GROB", we also want to support files
173 // that have "%%HP" headers, and other files that have one or more GROB data objects
178 while(pos
< c
->infile
->len
) {
179 // TODO: Ideally, we should be more careful about what we search for.
180 // Maybe we should make sure "GROB" is the first nonwhitespace on the line,
181 // but even that isn't enough.
183 ret
= dbuf_search(c
->infile
, (const u8
*)"GROB", 4, pos
, c
->infile
->len
-pos
, &img_pos
);
185 // No more images in this file.
189 de_dbg(c
, "GROB format found at %d", (int)img_pos
);
192 if(!de_good_image_count(c
, img_count
)) break;
193 grob_text_1_image(c
, d
, img_pos
);
195 if(d
->bytes_consumed
<1) break;
196 pos
= img_pos
+ d
->bytes_consumed
;
200 de_err(c
, "Unknown or unsupported GROB format");
204 static void de_run_grob(deark
*c
, de_module_params
*mparams
)
210 d
= de_malloc(c
, sizeof(lctx
));
212 s
= de_get_ext_option(c
, "grob:planes");
214 d
->num_planes
= de_atoi64(s
);
216 if(d
->num_planes
<1) d
->num_planes
=1;
217 if(d
->num_planes
>8) {
218 de_err(c
, "Unsupported grob:planes option");
222 s
= de_get_ext_option(c
, "grob:planeorder");
224 d
->grayscale_lsb
= 1;
229 if(!de_memcmp(buf
, "HPHP", 4)) {
230 de_run_grob_binary(c
, d
);
233 de_run_grob_text(c
, d
);
240 static int de_identify_grob(deark
*c
)
245 if(buf
[0]=='H' && buf
[1]=='P' && buf
[2]=='H' && buf
[3]=='P' &&
246 buf
[4]=='4' && (buf
[5]=='8' || buf
[5]=='9') &&
247 buf
[8]==0x1e && buf
[9]==0x2b)
252 if(buf
[0]=='G' && buf
[1]=='R' && buf
[2]=='O' && buf
[3]=='B') {
259 static void de_help_grob(deark
*c
)
261 de_msg(c
, "-opt grob:planes=<n> : Treat image as grayscale");
262 de_msg(c
, "-opt grob:planeorder=l : Least-significant plane comes first");
265 void de_module_grob(deark
*c
, struct deark_module_info
*mi
)
268 mi
->desc
= "GROB - HP48/49 calculator image";
269 mi
->run_fn
= de_run_grob
;
270 mi
->identify_fn
= de_identify_grob
;
271 mi
->help_fn
= de_help_grob
;