iccprofile: Refactoring
[deark.git] / modules / macpaint.c
blob125266518a6ae23f5d4c7ba7ccc7375dacdffe51
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // MacPaint image format
7 #include <deark-config.h>
8 #include <deark-private.h>
9 #include <deark-fmtutil.h>
10 DE_DECLARE_MODULE(de_module_macpaint);
12 #define MACPAINT_WIDTH 576
13 #define MACPAINT_HEIGHT 720
14 #define MACPAINT_IMAGE_BYTES ((MACPAINT_WIDTH/8)*MACPAINT_HEIGHT)
16 typedef struct localctx_struct {
17 int has_macbinary_header;
18 u8 df_known, rf_known;
19 i64 expected_dfpos, expected_rfpos;
20 i64 expected_dflen, expected_rflen;
21 de_ucstring *filename;
22 struct de_timestamp mod_time_from_macbinary;
23 } lctx;
25 static void do_read_bitmap(deark *c, lctx *d, i64 pos)
27 i64 ver_num;
28 i64 cmpr_bytes_consumed = 0;
29 dbuf *unc_pixels = NULL;
30 de_finfo *fi = NULL;
32 ver_num = de_getu32be(pos);
33 de_dbg(c, "version number: %u", (unsigned int)ver_num);
34 if(ver_num!=0 && ver_num!=2 && ver_num!=3) {
35 de_warn(c, "Unrecognized version number: %u", (unsigned int)ver_num);
38 pos += 512;
40 unc_pixels = dbuf_create_membuf(c, MACPAINT_IMAGE_BYTES, 1);
42 fmtutil_decompress_packbits(c->infile, pos, c->infile->len - pos,
43 unc_pixels, &cmpr_bytes_consumed);
45 de_dbg(c, "decompressed %d to %d bytes", (int)cmpr_bytes_consumed,
46 (int)unc_pixels->len);
48 if(d->df_known) {
49 if(pos+cmpr_bytes_consumed > d->expected_dfpos+d->expected_dflen) {
50 de_warn(c, "Image (ends at %"I64_FMT") goes beyond end of "
51 "MacBinary data fork (ends at %"I64_FMT")",
52 pos+cmpr_bytes_consumed, d->expected_dfpos+d->expected_dflen);
56 if(unc_pixels->len < MACPAINT_IMAGE_BYTES) {
57 de_warn(c, "Image decompressed to %d bytes, expected %d.",
58 (int)unc_pixels->len, (int)MACPAINT_IMAGE_BYTES);
61 fi = de_finfo_create(c);
62 if(d->filename && c->filenames_from_file) {
63 de_finfo_set_name_from_ucstring(c, fi, d->filename, 0);
66 if(d->mod_time_from_macbinary.is_valid) {
67 fi->internal_mod_time = d->mod_time_from_macbinary;
70 de_convert_and_write_image_bilevel(unc_pixels, 0,
71 MACPAINT_WIDTH, MACPAINT_HEIGHT, MACPAINT_WIDTH/8,
72 DE_CVTF_WHITEISZERO, fi, 0);
74 dbuf_close(unc_pixels);
75 de_finfo_destroy(c, fi);
78 // A function to help determine if the file has a MacBinary header.
79 // Each row is RLE-compressed independently, so once we assume one possibility
80 // or the other, we can do sanity checks to see if any code crosses a row
81 // boundary, or the image is too small to be a MacPaint image.
82 // It's inefficient to decompress whole image -- twice -- just to try to
83 // figure this out, but hopefully it's pretty reliable.
84 // Returns an integer (0, 1, 2) reflecting the likelihood that this is the
85 // correct position.
86 static int valid_file_at(deark *c, lctx *d, i64 pos1)
88 u8 b;
89 i64 x;
90 i64 xpos, ypos;
91 i64 pos;
92 i64 imgstart;
94 imgstart = pos1+512;
96 // Minimum bytes per row is 2.
97 // For a valid (non-truncated) file, file size must be at least
98 // pos1 + 512 + 2*MACPAINT_HEIGHT. But we want to tolerate truncated
99 // files as well.
100 if(c->infile->len < imgstart + 4) {
101 de_dbg(c, "file too small");
102 return 0;
105 xpos=0; ypos=0;
106 pos = pos1 + 512;
108 while(pos < c->infile->len) {
109 if(ypos>=MACPAINT_HEIGHT) {
110 break;
113 b = de_getbyte(pos);
114 pos++;
116 if(b<=127) {
117 x = 1+(i64)b;
118 pos+=x;
119 xpos+=8*x;
120 if(xpos==MACPAINT_WIDTH) {
121 xpos=0;
122 ypos++;
124 else if(xpos>MACPAINT_WIDTH) {
125 de_dbg(c, "image at offset %d: literal too long", (int)imgstart);
126 return 0;
129 else if(b>=129) {
130 x = 257 - (i64)b;
131 pos++;
132 xpos+=8*x;
133 if(xpos==MACPAINT_WIDTH) {
134 xpos=0;
135 ypos++;
137 else if(xpos>MACPAINT_WIDTH) {
138 de_dbg(c, "image at offset %d: run too long", (int)imgstart);
139 return 0;
144 if(xpos==0 && ypos==MACPAINT_HEIGHT) {
145 de_dbg(c, "image at offset %d decodes okay", (int)imgstart);
146 return 2;
149 de_dbg(c, "image at offset %d: premature end of file (x=%d, y=%d)", (int)imgstart, (int)xpos, (int)ypos);
150 return 1;
153 static const char *get_pattern_set_info(u32 patcrc, int *is_blank)
155 *is_blank = 0;
156 switch(patcrc) {
157 case 0x284a7a15: return "variant 1";
158 case 0x33d2d8d6: return "standard";
159 case 0x47514647: *is_blank = 1; return "blank";
160 case 0xb5348fd2: *is_blank = 1; return "blank variant 1";
162 return "unrecognized";
165 // Some MacPaint files contain a collection of brush patterns.
166 // Essentially, MacPaint saves workspace settings inside image files.
167 // (But these patterns are the only setting.)
168 static void do_read_patterns(deark *c, lctx *d, i64 pos)
170 i64 cell;
171 i64 i, j;
172 u8 x;
173 const i64 dispwidth = 19;
174 const i64 dispheight = 17;
175 i64 xpos, ypos;
176 int is_blank;
177 de_bitmap *pat = NULL;
178 u32 patcrc;
179 const char *patsetname;
180 de_finfo *fi = NULL;
181 de_ucstring *tmpname = NULL;
182 struct de_crcobj *crc32o;
184 pos += 4;
186 crc32o = de_crcobj_create(c, DE_CRCOBJ_CRC32_IEEE);
187 de_crcobj_addslice(crc32o, c->infile, pos, 38*8);
188 patcrc = de_crcobj_getval(crc32o);
189 de_crcobj_destroy(crc32o);
190 patsetname = get_pattern_set_info(patcrc, &is_blank);
191 de_dbg(c, "brush patterns crc: 0x%08x (%s)", (unsigned int)patcrc, patsetname);
193 if(c->extract_level<2) {
194 goto done;
197 if(is_blank) {
198 de_dbg(c, "brush patterns are blank: not extracting");
199 goto done;
202 pat = de_bitmap_create(c, (dispwidth+1)*19+1, (dispheight+1)*2+1, 1);
204 for(cell=0; cell<38; cell++) {
205 xpos = (dispwidth+1)*(cell%19)+1;
206 ypos = (dispheight+1)*(cell/19)+1;
208 for(j=0; j<dispheight; j++) {
209 for(i=0; i<dispwidth; i++) {
210 // TODO: Figure out the proper "brush origin" of these patterns.
211 // Some of them may be shifted differently than MacPaint displays them.
212 x = de_get_bits_symbol(c->infile, 1, pos+cell*8+j%8, i%8);
214 // 0 = white. Only need to set the white pixels, since de_bitmap
215 // pixels default to black.
216 if(x==0) {
217 de_bitmap_setpixel_gray(pat, xpos+i, ypos+j, 255);
223 tmpname = ucstring_create(c);
224 if(d->filename && c->filenames_from_file) {
225 ucstring_append_ucstring(tmpname, d->filename);
226 ucstring_append_sz(tmpname, ".", DE_ENCODING_LATIN1);
228 ucstring_append_sz(tmpname, "pat", DE_ENCODING_LATIN1);
229 fi = de_finfo_create(c);
230 de_finfo_set_name_from_ucstring(c, fi, tmpname, 0);
231 de_bitmap_write_to_file_finfo(pat, fi, DE_CREATEFLAG_IS_AUX);
233 done:
234 de_bitmap_destroy(pat);
235 de_finfo_destroy(c, fi);
236 ucstring_destroy(tmpname);
239 // Not many MacPaint-in-MacBinary files have a resource fork, but a few do.
240 static void do_decode_rsrc(deark *c, lctx *d)
242 if(!d->rf_known) return;
243 if(d->expected_rflen<1) return;
244 if(d->expected_rfpos+d->expected_rflen > c->infile->len) {
245 return;
247 de_dbg(c, "resource fork at %"I64_FMT", len=%"I64_FMT, d->expected_rfpos, d->expected_rflen);
248 de_dbg_indent(c, 1);
249 de_run_module_by_id_on_slice2(c, "macrsrc", NULL, c->infile,
250 d->expected_rfpos, d->expected_rflen);
251 de_dbg_indent(c, -1);
254 static void do_macbinary(deark *c, lctx *d)
256 u8 b0, b1;
257 de_module_params *mparams = NULL;
259 b0 = de_getbyte(0);
260 b1 = de_getbyte(1);
262 // Instead of a real MacBinary header, a few macpaint files just have
263 // 128 NUL bytes, or something like that. So we'll skip MacBinary parsing
264 // in some cases.
265 if(b0!=0) goto done;
266 if(b1<1 || b1>63) goto done;
268 de_dbg(c, "MacBinary header at %d", 0);
269 de_dbg_indent(c, 1);
270 mparams = de_malloc(c, sizeof(de_module_params));
271 mparams->in_params.codes = "D"; // = decode only, don't extract
272 mparams->out_params.fi = de_finfo_create(c); // A temporary finfo object
273 mparams->out_params.fi->name_other = ucstring_create(c);
274 de_run_module_by_id_on_slice(c, "macbinary", mparams, c->infile, 0, c->infile->len);
275 de_dbg_indent(c, -1);
277 if(mparams->out_params.uint1>0) {
278 d->df_known = 1;
279 d->expected_dfpos = (i64)mparams->out_params.uint1;
280 d->expected_dflen = (i64)mparams->out_params.uint2;
282 if(mparams->out_params.uint3>0) {
283 d->rf_known = 1;
284 d->expected_rfpos = (i64)mparams->out_params.uint3;
285 d->expected_rflen = (i64)mparams->out_params.uint4;
288 if(mparams->out_params.fi->timestamp[DE_TIMESTAMPIDX_MODIFY].is_valid) {
289 d->mod_time_from_macbinary = mparams->out_params.fi->timestamp[DE_TIMESTAMPIDX_MODIFY];
292 if(d->df_known) {
293 if(d->expected_dfpos+d->expected_dflen>c->infile->len) {
294 de_warn(c, "MacBinary data fork (ends at %"I64_FMT") "
295 "goes past end of file (%"I64_FMT")",
296 d->expected_dfpos+d->expected_dflen, c->infile->len);
297 d->df_known = 0;
301 if(ucstring_isnonempty(mparams->out_params.fi->name_other) && !d->filename) {
302 d->filename = ucstring_clone(mparams->out_params.fi->name_other);
305 if(d->rf_known) {
306 do_decode_rsrc(c, d);
309 done:
310 if(mparams) {
311 de_finfo_destroy(c, mparams->out_params.fi);
312 de_free(c, mparams);
316 static void de_run_macpaint(deark *c, de_module_params *mparams)
318 lctx *d;
319 i64 pos;
321 d = de_malloc(c, sizeof(lctx));
322 d->has_macbinary_header = de_get_ext_option_bool(c, "macpaint:macbinary", -1);
324 if(d->has_macbinary_header == -1) {
325 int v512;
326 int v640;
327 de_dbg(c, "trying to determine if file has a MacBinary header");
329 de_dbg_indent(c, 1);
330 de_dbg(c, "checking for image at offset 512");
331 de_dbg_indent(c, 1);
332 v512 = valid_file_at(c, d, 0);
333 de_dbg_indent(c, -1);
334 de_dbg(c, "checking for image at offset 640");
335 de_dbg_indent(c, 1);
336 v640 = valid_file_at(c, d, 128);
337 de_dbg_indent(c, -1);
338 de_dbg_indent(c, -1);
340 if(v512 > v640) {
341 de_dbg(c, "assuming it has no MacBinary header");
342 d->has_macbinary_header = 0;
344 else if(v640 > v512) {
345 de_dbg(c, "assuming it has a MacBinary header");
346 d->has_macbinary_header = 1;
348 else if(v512 && v640) {
349 de_warn(c, "Can't determine if this file has a MacBinary header. "
350 "Try \"-opt macpaint:macbinary=0\".");
351 d->has_macbinary_header = 1;
353 else {
354 de_warn(c, "This is probably not a MacPaint file.");
355 d->has_macbinary_header = 1;
359 if(d->has_macbinary_header)
360 de_declare_fmt(c, "MacPaint with MacBinary header");
361 else
362 de_declare_fmt(c, "MacPaint without MacBinary header");
364 pos = 0;
365 if(d->has_macbinary_header) {
366 do_macbinary(c, d);
367 pos += 128;
370 do_read_bitmap(c, d, pos);
372 do_read_patterns(c, d, pos);
374 if(d) {
375 ucstring_destroy(d->filename);
376 de_free(c, d);
380 // Note: This must be coordinated with the macbinary detection routine.
381 static int de_identify_macpaint(deark *c)
383 u8 buf[8];
385 de_read(buf, 65, 8);
387 // Not all MacPaint files can be easily identified, but this will work
388 // for some of them.
389 if(!de_memcmp(buf, "PNTG", 4)) {
390 if(c->detection_data->is_macbinary) return 100;
391 if(!de_memcmp(&buf[4], "MPNT", 4)) return 80;
392 return 70;
395 if(de_input_file_has_ext(c, "mac")) return 10;
396 if(de_input_file_has_ext(c, "macp")) return 15;
397 if(de_input_file_has_ext(c, "pntg")) return 15;
398 return 0;
401 static void de_help_macpaint(deark *c)
403 de_msg(c, "-opt macpaint:macbinary=<0|1> : Assume file doesn't/does have "
404 "a MacBinary header");
405 de_msg(c, "-m macbinary : Extract from MacBinary container, instead of "
406 "decoding");
409 void de_module_macpaint(deark *c, struct deark_module_info *mi)
411 mi->id = "macpaint";
412 mi->desc = "MacPaint image";
413 mi->run_fn = de_run_macpaint;
414 mi->identify_fn = de_identify_macpaint;
415 mi->help_fn = de_help_macpaint;