xwd: Support a kind of RLE compression, etc.
[deark.git] / modules / xwd.c
blobc071fd348640a26cf469c6e4f516a5026d326b49
1 // This file is part of Deark.
2 // Copyright (C) 2024 Jason Summers
3 // See the file COPYING for terms of use.
5 // XWD - X-Windows screen dump
7 #include <deark-private.h>
8 DE_DECLARE_MODULE(de_module_xwd);
10 typedef struct localctx_struct_xwd {
11 u8 errflag;
12 u8 need_errmsg;
13 #define HF_HSIZE 0
14 #define HF_VER 1
15 #define HF_PIXFMT 2
16 #define HF_DEPTH 3
17 #define HF_WIDTH 4
18 #define HF_HEIGHT 5
19 #define HF_BYTE_ORDER 7
20 #define HF_BITMAP_UNIT 8
21 #define HF_BIT_ORDER 9
22 #define HF_SCANLINE_PAD 10
23 #define HF_BITS_PER_PIX 11
24 #define HF_BYTES_PER_LINE 12
25 #define HF_VCLASS 13
26 #define HF_RMASK 14
27 #define HF_GMASK 15
28 #define HF_BMASK 16
29 #define HF_BITS_PER_RGB 17
30 #define HF_CMAP_NUM_ENTRIES 18
31 #define HF_NCOLORS 19
32 UI hf[25];
34 UI vclass_adj;
35 i64 cmap_pos;
36 i64 cmap_num_entries;
37 i64 cmap_size_in_bytes;
38 i64 imgpos;
39 i64 expected_image_size;
40 i64 actual_image_size;
41 int pixel_byte_order; // 1 if LE
42 i64 bytes_per_pixel; // Not used by every image type
43 i64 bits_per_pixel; // Not used by every image type
44 i64 width, height;
45 i64 rowspan;
46 UI cmpr_meth;
48 #define XWD_IMGTYPE_GRAY 1
49 #define XWD_IMGTYPE_PALETTE 2
50 #define XWD_IMGTYPE_RGB 3
51 int imgtype;
53 UI sample_bit_shift[3];
54 i64 sample_maxval[3];
55 de_color pal[256];
56 } lctx;
58 static void read_or_construct_colormap(deark *c, lctx *d)
60 UI k;
61 UI num_entries_to_read;
62 int saved_indent_level;
63 i64 pos;
65 de_dbg_indent_save(c, &saved_indent_level);
66 if((d->imgtype==XWD_IMGTYPE_PALETTE || d->imgtype==XWD_IMGTYPE_GRAY) &&
67 (d->bits_per_pixel>=1 && d->bits_per_pixel<=8))
69 de_make_grayscale_palette(d->pal, 1ULL<<d->bits_per_pixel, 0);
72 if(d->cmap_size_in_bytes<1) goto done;
74 de_dbg(c, "colormap at %"I64_FMT, d->cmap_pos);
75 de_dbg_indent(c, 1);
77 num_entries_to_read = d->hf[HF_CMAP_NUM_ENTRIES];
78 if(num_entries_to_read>256) num_entries_to_read = 256;
80 pos = d->cmap_pos;
81 for(k=0; k<num_entries_to_read; k++) {
82 UI s;
83 u8 samp[3];
85 pos += 4;
86 for(s=0; s<3; s++) {
87 samp[s] = de_getbyte_p(&pos);
88 pos++;
90 d->pal[k] = DE_MAKE_RGB(samp[0], samp[1], samp[2]);
91 de_dbg_pal_entry(c, (i64)k, d->pal[k]);
92 pos += 2;
95 done:
96 de_dbg_indent_restore(c, saved_indent_level);
99 static const char *hnames[25] = {
100 "hsize", "ver", "pix fmt", "depth", "width",
101 "height", "xoffs", "byte order", "bitmap unit", "bitmap bit order",
102 "scanline pad", "bits/pixel", "bytes/line", "visual class", "R mask",
103 "G mask", "B mask", "bits/rgb", "cmap num entries", "ncolors",
104 "window width", "window height", "window x", "window y", "window bdrwidth" };
106 static const char *get_pixfmt_name(UI x)
108 const char *name = NULL;
110 switch(x) {
111 case 0: name = "1-bit"; break;
112 case 1: name = "1-plane"; break;
113 case 2: name = "multi-plane"; break;
115 return name?name:"?";
118 static const char *get_vclass_name(UI x)
120 const char *name = NULL;
122 switch(x) {
123 case 0: name = "grayscale"; break;
124 case 2: name = "colormapped"; break;
125 case 4: name = "truecolor"; break;
127 return name?name:"?";
130 static void interpret_header(deark *c, lctx *d)
132 u8 need_fixup_warning = 0;
133 u8 depth_1248;
134 UI k;
136 d->cmap_pos = (i64)d->hf[HF_HSIZE];
137 d->width = (i64)d->hf[HF_WIDTH];
138 d->height = (i64)d->hf[HF_HEIGHT];
139 d->pixel_byte_order = !d->hf[HF_BYTE_ORDER];
140 d->rowspan = (i64)d->hf[HF_BYTES_PER_LINE];
141 d->expected_image_size = d->height * d->rowspan;
143 depth_1248 = (d->hf[HF_DEPTH]==8 || d->hf[HF_DEPTH]==4 ||
144 d->hf[HF_DEPTH]==2 || d->hf[HF_DEPTH]==1);
146 // paletted or grayscale, typical
147 if((d->vclass_adj==0 || d->vclass_adj==2) &&
148 depth_1248 &&
149 d->hf[HF_BITS_PER_PIX]==d->hf[HF_DEPTH])
151 d->imgtype = (d->vclass_adj==0) ? XWD_IMGTYPE_GRAY : XWD_IMGTYPE_PALETTE;
152 d->bits_per_pixel = (i64)d->hf[HF_DEPTH];
155 // paletted or grayscale, with unused bits
156 if(d->imgtype==0 && (d->vclass_adj==0 || d->vclass_adj==2) &&
157 depth_1248 &&
158 d->hf[HF_BITS_PER_PIX]!=d->hf[HF_DEPTH] &&
159 d->hf[HF_BITS_PER_PIX]==8)
161 d->imgtype = (d->vclass_adj==0) ? XWD_IMGTYPE_GRAY : XWD_IMGTYPE_PALETTE;
162 d->bits_per_pixel = (i64)d->hf[HF_BITS_PER_PIX];
165 // RGB 32bits/pixel
166 if(d->imgtype==0 && d->vclass_adj==4 &&
167 d->hf[HF_BITMAP_UNIT]==32 &&
168 (d->hf[HF_BITS_PER_PIX]==24 || d->hf[HF_BITS_PER_PIX]==32) &&
169 (d->hf[HF_DEPTH]==24 || d->hf[HF_DEPTH]==32))
171 d->imgtype = XWD_IMGTYPE_RGB;
172 d->bytes_per_pixel = 4;
175 // RGB 16bits/pixel
176 if(d->imgtype==0 && d->vclass_adj==4 &&
177 (d->hf[HF_BITMAP_UNIT]==16 || d->hf[HF_BITMAP_UNIT]==32) &&
178 d->hf[HF_BITS_PER_PIX]==16 &&
179 d->hf[HF_DEPTH]==16)
181 d->imgtype = XWD_IMGTYPE_RGB;
182 d->bytes_per_pixel = 2;
185 // e.g. "MARBLES.XWD"
186 if(d->imgtype==0 && d->vclass_adj==4 &&
187 d->hf[HF_PIXFMT]==2 &&
188 d->hf[HF_DEPTH]==24 &&
189 d->hf[HF_BYTE_ORDER]==1 &&
190 d->hf[HF_BITMAP_UNIT]==8 &&
191 d->hf[HF_SCANLINE_PAD]==8 &&
192 d->hf[HF_BITS_PER_PIX]==24)
194 d->bytes_per_pixel = 4;
195 d->imgtype = XWD_IMGTYPE_RGB;
196 need_fixup_warning = 1;
199 // If RGB image looks to be defined with 4 bytes stored per pixel, but
200 // a scanline is too small for 4, something's wrong.
201 // The XWD spec doesn't explain how 24-bit RGB images should be
202 // labeled. But they exist.
203 // E.g. BlueSteel.zip/screenshot.xwd
204 if(d->imgtype==XWD_IMGTYPE_RGB && d->bytes_per_pixel==4 &&
205 d->width*d->bytes_per_pixel > d->rowspan &&
206 d->width*3 <= d->rowspan)
208 d->bytes_per_pixel = 3;
209 need_fixup_warning = 1;
212 // Decode masks if needed
213 if(d->imgtype==XWD_IMGTYPE_RGB) {
214 for(k=0; k<3; k++) {
215 UI x;
217 x = d->hf[HF_RMASK+k];
218 d->sample_maxval[k] = 255; // default
220 // TODO: Generalize this code
221 if(x == 0x000000ffU) {
222 d->sample_bit_shift[k] = 0;
224 else if(x == 0x0000ff00U) {
225 d->sample_bit_shift[k] = 8;
227 else if(x == 0x00ff0000U) {
228 d->sample_bit_shift[k] = 16;
230 else if(x == 0xff000000U) {
231 d->sample_bit_shift[k] = 24;
233 else if(x == 0xf800) {
234 d->sample_bit_shift[k] = 11;
235 d->sample_maxval[k] = 31;
237 else if(x == 0x07e0) {
238 d->sample_bit_shift[k] = 5;
239 d->sample_maxval[k] = 63;
241 else if(x == 0x001f) {
242 d->sample_bit_shift[k] = 0;
243 d->sample_maxval[k] = 31;
245 else {
246 d->errflag = 1;
247 d->need_errmsg = 1;
252 if(need_fixup_warning) {
253 de_warn(c, "Inconsistent or unusual image parameters. Attempting to correct.");
257 // Try to figure out the colormap size, and consequently the image position.
258 static void find_cmap_and_image(deark *c, lctx *d)
260 u8 need_cmap_warning = 0;
261 i64 size1, size2;
262 i64 avail_size;
264 d->cmap_num_entries = (i64)d->hf[HF_CMAP_NUM_ENTRIES];
265 d->cmap_size_in_bytes = d->cmap_num_entries * 12;
267 // It's critical that we know exactly how many entries are in the colormap.
268 // Sometimes its in one field, sometimes in another, and it's not clear
269 // how to tell which.
271 // We hope these two fields are the same.
272 if(d->hf[HF_CMAP_NUM_ENTRIES] == d->hf[HF_NCOLORS]) goto done;
274 if(d->hf[HF_NCOLORS] > d->hf[HF_CMAP_NUM_ENTRIES]) {
275 // I haven't seen this happen.
276 goto done;
279 // d->hf[HF_NCOLORS] < d->hf[HF_CMAP_NUM_ENTRIES]
281 size1 = d->hf[HF_NCOLORS] * 12; // Note, size1 is smaller than size2
282 size2 = d->hf[HF_CMAP_NUM_ENTRIES] * 12;
283 avail_size = c->infile->len - d->expected_image_size - d->cmap_pos;
285 if(size2==avail_size) {
286 goto done;
288 if(size1==avail_size) {
289 d->cmap_num_entries = (i64)d->hf[HF_NCOLORS];
290 goto done;
292 if(size2>avail_size && size1<avail_size) {
293 d->cmap_num_entries = (i64)d->hf[HF_NCOLORS];
294 need_cmap_warning = 1;
295 goto done;
298 need_cmap_warning = 1;
300 done:
301 if(need_cmap_warning) {
302 de_warn(c, "Can't reliably locate the image. Might not be decoded correctly.");
304 d->cmap_size_in_bytes = d->cmap_num_entries * 12;
305 d->imgpos = d->cmap_pos + d->cmap_size_in_bytes;
306 d->actual_image_size = c->infile->len - d->imgpos;
309 static void do_header(deark *c, lctx *d)
311 i64 pos;
312 int saved_indent_level;
313 UI k;
315 de_dbg_indent_save(c, &saved_indent_level);
316 pos = 0;
317 de_dbg(c, "header at %"I64_FMT, pos);
318 de_dbg_indent(c, 1);
320 for(k=0; k<25; k++) {
321 const char *name = NULL;
323 d->hf[k] = (UI)de_getu32be_p(&pos);
325 if(k==HF_PIXFMT) {
326 name = get_pixfmt_name(d->hf[k]);
328 else if(k==HF_BYTE_ORDER) {
329 if(d->hf[k]==0) name = "LE";
330 else name = "BE";
332 else if(k==HF_BIT_ORDER) {
333 if(d->hf[k]==0) name = "lsb first";
334 else name = "msb first";
336 else if(k==HF_VCLASS) {
337 d->vclass_adj = d->hf[k] & 0xfffffffeU;
338 name = get_vclass_name(d->vclass_adj);
341 if(k>=HF_RMASK && k<=HF_BMASK) {
342 de_dbg(c, "%s: 0x%08x", hnames[k], d->hf[k]);
344 else {
345 if(name) {
346 de_dbg(c, "%s: %u (%s)", hnames[k], d->hf[k], name);
348 else {
349 de_dbg(c, "%s: %u", hnames[k], d->hf[k]);
354 if(d->hf[HF_HSIZE]<100) {
355 d->errflag = 1;
356 d->need_errmsg = 1;
357 goto done;
360 if(d->hf[HF_VER]!=7) {
361 d->errflag = 1;
362 d->need_errmsg = 1;
363 goto done;
366 if(d->hf[HF_WIDTH]>1000000 || d->hf[HF_HEIGHT]>1000000 ||
367 d->hf[HF_BYTES_PER_LINE]>1000000)
369 d->errflag = 1;
370 d->need_errmsg = 1;
371 goto done;
374 interpret_header(c, d);
375 if(d->errflag) goto done;
376 find_cmap_and_image(c, d);
378 done:
379 de_dbg_indent_restore(c, saved_indent_level);
382 static void read_image_rgb(deark *c, lctx *d, dbuf *inf, i64 inf_pos1,
383 de_bitmap *img)
385 i64 i, j;
387 for(j=0; j<d->height; j++) {
388 i64 rowpos;
390 rowpos = inf_pos1 + j*d->rowspan;
392 for(i=0; i<d->width; i++) {
393 de_color clr = 0;
394 u32 v;
395 u32 cs[3];
396 UI k;
398 if(d->bytes_per_pixel==4) {
399 v = (u32)dbuf_getu32x(inf, rowpos+i*d->bytes_per_pixel, d->pixel_byte_order);
401 else {
402 v = (u32)dbuf_getint_ext(inf, rowpos+i*d->bytes_per_pixel,
403 (UI)d->bytes_per_pixel, d->pixel_byte_order, 0);
405 for(k=0; k<3; k++) {
406 UI v2;
408 v2 = v & d->hf[HF_RMASK+k];
409 v2 = v2 >> d->sample_bit_shift[k];
411 if(d->sample_maxval[k]==255) {
412 cs[k] = (u8)v2;
414 else {
415 cs[k] = de_scale_n_to_255(d->sample_maxval[k], v2);
418 clr = DE_MAKE_RGB(cs[0], cs[1], cs[2]);
419 de_bitmap_setpixel_rgb(img, i, j, clr);
424 static void read_image_colormapped(deark *c, lctx *d, dbuf *inf, i64 inf_pos,
425 de_bitmap *img)
427 UI flags = 0;
429 if(d->bits_per_pixel<1 || d->bits_per_pixel>8) return;
431 if(d->hf[HF_BIT_ORDER]==0) {
432 flags |= 0x01;
435 de_convert_image_paletted(inf, inf_pos, d->bits_per_pixel,
436 d->rowspan, d->pal, img, flags);
439 static void read_image_grayscale(deark *c, lctx *d, dbuf *inf, i64 inf_pos,
440 de_bitmap *img)
442 read_image_colormapped(c, d, inf, inf_pos, img);
445 static void decompress_pvwave_rle(deark *c, lctx *d, dbuf *unc_pixels)
447 i64 pos = d->imgpos;
448 i64 endpos = c->infile->len;
449 i64 row_padding;
450 i64 nbytes_written = 0;
451 i64 xpos = 0;
453 row_padding = d->rowspan - d->width;
454 if(row_padding<0) goto done;
456 while(1) {
457 i64 count;
458 u8 b;
460 if(pos+3 > endpos) goto done;
461 if(nbytes_written >= d->expected_image_size) goto done;
463 count = de_getu16be_p(&pos);
464 b = de_getbyte_p(&pos);
465 dbuf_write_run(unc_pixels, b, count);
466 nbytes_written += count;
467 xpos += count;
469 if(xpos >= d->width) {
470 if(row_padding!=0 && xpos==d->width) {
471 // It's stupid that we have to do this.
472 dbuf_write_run(unc_pixels, 0x00, row_padding);
473 nbytes_written += row_padding;
475 xpos = 0;
478 done:
479 de_dbg(c, "decompressed %"I64_FMT" bytes to %"I64_FMT,
480 pos-d->imgpos, nbytes_written);
481 dbuf_flush(unc_pixels);
484 static void detect_compression(deark *c, lctx *d)
486 i64 count;
488 if(d->actual_image_size == d->expected_image_size) goto done;
489 if(d->actual_image_size%3 != 0) goto done;
491 if(d->hf[HF_PIXFMT]==2 &&
492 d->hf[HF_DEPTH]==8 &&
493 d->hf[HF_BYTE_ORDER]==0 &&
494 d->hf[HF_BITMAP_UNIT]==32 &&
495 d->hf[HF_BIT_ORDER]==0 &&
496 d->hf[HF_SCANLINE_PAD]==32 &&
497 d->hf[HF_BITS_PER_PIX]==8 &&
498 d->hf[HF_VCLASS]==3 &&
499 d->hf[HF_BITS_PER_RGB]==8)
503 else {
504 goto done;
507 // TODO: We could do better by checking more than just the first
508 // compression code.
509 count = de_getu16be(d->imgpos);
510 if(count<1 || count>d->width) {
511 goto done;
514 d->cmpr_meth = 100;
515 done:
516 if(d->cmpr_meth==100) {
517 de_dbg(c, "detected PV-Wave RLE compression");
521 static void do_xwd_image(deark *c, lctx *d)
523 int bypp;
524 de_bitmap *img = NULL;
525 dbuf *unc_pixels = NULL;
526 dbuf *inf = c->infile; // This is a copy -- do not close
527 i64 inf_pos = d->imgpos;
528 int saved_indent_level;
530 de_dbg_indent_save(c, &saved_indent_level);
531 de_dbg(c, "image at %"I64_FMT, d->imgpos);
532 de_dbg_indent(c, 1);
533 if(!de_good_image_dimensions(c, d->width, d->height)) goto done;
535 detect_compression(c, d);
537 if(d->cmpr_meth!=0) {
538 unc_pixels = dbuf_create_membuf(c, d->expected_image_size, 0x1);
539 dbuf_enable_wbuffer(unc_pixels);
540 decompress_pvwave_rle(c, d, unc_pixels);
541 inf = unc_pixels;
542 inf_pos = 0;
545 if(d->cmpr_meth==0) {
546 if(d->imgpos + d->expected_image_size > c->infile->len+16) {
547 de_err(c, "Bad or truncated XWD file");
548 d->errflag = 1;
549 goto done;
553 if(d->imgtype==XWD_IMGTYPE_RGB &&
554 (d->bytes_per_pixel==2 || d->bytes_per_pixel==3 || d->bytes_per_pixel==4))
558 else if(d->imgtype==XWD_IMGTYPE_PALETTE &&
559 d->bits_per_pixel>0)
563 else if(d->imgtype==XWD_IMGTYPE_GRAY &&
564 d->bits_per_pixel>0)
568 else {
569 d->errflag = 1;
570 d->need_errmsg = 1;
571 goto done;
574 bypp = 3;
575 if(d->imgtype==XWD_IMGTYPE_GRAY || d->imgtype==XWD_IMGTYPE_PALETTE) {
576 if(de_is_grayscale_palette(d->pal, 256)) {
577 bypp = 1;
580 img = de_bitmap_create(c, d->width, d->height, bypp);
582 if(d->imgtype==XWD_IMGTYPE_RGB) {
583 read_image_rgb(c, d, inf, inf_pos, img);
585 else if(d->imgtype==XWD_IMGTYPE_PALETTE) {
586 read_image_colormapped(c, d, inf, inf_pos, img);
588 else if(d->imgtype==XWD_IMGTYPE_GRAY) {
589 read_image_grayscale(c, d, inf, inf_pos, img);
592 de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_OPT_IMAGE);
594 done:
595 if(d->need_errmsg) {
596 de_err(c, "Unsupported image type");
597 d->need_errmsg = 0;
599 de_bitmap_destroy(img);
600 dbuf_close(unc_pixels);
601 de_dbg_indent_restore(c, saved_indent_level);
604 static void de_run_xwd(deark *c, de_module_params *mparams)
606 lctx *d = NULL;
608 d = de_malloc(c, sizeof(lctx));
609 do_header(c, d);
610 if(d->errflag) goto done;
611 // TODO?: Do something with the name that may appear after the header.
613 read_or_construct_colormap(c, d);
615 do_xwd_image(c, d);
617 done:
618 if(d) {
619 if(d->need_errmsg) {
620 de_err(c, "Bad or unsupported XWD file");
622 de_free(c, d);
626 static int de_identify_xwd(deark *c)
628 int has_ext = 0;
629 i64 hdrsize;
630 i64 cmapn;
631 UI n;
633 // TODO: Identification could be improved.
635 n = (UI)de_getu32be(4); // version
636 if (n!=7) return 0;
638 hdrsize = (UI)de_getu32be(0);
639 if(hdrsize<100 || hdrsize>500) return 0;
641 n = (UI)de_getu32be(8); // pixfmt
642 if(n>2) return 0;
644 n = (UI)de_getu32be(12); // depth
645 if(n<1 || n>32) return 0;
647 cmapn = de_getu32be(76);
648 if(cmapn>512) return 0;
649 if(hdrsize + 12*cmapn > c->infile->len) return 0;
651 has_ext = de_input_file_has_ext(c, "xwd");
652 if(has_ext) return 100;
653 return 75;
656 void de_module_xwd(deark *c, struct deark_module_info *mi)
658 mi->id = "xwd";
659 mi->desc = "X-Windows screen dump";
660 mi->run_fn = de_run_xwd;
661 mi->identify_fn = de_identify_xwd;