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
49 #define XWD_IMGTYPE_PAL_OR_GRAY 1
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_PAL_OR_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 // Always sets shift to a number 0 to 31.
131 // Always sets maxval to a positive number, not too large.
132 static void decode_bitfield(UI n
, UI
*pshift
, i64
*pmaxval
)
137 UI count_of_1_bits
= 0;
140 for(k
=0; k
<=31; k
++) {
142 if(count_of_1_bits
==0) {
150 if(count_of_1_bits
==0 || count_of_1_bits
>16) goto done
;
164 static void interpret_header(deark
*c
, lctx
*d
)
166 u8 need_fixup_warning
= 0;
170 d
->cmap_pos
= (i64
)d
->hf
[HF_HSIZE
];
171 d
->width
= (i64
)d
->hf
[HF_WIDTH
];
172 d
->height
= (i64
)d
->hf
[HF_HEIGHT
];
173 d
->pixel_byte_order
= !d
->hf
[HF_BYTE_ORDER
];
174 d
->nplanes
= 1; // tentative
175 d
->rowspan
= (i64
)d
->hf
[HF_BYTES_PER_LINE
];
177 depth_1248
= (d
->hf
[HF_DEPTH
]==8 || d
->hf
[HF_DEPTH
]==4 ||
178 d
->hf
[HF_DEPTH
]==2 || d
->hf
[HF_DEPTH
]==1);
180 // paletted or grayscale, planar
182 (d
->vclass_adj
==0 || d
->vclass_adj
==2) &&
183 d
->hf
[HF_PIXFMT
]==1 &&
184 d
->hf
[HF_BITS_PER_PIX
]==1 &&
185 (d
->hf
[HF_DEPTH
]>=1 && d
->hf
[HF_DEPTH
]<=8))
187 d
->imgtype
= XWD_IMGTYPE_PAL_OR_GRAY
;
188 d
->bits_per_pixel
= (i64
)d
->hf
[HF_DEPTH
];
189 d
->nplanes
= (i64
)d
->hf
[HF_DEPTH
];
192 // paletted or grayscale, typical
194 (d
->vclass_adj
==0 || d
->vclass_adj
==2) &&
196 d
->hf
[HF_BITS_PER_PIX
]==d
->hf
[HF_DEPTH
])
198 d
->imgtype
= XWD_IMGTYPE_PAL_OR_GRAY
;
199 d
->bits_per_pixel
= (i64
)d
->hf
[HF_BITS_PER_PIX
];
202 // paletted or grayscale, with unused bits (?)
203 if(d
->imgtype
==0 && (d
->vclass_adj
==0 || d
->vclass_adj
==2) &&
205 d
->hf
[HF_BITS_PER_PIX
]>d
->hf
[HF_DEPTH
] &&
206 d
->hf
[HF_BITS_PER_PIX
]==8)
208 d
->imgtype
= XWD_IMGTYPE_PAL_OR_GRAY
;
209 d
->bits_per_pixel
= (i64
)d
->hf
[HF_BITS_PER_PIX
];
212 // RGB 24 or 32 bits/pixel
213 if(d
->imgtype
==0 && d
->vclass_adj
==4 &&
214 (d
->hf
[HF_BITS_PER_PIX
]==24 || d
->hf
[HF_BITS_PER_PIX
]==32) &&
215 (d
->hf
[HF_DEPTH
]==24 || d
->hf
[HF_DEPTH
]==32))
217 d
->imgtype
= XWD_IMGTYPE_RGB
;
218 // HF_BITS_PER_PIX is usually correct. We'll change it later if
220 d
->bits_per_pixel
= (i64
)d
->hf
[HF_BITS_PER_PIX
];
221 d
->bytes_per_pixel
= d
->bits_per_pixel
/8;
225 if(d
->imgtype
==0 && d
->vclass_adj
==4 &&
226 d
->hf
[HF_BITS_PER_PIX
]==16 &&
229 d
->imgtype
= XWD_IMGTYPE_RGB
;
230 d
->bytes_per_pixel
= 2;
233 // e.g. "MARBLES.XWD"
234 if(d
->imgtype
==XWD_IMGTYPE_RGB
&&
235 d
->bytes_per_pixel
==3 &&
236 d
->hf
[HF_SCANLINE_PAD
]==8 &&
237 d
->hf
[HF_WIDTH
]*4 == d
->hf
[HF_BYTES_PER_LINE
])
239 d
->bytes_per_pixel
= 4;
240 d
->bits_per_pixel
= 0;
241 d
->imgtype
= XWD_IMGTYPE_RGB
;
242 need_fixup_warning
= 1;
245 // Decode masks if needed
246 if(d
->imgtype
==XWD_IMGTYPE_RGB
) {
248 decode_bitfield(d
->hf
[HF_RMASK
+k
], &d
->sample_bit_shift
[k
],
249 &d
->sample_maxval
[k
]);
253 if(need_fixup_warning
) {
254 de_warn(c
, "Inconsistent or unusual image parameters. Attempting to correct.");
257 d
->expected_image_size
= d
->height
* d
->rowspan
* d
->nplanes
;
259 if(d
->bits_per_pixel
==0) {
260 d
->bits_per_pixel
= 8*d
->bytes_per_pixel
;
263 de_dbg(c
, "interpreted bits/pixel: %d", (int)d
->bits_per_pixel
);
267 // Try to figure out the colormap size, and consequently the image position.
268 static void find_cmap_and_image(deark
*c
, lctx
*d
)
270 u8 need_cmap_warning
= 0;
274 d
->cmap_num_entries
= (i64
)d
->hf
[HF_CMAP_NUM_ENTRIES
];
275 d
->cmap_size_in_bytes
= d
->cmap_num_entries
* 12;
277 // It's critical that we know exactly how many entries are in the colormap.
278 // Sometimes its in one field, sometimes in another, and it's not clear
279 // how to tell which.
281 // We hope these two fields are the same.
282 if(d
->hf
[HF_CMAP_NUM_ENTRIES
] == d
->hf
[HF_NCOLORS
]) goto done
;
284 if(d
->hf
[HF_NCOLORS
] > d
->hf
[HF_CMAP_NUM_ENTRIES
]) {
285 // I haven't seen this happen.
289 // d->hf[HF_NCOLORS] < d->hf[HF_CMAP_NUM_ENTRIES]
291 size1
= (i64
)d
->hf
[HF_NCOLORS
] * 12; // Note, size1 is smaller than size2
292 size2
= (i64
)d
->hf
[HF_CMAP_NUM_ENTRIES
] * 12;
293 avail_size
= c
->infile
->len
- d
->expected_image_size
- d
->cmap_pos
;
295 if(size2
==avail_size
) {
298 if(size1
==avail_size
) {
299 d
->cmap_num_entries
= (i64
)d
->hf
[HF_NCOLORS
];
302 if(size2
>avail_size
&& size1
<avail_size
) {
303 d
->cmap_num_entries
= (i64
)d
->hf
[HF_NCOLORS
];
304 need_cmap_warning
= 1;
308 need_cmap_warning
= 1;
311 if(need_cmap_warning
) {
312 de_warn(c
, "Can't reliably locate the image. Might not be decoded correctly.");
314 d
->cmap_size_in_bytes
= d
->cmap_num_entries
* 12;
315 d
->imgpos
= d
->cmap_pos
+ d
->cmap_size_in_bytes
;
316 d
->actual_image_size
= c
->infile
->len
- d
->imgpos
;
319 static void do_header(deark
*c
, lctx
*d
)
322 int saved_indent_level
;
325 de_dbg_indent_save(c
, &saved_indent_level
);
327 de_dbg(c
, "header at %"I64_FMT
, pos
);
330 for(k
=0; k
<25; k
++) {
331 const char *name
= NULL
;
333 d
->hf
[k
] = (UI
)de_getu32be_p(&pos
);
336 name
= get_pixfmt_name(d
->hf
[k
]);
338 else if(k
==HF_BYTE_ORDER
) {
339 if(d
->hf
[k
]==0) name
= "LE";
342 else if(k
==HF_BIT_ORDER
) {
343 if(d
->hf
[k
]==0) name
= "lsb first";
344 else name
= "msb first";
346 else if(k
==HF_VCLASS
) {
347 d
->vclass_adj
= d
->hf
[k
] & 0xfffffffeU
;
348 name
= get_vclass_name(d
->vclass_adj
);
351 if(k
>=HF_RMASK
&& k
<=HF_BMASK
) {
352 de_dbg(c
, "%s: 0x%08x", hnames
[k
], d
->hf
[k
]);
356 de_dbg(c
, "%s: %u (%s)", hnames
[k
], d
->hf
[k
], name
);
359 de_dbg(c
, "%s: %u", hnames
[k
], d
->hf
[k
]);
364 if(d
->hf
[HF_HSIZE
]<100) {
370 if(d
->hf
[HF_VER
]!=7) {
376 if(d
->hf
[HF_WIDTH
]>1000000 || d
->hf
[HF_HEIGHT
]>1000000 ||
377 d
->hf
[HF_BYTES_PER_LINE
]>1000000)
384 interpret_header(c
, d
);
385 if(d
->errflag
) goto done
;
386 find_cmap_and_image(c
, d
);
389 de_dbg_indent_restore(c
, saved_indent_level
);
392 static void read_image_rgb(deark
*c
, lctx
*d
, dbuf
*inf
, i64 inf_pos1
,
397 for(j
=0; j
<d
->height
; j
++) {
400 rowpos
= inf_pos1
+ j
*d
->rowspan
;
402 for(i
=0; i
<d
->width
; i
++) {
408 if(d
->bytes_per_pixel
==4) {
409 v
= (u32
)dbuf_getu32x(inf
, rowpos
+i
*d
->bytes_per_pixel
, d
->pixel_byte_order
);
412 v
= (u32
)dbuf_getint_ext(inf
, rowpos
+i
*d
->bytes_per_pixel
,
413 (UI
)d
->bytes_per_pixel
, d
->pixel_byte_order
, 0);
418 v2
= v
& d
->hf
[HF_RMASK
+k
];
419 v2
= v2
>> d
->sample_bit_shift
[k
];
421 if(d
->sample_maxval
[k
]==255) {
425 cs
[k
] = de_scale_n_to_255(d
->sample_maxval
[k
], v2
);
428 clr
= DE_MAKE_RGB(cs
[0], cs
[1], cs
[2]);
429 de_bitmap_setpixel_rgb(img
, i
, j
, clr
);
434 static void read_image_colormapped(deark
*c
, lctx
*d
, dbuf
*inf
, i64 inf_pos
,
439 if(d
->bits_per_pixel
<1 || d
->bits_per_pixel
>8) return;
442 if(d
->hf
[HF_BIT_ORDER
]==0) {
446 de_convert_image_paletted(inf
, inf_pos
, d
->bits_per_pixel
,
447 d
->rowspan
, d
->pal
, img
, flags
);
450 if(d
->hf
[HF_BIT_ORDER
]==0) {
454 de_convert_image_paletted_planar(inf
, inf_pos
, d
->nplanes
,
455 d
->rowspan
, d
->rowspan
*d
->height
, d
->pal
, img
, flags
);
459 static void decompress_pvwave_rle(deark
*c
, lctx
*d
, dbuf
*unc_pixels
)
462 i64 endpos
= c
->infile
->len
;
464 i64 nbytes_written
= 0;
467 row_padding
= d
->rowspan
- d
->width
;
468 if(row_padding
<0) goto done
;
474 if(pos
+3 > endpos
) goto done
;
475 if(nbytes_written
>= d
->expected_image_size
) goto done
;
477 count
= de_getu16be_p(&pos
);
478 b
= de_getbyte_p(&pos
);
479 dbuf_write_run(unc_pixels
, b
, count
);
480 nbytes_written
+= count
;
483 if(xpos
>= d
->width
) {
484 if(row_padding
!=0 && xpos
==d
->width
) {
485 // It's stupid that we have to do this.
486 dbuf_write_run(unc_pixels
, 0x00, row_padding
);
487 nbytes_written
+= row_padding
;
493 de_dbg(c
, "decompressed %"I64_FMT
" bytes to %"I64_FMT
,
494 pos
-d
->imgpos
, nbytes_written
);
495 dbuf_flush(unc_pixels
);
498 static void detect_compression(deark
*c
, lctx
*d
)
502 if(d
->actual_image_size
== d
->expected_image_size
) goto done
;
503 if(d
->actual_image_size
%3 != 0) goto done
;
505 if(d
->hf
[HF_PIXFMT
]==2 &&
506 d
->hf
[HF_DEPTH
]==8 &&
507 d
->hf
[HF_BYTE_ORDER
]==0 &&
508 d
->hf
[HF_BITMAP_UNIT
]==32 &&
509 d
->hf
[HF_BIT_ORDER
]==0 &&
510 d
->hf
[HF_SCANLINE_PAD
]==32 &&
511 d
->hf
[HF_BITS_PER_PIX
]==8 &&
512 d
->hf
[HF_VCLASS
]==3 &&
513 d
->hf
[HF_BITS_PER_RGB
]==8)
521 // TODO: We could do better by checking more than just the first
523 count
= de_getu16be(d
->imgpos
);
524 if(count
<1 || count
>d
->width
) {
530 if(d
->cmpr_meth
==100) {
531 de_dbg(c
, "detected PV-Wave RLE compression");
535 static void do_xwd_image(deark
*c
, lctx
*d
)
538 de_bitmap
*img
= NULL
;
539 dbuf
*unc_pixels
= NULL
;
540 dbuf
*inf
= c
->infile
; // This is a copy -- do not close
541 i64 inf_pos
= d
->imgpos
;
542 int saved_indent_level
;
544 de_dbg_indent_save(c
, &saved_indent_level
);
545 de_dbg(c
, "image at %"I64_FMT
, d
->imgpos
);
547 if(!de_good_image_dimensions(c
, d
->width
, d
->height
)) goto done
;
549 detect_compression(c
, d
);
551 if(d
->cmpr_meth
!=0) {
552 unc_pixels
= dbuf_create_membuf(c
, d
->expected_image_size
, 0x1);
553 dbuf_enable_wbuffer(unc_pixels
);
554 decompress_pvwave_rle(c
, d
, unc_pixels
);
559 if(d
->cmpr_meth
==0) {
560 if(d
->imgpos
+ d
->expected_image_size
> c
->infile
->len
+16) {
561 de_err(c
, "Bad or truncated XWD file");
567 if(d
->imgtype
==XWD_IMGTYPE_RGB
&&
568 (d
->bytes_per_pixel
==2 || d
->bytes_per_pixel
==3 || d
->bytes_per_pixel
==4))
572 else if(d
->imgtype
==XWD_IMGTYPE_PAL_OR_GRAY
&&
584 if(d
->imgtype
==XWD_IMGTYPE_PAL_OR_GRAY
) {
585 if(de_is_grayscale_palette(d
->pal
, 256)) {
589 img
= de_bitmap_create(c
, d
->width
, d
->height
, bypp
);
591 if(d
->imgtype
==XWD_IMGTYPE_RGB
) {
592 read_image_rgb(c
, d
, inf
, inf_pos
, img
);
594 else if(d
->imgtype
==XWD_IMGTYPE_PAL_OR_GRAY
) {
595 read_image_colormapped(c
, d
, inf
, inf_pos
, img
);
598 if(d
->errflag
) goto done
;
600 de_bitmap_write_to_file(img
, NULL
, DE_CREATEFLAG_OPT_IMAGE
);
604 de_err(c
, "Unsupported image type");
607 de_bitmap_destroy(img
);
608 dbuf_close(unc_pixels
);
609 de_dbg_indent_restore(c
, saved_indent_level
);
612 static void de_run_xwd(deark
*c
, de_module_params
*mparams
)
616 d
= de_malloc(c
, sizeof(lctx
));
618 if(d
->errflag
) goto done
;
619 // TODO?: Do something with the name that may appear after the header.
621 read_or_construct_colormap(c
, d
);
628 de_err(c
, "Bad or unsupported XWD file");
634 static int de_identify_xwd(deark
*c
)
641 // TODO: Identification could be improved.
643 n
= (UI
)de_getu32be(4); // version
646 hdrsize
= (UI
)de_getu32be(0);
647 if(hdrsize
<100 || hdrsize
>500) return 0;
649 n
= (UI
)de_getu32be(8); // pixfmt
652 n
= (UI
)de_getu32be(12); // depth
653 if(n
<1 || n
>32) return 0;
655 cmapn
= de_getu32be(76);
656 if(cmapn
>512) return 0;
657 if(hdrsize
+ 12*cmapn
> c
->infile
->len
) return 0;
659 has_ext
= de_input_file_has_ext(c
, "xwd");
660 if(has_ext
) return 100;
664 void de_module_xwd(deark
*c
, struct deark_module_info
*mi
)
667 mi
->desc
= "X-Windows screen dump";
668 mi
->run_fn
= de_run_xwd
;
669 mi
->identify_fn
= de_identify_xwd
;