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
{
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
29 #define HF_BITS_PER_RGB 17
30 #define HF_CMAP_NUM_ENTRIES 18
37 i64 cmap_size_in_bytes
;
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
48 #define XWD_IMGTYPE_GRAY 1
49 #define XWD_IMGTYPE_PALETTE 2
50 #define XWD_IMGTYPE_RGB 3
53 UI sample_bit_shift
[3];
58 static void read_or_construct_colormap(deark
*c
, lctx
*d
)
61 UI num_entries_to_read
;
62 int saved_indent_level
;
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
);
77 num_entries_to_read
= d
->hf
[HF_CMAP_NUM_ENTRIES
];
78 if(num_entries_to_read
>256) num_entries_to_read
= 256;
81 for(k
=0; k
<num_entries_to_read
; k
++) {
87 samp
[s
] = de_getbyte_p(&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
]);
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
;
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
;
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;
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) &&
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) &&
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
];
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;
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 &&
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
) {
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;
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;
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.
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
) {
288 if(size1
==avail_size
) {
289 d
->cmap_num_entries
= (i64
)d
->hf
[HF_NCOLORS
];
292 if(size2
>avail_size
&& size1
<avail_size
) {
293 d
->cmap_num_entries
= (i64
)d
->hf
[HF_NCOLORS
];
294 need_cmap_warning
= 1;
298 need_cmap_warning
= 1;
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
)
312 int saved_indent_level
;
315 de_dbg_indent_save(c
, &saved_indent_level
);
317 de_dbg(c
, "header at %"I64_FMT
, pos
);
320 for(k
=0; k
<25; k
++) {
321 const char *name
= NULL
;
323 d
->hf
[k
] = (UI
)de_getu32be_p(&pos
);
326 name
= get_pixfmt_name(d
->hf
[k
]);
328 else if(k
==HF_BYTE_ORDER
) {
329 if(d
->hf
[k
]==0) name
= "LE";
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
]);
346 de_dbg(c
, "%s: %u (%s)", hnames
[k
], d
->hf
[k
], name
);
349 de_dbg(c
, "%s: %u", hnames
[k
], d
->hf
[k
]);
354 if(d
->hf
[HF_HSIZE
]<100) {
360 if(d
->hf
[HF_VER
]!=7) {
366 if(d
->hf
[HF_WIDTH
]>1000000 || d
->hf
[HF_HEIGHT
]>1000000 ||
367 d
->hf
[HF_BYTES_PER_LINE
]>1000000)
374 interpret_header(c
, d
);
375 if(d
->errflag
) goto done
;
376 find_cmap_and_image(c
, d
);
379 de_dbg_indent_restore(c
, saved_indent_level
);
382 static void read_image_rgb(deark
*c
, lctx
*d
, dbuf
*inf
, i64 inf_pos1
,
387 for(j
=0; j
<d
->height
; j
++) {
390 rowpos
= inf_pos1
+ j
*d
->rowspan
;
392 for(i
=0; i
<d
->width
; i
++) {
398 if(d
->bytes_per_pixel
==4) {
399 v
= (u32
)dbuf_getu32x(inf
, rowpos
+i
*d
->bytes_per_pixel
, d
->pixel_byte_order
);
402 v
= (u32
)dbuf_getint_ext(inf
, rowpos
+i
*d
->bytes_per_pixel
,
403 (UI
)d
->bytes_per_pixel
, d
->pixel_byte_order
, 0);
408 v2
= v
& d
->hf
[HF_RMASK
+k
];
409 v2
= v2
>> d
->sample_bit_shift
[k
];
411 if(d
->sample_maxval
[k
]==255) {
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
,
429 if(d
->bits_per_pixel
<1 || d
->bits_per_pixel
>8) return;
431 if(d
->hf
[HF_BIT_ORDER
]==0) {
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
,
442 read_image_colormapped(c
, d
, inf
, inf_pos
, img
);
445 static void decompress_pvwave_rle(deark
*c
, lctx
*d
, dbuf
*unc_pixels
)
448 i64 endpos
= c
->infile
->len
;
450 i64 nbytes_written
= 0;
453 row_padding
= d
->rowspan
- d
->width
;
454 if(row_padding
<0) goto done
;
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
;
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
;
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
)
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)
507 // TODO: We could do better by checking more than just the first
509 count
= de_getu16be(d
->imgpos
);
510 if(count
<1 || count
>d
->width
) {
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
)
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
);
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
);
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");
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
&&
563 else if(d
->imgtype
==XWD_IMGTYPE_GRAY
&&
575 if(d
->imgtype
==XWD_IMGTYPE_GRAY
|| d
->imgtype
==XWD_IMGTYPE_PALETTE
) {
576 if(de_is_grayscale_palette(d
->pal
, 256)) {
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
);
596 de_err(c
, "Unsupported image type");
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
)
608 d
= de_malloc(c
, sizeof(lctx
));
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
);
620 de_err(c
, "Bad or unsupported XWD file");
626 static int de_identify_xwd(deark
*c
)
633 // TODO: Identification could be improved.
635 n
= (UI
)de_getu32be(4); // version
638 hdrsize
= (UI
)de_getu32be(0);
639 if(hdrsize
<100 || hdrsize
>500) return 0;
641 n
= (UI
)de_getu32be(8); // pixfmt
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;
656 void de_module_xwd(deark
*c
, struct deark_module_info
*mi
)
659 mi
->desc
= "X-Windows screen dump";
660 mi
->run_fn
= de_run_xwd
;
661 mi
->identify_fn
= de_identify_xwd
;