1 // This file is part of Deark.
2 // Copyright (C) 2016-2024 Jason Summers
3 // See the file COPYING for terms of use.
7 #include <deark-private.h>
8 #include <deark-fmtutil.h>
9 DE_DECLARE_MODULE(de_module_insetpix
);
11 struct insetpix_item_data
{
20 typedef struct localctx_insetpix
{
21 de_encoding input_encoding
;
24 u8 graphics_type
; // 0=character, 1=bitmap
27 u8 have_image_info
; // 1 = we've read the segment
31 i64 imginfo_pos
, imginfo_len
; // 0 = no info
33 i64 tileinfo_pos
, tileinfo_len
;
38 i64 npwidth
, pdwidth
, height
;
39 i64 w_in_chars
, h_in_chars
;
40 i64 gfore
; // Foreground color bits
42 u8 pal_sample_descriptor
[4]; // 0=intens, 1=red, 2=green, 3=blue
43 u32 descriptor_combined
;
46 i64 page_rows
, page_cols
;
47 i64 stp_rows
, stp_cols
;
50 i64 compression_bytes_per_row
;
53 u8 max_pal_intensity
, max_pal_sample
;
55 de_bitmap
*tile_img
; // Re-used for each tile
61 // Sets d->have_pal_data if successful
62 static void do_palette(deark
*c
, lctx
*d
)
66 i64 pal_entries_in_file
;
69 u8 sm1
[4]; // Original I, R, G, B
70 u8 sm2
[4]; // Intermediate
71 u8 sm3
[4]; // Post-processed samples
72 u8 uses_intens
= 0; // Special handling if we have RGB and 'I' values.
73 double max_color_sample
;
74 double pal_sample_scalefactor
[4];
75 int saved_indent_level
;
77 de_dbg_indent_save(c
, &saved_indent_level
);
79 if(pos1
==0 || d
->pal_len
<4) goto done
;
81 de_dbg(c
, "palette at %"I64_FMT
, pos1
);
84 pal_entries_in_file
= d
->pal_len
/4;
85 de_dbg(c
, "number of palette colors: %d", (int)pal_entries_in_file
);
87 d
->pal_entries_used
= pal_entries_in_file
; // default
88 if(d
->graphics_type
==1) {
89 d
->pal_entries_used
= d
->max_sample_value
+1;
91 if(d
->pal_entries_used
> pal_entries_in_file
) d
->pal_entries_used
= pal_entries_in_file
;
94 // If intensity bits are used, make the initial colors darker, so that the
95 // intensity bits can lighten them.
96 uses_intens
= (d
->pal_sample_descriptor
[0]!=0) && !d
->is_grayscale
;
97 max_color_sample
= uses_intens
? 170.0 : 255.0;
100 if(d
->pal_sample_descriptor
[k
]>=2)
101 pal_sample_scalefactor
[k
] = max_color_sample
/ (double)(d
->pal_sample_descriptor
[k
]-1);
103 pal_sample_scalefactor
[k
] = 0.0;
106 for(i
=0; i
<pal_entries_in_file
; i
++) {
112 sm1
[k
] = de_getbyte_p(&pos
);
117 // Best I can figure is that, in the palette definition, when there
118 // are exactly 4 sample intensities, intensity 1 is brighter than
119 // intensity 2. I don't know why I have to swap them like this.
120 if(d
->pal_sample_descriptor
[k
]==4) {
121 if(sm2
[k
]==1) sm2
[k
] = 2;
122 else if(sm2
[k
]==2) sm2
[k
] = 1;
126 if(d
->is_grayscale
) {
127 sm3
[0] = (u8
)(0.5+ pal_sample_scalefactor
[0] * (double)sm2
[0]);
128 d
->pal
[i
] = DE_MAKE_GRAY(sm3
[0]);
131 sm3
[1] = (u8
)(0.5+ pal_sample_scalefactor
[1] * (double)sm2
[1]);
132 sm3
[2] = (u8
)(0.5+ pal_sample_scalefactor
[2] * (double)sm2
[2]);
133 sm3
[3] = (u8
)(0.5+ pal_sample_scalefactor
[3] * (double)sm2
[3]);
134 if(uses_intens
&& sm2
[0]) {
135 // This is just a guess. The spec doesn't say what intensity bits do.
136 // This is pretty much what old PC graphics cards do when the
137 // intensity bit is set.
142 d
->pal
[i
] = DE_MAKE_RGB(sm3
[1],sm3
[2],sm3
[3]);
146 de_snprintf(tmps
, sizeof(tmps
), "(%3u,%3u,%3u,intens=%u) "DE_CHAR_RIGHTARROW
" ",
147 (UI
)sm1
[1], (UI
)sm1
[2], (UI
)sm1
[3], (UI
)sm1
[0]);
150 de_snprintf(tmps
, sizeof(tmps
), "(%3u,%3u,%3u) "DE_CHAR_RIGHTARROW
" ",
151 (UI
)sm1
[1], (UI
)sm1
[2], (UI
)sm1
[3]);
153 de_dbg_pal_entry2(c
, i
, d
->pal
[i
], tmps
, NULL
,
154 i
<d
->pal_entries_used
? "":" [unused]");
157 d
->have_pal_data
= 1;
159 de_dbg_indent_restore(c
, saved_indent_level
);
162 static const char *get_board_type_name(u8 bt
)
164 const char *name
= NULL
;
167 case 0: name
="none"; break;
168 case 8: name
="CGA"; break;
169 case 16: name
="Hercules"; break;
170 case 24: name
="EGA"; break;
172 return name
?name
:"?";
175 // Sets d->have_image_info if successful
176 static void do_image_info(deark
*c
, lctx
*d
)
180 int saved_indent_level
;
182 de_dbg_indent_save(c
, &saved_indent_level
);
183 pos1
= d
->imginfo_pos
;
184 if(!pos1
|| d
->imginfo_len
<32) goto done
;
186 de_dbg(c
, "image information at %"I64_FMT
, pos1
);
190 d
->hmode
= de_getbyte_p(&pos
);
191 de_dbg(c
, "hardware mode: %u", (UI
)d
->hmode
);
193 d
->htype
= de_getbyte_p(&pos
);
194 d
->graphics_type
= d
->htype
& 0x01;
195 d
->board_type
= d
->htype
& 0xfe;
196 de_dbg(c
, "htype: 0x%02x", (UI
)d
->htype
);
198 de_dbg(c
, "board type: %u (%s)", (UI
)d
->board_type
,
199 get_board_type_name(d
->board_type
));
200 de_dbg(c
, "graphics type: %u (%s)", (UI
)d
->graphics_type
,
201 d
->graphics_type
?"bitmap":"character");
202 de_dbg_indent(c
, -1);
204 if(d
->graphics_type
==0) {
206 d
->w_in_chars
= (i64
)de_getbyte_p(&pos
);
207 d
->h_in_chars
= (i64
)de_getbyte_p(&pos
);
208 de_dbg(c
, "dimensions: %u"DE_CHAR_TIMES
"%u characters",
209 (UI
)d
->w_in_chars
, (UI
)d
->h_in_chars
);
212 if(d
->graphics_type
==1) {
214 d
->npwidth
= de_getu16le_p(&pos
);
215 d
->height
= de_getu16le_p(&pos
);
216 de_dbg_dimensions(c
, d
->npwidth
, d
->height
);
218 d
->gfore
= (i64
)de_getbyte_p(&pos
);
219 de_dbg(c
, "foreground color bits: %d", (int)d
->gfore
);
220 d
->max_sample_value
= de_pow2(d
->gfore
) -1;
224 d
->descriptor_combined
= (u32
)de_getu32be_p(&pos
);
225 d
->pal_sample_descriptor
[0] = (u8
)(d
->descriptor_combined
>>24);
226 d
->pal_sample_descriptor
[1] = (u8
)((d
->descriptor_combined
>>16) & 0xff);
227 d
->pal_sample_descriptor
[2] = (u8
)((d
->descriptor_combined
>>8) & 0xff);
228 d
->pal_sample_descriptor
[3] = (u8
)(d
->descriptor_combined
& 0xff);
229 de_dbg(c
, "palette descriptor (IRGB): %u,%u,%u,%u",
230 (UI
)d
->pal_sample_descriptor
[0], (UI
)d
->pal_sample_descriptor
[1],
231 (UI
)d
->pal_sample_descriptor
[2], (UI
)d
->pal_sample_descriptor
[3]);
234 d
->haspect
= de_getbyte_p(&pos
);
235 d
->vaspect
= de_getbyte_p(&pos
);
236 de_dbg(c
, "aspect ratio: %d"DE_CHAR_TIMES
"%d", (int)d
->haspect
, (int)d
->vaspect
);
238 d
->have_image_info
= 1;
240 de_dbg_indent_restore(c
, saved_indent_level
);
243 // Sets d->have_tile_info if successful
244 static void do_tileinfo(deark
*c
, lctx
*d
)
248 int saved_indent_level
;
250 de_dbg_indent_save(c
, &saved_indent_level
);
251 pos1
= d
->tileinfo_pos
;
252 if(!pos1
|| d
->tileinfo_len
<8) goto done
;
254 de_dbg(c
, "tile information at %"I64_FMT
, pos1
);
258 d
->page_rows
= de_getu16le_p(&pos
);
259 d
->page_cols
= de_getu16le_p(&pos
);
260 d
->stp_rows
= de_getu16le_p(&pos
);
261 d
->stp_cols
= de_getu16le_p(&pos
);
262 de_dbg(c
, "dimensions of a tile: %"I64_FMT DE_CHAR_TIMES
"%"I64_FMT
,
263 d
->page_cols
, d
->page_rows
);
264 de_dbg(c
, "dimensions in tiles: %"I64_FMT DE_CHAR_TIMES
"%"I64_FMT
,
265 d
->stp_cols
, d
->stp_rows
);
267 if(d
->page_cols
%8 != 0) {
268 de_err(c
, "page_cols must be a multiple of 8 (is %d)", (int)d
->page_cols
);
272 if(d
->num_tiles_found
==0) goto done
;
274 d
->have_tile_info
= 1;
276 de_dbg_indent_restore(c
, saved_indent_level
);
279 static u8
getbit(const u8
*m
, i64 bitnum
)
284 b
= (b
>>(7-bitnum
%8)) & 0x1;
288 static void do_decompress_tile(deark
*c
, lctx
*d
, struct insetpix_item_data
*itd
,
289 dbuf
*unc_pixels
, i64 num_rows
)
292 u8
*compression_bytes
= NULL
;
296 i64 endpos
= itd
->loc
+ itd
->len
;
298 // There are d->gfore planes (1-bpp images). The first row of each plane is
299 // uncompressed. The rest are compressed with a delta compression algorithm.
300 // There are d->page_rows rows in each plane.
302 rowbuf1
= de_malloc(c
, d
->rowspan
);
303 compression_bytes
= de_malloc(c
, d
->compression_bytes_per_row
);
307 for(plane
=0; plane
<d
->gfore
; plane
++) {
308 for(j
=0; j
<num_rows
; j
++) {
310 de_warn(c
, "Not enough data in tile %u", itd
->tile_num
);
315 // First row is stored uncompressed
316 dbuf_copy(c
->infile
, pos
, d
->rowspan
, unc_pixels
);
320 de_read(compression_bytes
, pos
, d
->compression_bytes_per_row
);
321 pos
+= d
->compression_bytes_per_row
;
323 // Read back a copy of the previous row
324 dbuf_read(unc_pixels
, rowbuf1
, unc_pixels
->len
- d
->rowspan
, d
->rowspan
);
326 // For every 1 bit in the compression_bytes array, read a byte from the file.
327 // For every 0 bit, copy the byte from the previous row.
328 for(i
=0; i
<d
->rowspan
; i
++) {
331 if(getbit(compression_bytes
, i
)) {
332 b
= de_getbyte_p(&pos
);
337 dbuf_writebyte(unc_pixels
, b
);
343 de_dbg(c
, "decompressed %"I64_FMT
" bytes to %"I64_FMT
, pos
-itd
->loc
, unc_pixels
->len
);
346 de_free(c
, compression_bytes
);
350 static void do_render_tile(deark
*c
, lctx
*d
, de_bitmap
*img
,
351 struct insetpix_item_data
*itd
)
353 i64 x_pos_in_tiles
, y_pos_in_tiles
;
354 i64 x_origin_in_pixels
, y_origin_in_pixels
;
355 dbuf
*unc_pixels
= NULL
;
359 x_pos_in_tiles
= (i64
)itd
->tile_num
% d
->stp_cols
;
360 y_pos_in_tiles
= (i64
)itd
->tile_num
/ d
->stp_cols
;
362 x_origin_in_pixels
= x_pos_in_tiles
* d
->page_cols
;
363 y_origin_in_pixels
= y_pos_in_tiles
* d
->page_rows
;
365 // "If the actual row bound of the tile exceeds the image, the extra
366 // rows are not present."
367 nrows_expected
= d
->height
- y_origin_in_pixels
;
368 if(nrows_expected
> d
->page_rows
) nrows_expected
= d
->page_rows
;
369 planespan
= nrows_expected
* d
->rowspan
;
371 de_dbg(c
, "tile (%d,%d), pixel position (%d,%d), size %d"DE_CHAR_TIMES
"%d",
372 (int)x_pos_in_tiles
, (int)y_pos_in_tiles
,
373 (int)x_origin_in_pixels
, (int)y_origin_in_pixels
,
374 (int)d
->page_cols
, (int)nrows_expected
);
376 unc_pixels
= dbuf_create_membuf(c
, 4096, 0);
378 do_decompress_tile(c
, d
, itd
, unc_pixels
, nrows_expected
);
381 // Clear the previous image
382 de_bitmap_rect(d
->tile_img
, 0, 0, d
->page_cols
, d
->page_rows
, 0, 0);
385 d
->tile_img
= de_bitmap_create(c
, d
->page_cols
, d
->page_rows
, d
->is_grayscale
?1:3);
388 // This will try to convert 'd->page_rows' rows, when there might only be
389 // 'nrows_expected' rows, but that won't cause a problem.
390 de_convert_image_paletted_planar(unc_pixels
, 0, d
->gfore
, d
->rowspan
, planespan
,
391 d
->pal
, d
->tile_img
, 0x2);
393 de_bitmap_copy_rect(d
->tile_img
, img
, 0, 0, d
->page_cols
, nrows_expected
, x_origin_in_pixels
,
394 y_origin_in_pixels
, 0);
396 dbuf_close(unc_pixels
);
399 static void insetpix_read_item(deark
*c
, i64 pos
, struct insetpix_item_data
*itd
)
401 itd
->id
= (UI
)de_getu16le_p(&pos
);
402 itd
->len
= de_getu16le_p(&pos
);
403 itd
->loc
= de_getu32le(pos
);
404 itd
->is_special
= (u8
)(itd
->id
>=0x4000);
405 itd
->is_tile
= (u8
)(itd
->id
>=0x8000 && itd
->id
<0xffff);
407 itd
->tile_num
= itd
->id
-0x8000;
412 static void get_item_name(struct insetpix_item_data
*itd
, char *nbuf
, size_t nbuf_len
)
417 de_snprintf(nbuf
, nbuf_len
, "tile #%u", itd
->tile_num
);
422 case 0: n1
= "image info"; break;
423 case 1: n1
= "palette"; break;
424 case 2: n1
= "tile info"; break;
425 case 17: n1
= "printing options"; break;
426 case 0xff: n1
= "empty"; break;
429 de_strlcpy(nbuf
, n1
, nbuf_len
);
432 static void insetpix_dbg_item(deark
*c
, struct insetpix_item_data
*itd
, i64 idx
)
436 get_item_name(itd
, nbuf
, sizeof(nbuf
));
437 de_dbg(c
, "item #%d: id=%u (%s), loc=%"I64_FMT
", len=%"I64_FMT
, (int)idx
,
438 itd
->id
, nbuf
, itd
->loc
, itd
->len
);
441 static void do_bitmap(deark
*c
, lctx
*d
)
444 de_bitmap
*img
= NULL
;
447 de_dbg(c
, "reading image data");
450 if(!de_good_image_dimensions(c
, d
->npwidth
, d
->height
)) goto done
;
452 d
->rowspan
= d
->page_cols
/8;
453 d
->compression_bytes_per_row
= (d
->rowspan
+7)/8; // Just a guess. Spec doesn't say.
456 d
->pdwidth
= d
->page_cols
* d
->stp_cols
;
459 d
->pdwidth
= d
->npwidth
;
462 img
= de_bitmap_create2(c
, d
->npwidth
, d
->pdwidth
, d
->height
, d
->is_grayscale
?1:3);
464 // Read through the items again, this time looking only at the image tiles.
465 for(item
=0; item
<d
->item_count
; item
++) {
466 struct insetpix_item_data itd
;
470 if(pos
+8 > c
->infile
->len
) break;
472 insetpix_read_item(c
, pos
, &itd
);
473 if(!itd
.is_tile
) continue;
474 insetpix_dbg_item(c
, &itd
, item
);
477 do_render_tile(c
, d
, img
, &itd
);
478 de_dbg_indent(c
, -1);
481 fi
= de_finfo_create(c
);
482 fi
->density
.code
= DE_DENSITY_UNK_UNITS
;
483 fi
->density
.xdens
= (double)d
->haspect
;
484 fi
->density
.ydens
= (double)d
->vaspect
;
485 de_bitmap_write_to_file_finfo(img
, fi
, DE_CREATEFLAG_OPT_IMAGE
);
488 de_bitmap_destroy(img
);
489 de_finfo_destroy(c
, fi
);
490 de_dbg_indent(c
, -1);
493 static void do_char_graphics(deark
*c
, lctx
*d
)
495 struct insetpix_item_data itd
;
496 dbuf
*unc_pixels
= NULL
;
497 struct de_char_context
*charctx
= NULL
;
498 struct fmtutil_char_simplectx
*csctx
= NULL
;
499 int saved_indent_level
;
501 de_dbg_indent_save(c
, &saved_indent_level
);
502 de_dbg(c
, "reading char graphics");
504 if(d
->num_tiles_found
!=1) {
505 de_err(c
, "Multi-tile char. graphics not supported");
509 insetpix_read_item(c
, 4 + 8*d
->idx_of_1st_tile
, &itd
);
510 insetpix_dbg_item(c
, &itd
, d
->idx_of_1st_tile
);
512 unc_pixels
= dbuf_create_membuf(c
, 4096, 0);
513 csctx
= de_malloc(c
, sizeof(struct fmtutil_char_simplectx
));
514 charctx
= de_create_charctx(c
, 0);
516 csctx
->width_in_chars
= d
->page_cols
;
517 csctx
->height_in_chars
= d
->page_rows
;
519 // Unless I'm missing something, this format is just wacky.
520 // Assume the screen is 80x25. The original data is then 80x25 bytes of
521 // character data, followed immediately by 80x25 bytes of attribute data.
522 // So its sort of 80x50. Good so far. But then it's compressed as if it were
523 // 160x25. Which is strange.
524 // The first 12 rows are character data.
525 // The 13th row is half character data, half attribute data.
526 // The last 12 rows are attribute data.
527 // The compression is only effective when a character is the same as the one
528 // *two* rows above it.
529 // (The format documentation does hint at this, but doesn't adequately
532 d
->gfore
= 1; // hack (gfore is used as # of planes)
533 d
->rowspan
= csctx
->width_in_chars
*2;
534 d
->compression_bytes_per_row
= (d
->rowspan
+7)/8;
535 do_decompress_tile(c
, d
, &itd
, unc_pixels
, csctx
->height_in_chars
);
537 csctx
->input_encoding
= d
->input_encoding
;
538 csctx
->inf
= unc_pixels
;
540 csctx
->inf_len
= csctx
->width_in_chars
* csctx
->height_in_chars
* 2;
541 csctx
->fg_stride
= 1;
542 csctx
->attr_offset
= csctx
->width_in_chars
*csctx
->height_in_chars
;
543 de_memcpy(&charctx
->pal
, &d
->pal
, sizeof(de_color
)*16);
545 fmtutil_char_simple_run(c
, csctx
, charctx
);
548 de_free_charctx(c
, charctx
);
550 dbuf_close(unc_pixels
);
551 de_dbg_indent_restore(c
, saved_indent_level
);
554 static void de_run_insetpix(deark
*c
, de_module_params
*mparams
)
560 i64 num_skipped_items
= 0;
561 int saved_indent_level
;
563 de_dbg_indent_save(c
, &saved_indent_level
);
564 d
= de_malloc(c
, sizeof(lctx
));
565 d
->input_encoding
= de_get_input_encoding(c
, NULL
, DE_ENCODING_CP437
);
567 pix_version
= (UI
)de_getu16le(0);
568 d
->item_count
= de_getu16le(2);
569 de_dbg(c
, "version: %u", pix_version
);
570 de_dbg(c
, "index at 4, %d items", (int)d
->item_count
);
572 // Scan the index, and record the location of items we care about.
573 // (The index will be read again when converting the image bitmap.)
575 for(item
=0; item
<d
->item_count
; item
++) {
576 struct insetpix_item_data itd
;
580 if(pos
+8 > c
->infile
->len
) goto done
;
582 insetpix_read_item(c
, pos
, &itd
);
584 if(d
->num_tiles_found
==0) {
585 d
->idx_of_1st_tile
= item
;
587 d
->num_tiles_found
++;
589 skip_flag
= itd
.is_special
;
593 if(skip_flag
&& c
->debug_level
<2) { // Skip "tile" items for now
597 insetpix_dbg_item(c
, &itd
, item
);
603 if(itd
.loc
+ itd
.len
> c
->infile
->len
) {
604 de_err(c
, "Item #%d (ID %u) goes beyond end of file",
611 d
->imginfo_pos
= itd
.loc
;
612 d
->imginfo_len
= itd
.len
;
616 d
->pal_pos
= itd
.loc
;
617 d
->pal_len
= itd
.len
;
621 d
->tileinfo_pos
= itd
.loc
;
622 d
->tileinfo_len
= itd
.len
;
626 if(c
->debug_level
<2) {
627 de_dbg(c
, "other items not listed: %"I64_FMT
, num_skipped_items
);
629 de_dbg(c
, "number of tiles: %"I64_FMT
, d
->num_tiles_found
);
630 de_dbg_indent(c
, -1);
633 if(!d
->have_image_info
) {
634 de_err(c
, "Bad or missing Image Info");
638 if(d
->pal_sample_descriptor
[0]!=0 && d
->pal_sample_descriptor
[1]==0 &&
639 d
->pal_sample_descriptor
[2]==0 && d
->pal_sample_descriptor
[3]==0)
645 if(!d
->have_pal_data
) {
646 de_err(c
, "Bad or missing palette");
651 if(!d
->have_tile_info
) {
652 de_err(c
, "Bad or missing Tile Info");
656 if(d
->graphics_type
==0) {
657 do_char_graphics(c
, d
);
660 if(d
->gfore
<1 || d
->gfore
>8) {
661 de_err(c
, "Inset PIX with %d bits/pixel are not supported", (int)d
->gfore
);
665 switch(d
->descriptor_combined
) {
672 de_warn(c
, "Not a known image type. This image might not be handled correctly.");
680 de_bitmap_destroy(d
->tile_img
);
683 de_dbg_indent_restore(c
, saved_indent_level
);
686 // Inset PIX is hard to identify.
687 static int de_identify_insetpix(deark
*c
)
692 i64 item_loc
, item_len
;
694 u8 has_typical_1st_item
;
696 if(c
->detection_data
->best_confidence_so_far
>20) return 0;
698 pix_version
= (UI
)de_getu16le(0);
699 // Other versions exist, but I don't know anything about them.
700 if(pix_version
!=3) return 0;
702 has_ext
= (u8
)de_input_file_has_ext(c
, "pix");
703 has_typical_1st_item
= (u8
)((u32
)de_getu32le(4)==(u32
)0x00200000);
704 if(!has_ext
&& !has_typical_1st_item
) return 0;
706 item_count
= de_getu16le(2);
707 // Need at least 4 items (image info, palette info, tile info, and 1 tile).
708 if(item_count
<4 || item_count
>500) return 0;
710 if(4 + 8*item_count
>= c
->infile
->len
) return 0;
712 for(item
=0; item
<item_count
&& item
<16; item
++) {
713 item_len
= de_getu16le(4+8*item
+2);
714 item_loc
= de_getu32le(4+8*item
+4);
715 if(item_loc
< 4 + 8*item_count
) return 0;
716 if(item_loc
+item_len
> c
->infile
->len
) return 0;
722 void de_module_insetpix(deark
*c
, struct deark_module_info
*mi
)
725 mi
->desc
= "Inset PIX image";
726 mi
->run_fn
= de_run_insetpix
;
727 mi
->identify_fn
= de_identify_insetpix
;