// This file is part of Deark.
// Copyright (C) 2016-2021 Jason Summers
// See the file COPYING for terms of use.

// This file is for miscellaneous small modules that primarily do image decoding.

#include <deark-private.h>
#include <deark-fmtutil.h>
DE_DECLARE_MODULE(de_module_hpicn);
DE_DECLARE_MODULE(de_module_xpuzzle);
DE_DECLARE_MODULE(de_module_bob);
DE_DECLARE_MODULE(de_module_alias_pix);
DE_DECLARE_MODULE(de_module_applevol);
DE_DECLARE_MODULE(de_module_hr);
DE_DECLARE_MODULE(de_module_ripicon);
DE_DECLARE_MODULE(de_module_lss16);
DE_DECLARE_MODULE(de_module_vbm);
DE_DECLARE_MODULE(de_module_fp_art);
DE_DECLARE_MODULE(de_module_ybm);
DE_DECLARE_MODULE(de_module_olpc565);
DE_DECLARE_MODULE(de_module_iim);
DE_DECLARE_MODULE(de_module_pm_xv);
DE_DECLARE_MODULE(de_module_crg);
DE_DECLARE_MODULE(de_module_farbfeld);
DE_DECLARE_MODULE(de_module_hsiraw);
DE_DECLARE_MODULE(de_module_qdv);
DE_DECLARE_MODULE(de_module_vitec);
DE_DECLARE_MODULE(de_module_hs2);
DE_DECLARE_MODULE(de_module_lumena_cel);
DE_DECLARE_MODULE(de_module_deskmate_pnt);
DE_DECLARE_MODULE(de_module_mdesk_icn);
DE_DECLARE_MODULE(de_module_animator_pic);
DE_DECLARE_MODULE(de_module_dgi);
DE_DECLARE_MODULE(de_module_cserve_rle);
DE_DECLARE_MODULE(de_module_lotus_mscr);
DE_DECLARE_MODULE(de_module_fastgraph_spr);
DE_DECLARE_MODULE(de_module_fastgraph_ppr);
DE_DECLARE_MODULE(de_module_young_picasso);
DE_DECLARE_MODULE(de_module_iconmgr_ica);
DE_DECLARE_MODULE(de_module_thumbsplus);
DE_DECLARE_MODULE(de_module_fmtowns_icn);
DE_DECLARE_MODULE(de_module_fmtowns_hel);
DE_DECLARE_MODULE(de_module_pixfolio);
DE_DECLARE_MODULE(de_module_apple2icons);
DE_DECLARE_MODULE(de_module_pixit);
DE_DECLARE_MODULE(de_module_dxp_image);

static void datetime_dbgmsg(deark *c, struct de_timestamp *ts, const char *name)
{
	char timestamp_buf[64];

	de_timestamp_to_string(ts, timestamp_buf, sizeof(timestamp_buf), 0);
	de_dbg(c, "%s: %s", name, timestamp_buf);
}

// Assumes path separators are  '/' or '\'.
static void get_base_filename(de_ucstring *s1, de_ucstring *s2)
{
	i64 i;
	i64 len;

	ucstring_empty(s2);
	len = s1->len;
	for(i=0; i<len; i++) {
		de_rune ch;

		ch = s1->str[i];
		if(ch=='\\' || ch=='/') {
			ucstring_empty(s2);
		}
		else {
			ucstring_append_char(s2, ch);
		}
	}
}

// **************************************************************************
// HP 100LX / HP 200LX .ICN icon format
// **************************************************************************

struct hpicn_params {
	i64 w, h;
	i64 rowspan;
};

static void read_hpicn_params(deark *c, struct hpicn_params *icnp)
{
	icnp->w = de_getu16le(4);
	icnp->h = de_getu16le(6);
	icnp->rowspan = (icnp->w+7)/8;
}

static void de_run_hpicn(deark *c, de_module_params *mparams)
{
	struct hpicn_params icnp;

	read_hpicn_params(c, &icnp);
	de_dbg_dimensions(c, icnp.w, icnp.h);
	de_convert_and_write_image_bilevel2(c->infile, 8, icnp.w, icnp.h, icnp.rowspan,
		DE_CVTF_WHITEISZERO, NULL, 0);
}

static int de_identify_hpicn(deark *c)
{
	struct hpicn_params icnp;

	if(dbuf_memcmp(c->infile, 0, "\x01\x00\x01\x00", 4))
		return 0;
	read_hpicn_params(c, &icnp);
	if(8 + icnp.rowspan*icnp.h != c->infile->len) return 0;
	if(icnp.w<1 || icnp.w>2048 || icnp.h<1 || icnp.h>2048) return 0;
	if(icnp.w==44 && icnp.h==32) return 100;
	return 60;
}

void de_module_hpicn(deark *c, struct deark_module_info *mi)
{
	mi->id = "hpicn";
	mi->desc = "HP 100LX/200LX .ICN icon";
	mi->run_fn = de_run_hpicn;
	mi->identify_fn = de_identify_hpicn;
}

// **************************************************************************
// X11 "puzzle" format
// This is the format generated by Netpbm's ppmtopuzz utility.
// **************************************************************************

struct xpuzzhdr {
	i64 w, h;
	i64 palentries;
};

static void xpuzz_read_header(deark *c, struct xpuzzhdr *hdr)
{
	hdr->w = de_getu32be(0);
	hdr->h = de_getu32be(4);
	hdr->palentries = (i64)de_getbyte(8);
	if(hdr->palentries==0) hdr->palentries = 256;
}

static void de_run_xpuzzle(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	struct xpuzzhdr hdr;
	de_color pal[256];
	i64 p;

	xpuzz_read_header(c, &hdr);
	de_dbg_dimensions(c, hdr.w, hdr.h);
	if(!de_good_image_dimensions(c, hdr.w, hdr.h)) goto done;

	img = de_bitmap_create(c,  hdr.w,  hdr.h, 3);

	// Read the palette
	p = 9;
	de_read_simple_palette(c, c->infile, p,  hdr.palentries, 3, pal, 256,
		DE_RDPALTYPE_24BIT, DE_RDPALFLAG_INITPAL);
	p += 3*hdr.palentries;

	// Read the bitmap
	de_convert_image_paletted(c->infile, p, 8, hdr.w, pal, img, 0);

	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_xpuzzle(deark *c)
{
	struct xpuzzhdr hdr;
	int retval = 0;

	xpuzz_read_header(c, &hdr);
	// 4096 is an arbitrary limit.
	if(hdr.w<1 || hdr.w>4096 || hdr.h<1 || hdr.h>4096) goto done;
	if(hdr.w * hdr.h + 3*hdr.palentries + 9 == c->infile->len) {
		retval = 20;
	}

done:
	return retval;
}

void de_module_xpuzzle(deark *c, struct deark_module_info *mi)
{
	mi->id = "xpuzzle";
	mi->desc = "X11 \"puzzle\" image";
	mi->run_fn = de_run_xpuzzle;
	mi->identify_fn = de_identify_xpuzzle;
}

// **************************************************************************
// "Bob" bitmap image
// Used by the Bob ray tracer.
// **************************************************************************

static void de_run_bob(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	i64 w, h;
	u32 pal[256];
	i64 p;

	w = de_getu16le(0);
	h = de_getu16le(2);
	if(!de_good_image_dimensions(c, w, h)) goto done;
	img = de_bitmap_create(c, w, h, 3);

	// Read the palette
	p = 4;
	de_read_simple_palette(c, c->infile, p, 256, 3, pal, 256, DE_RDPALTYPE_24BIT, 0);
	p += 256*3;

	// Read the bitmap
	de_convert_image_paletted(c->infile, p, 8, w, pal, img, 0);

	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_bob(deark *c)
{
	i64 w, h;

	if(!de_input_file_has_ext(c, "bob")) return 0;

	w = de_getu16le(0);
	h = de_getu16le(2);
	if(c->infile->len == 4 + 768 + w*h) {
		return 100;
	}
	return 0;
}

void de_module_bob(deark *c, struct deark_module_info *mi)
{
	mi->id = "bob";
	mi->desc = "Bob Ray Tracer bitmap image";
	mi->run_fn = de_run_bob;
	mi->identify_fn = de_identify_bob;
}

// **************************************************************************
// Alias PIX bitmap image.
// Also used by the Vivid ray tracer.
// **************************************************************************

static int decompress_alias_pix(deark *c, i64 bypp, dbuf *unc_pixels,
	i64 nbytes_expected)
{
	u8 failure_flag = 0;
	i64 nbytes_decompressed = 0;
	i64 pos = 10;
	u8 pixbuf[3] = {0, 0, 0};

	while(1) {
		i64 i;
		i64 count;

		if(nbytes_decompressed >= nbytes_expected) goto done;
		if(pos >= c->infile->len) {
			de_warn(c, "Unexpected end of image");
			goto done;
		}

		count = de_getbyte_p(&pos);
		if(count==0) {
			failure_flag = 1;
			goto done;
		}
		de_read(pixbuf, pos, bypp);
		pos += bypp;
		for(i=0; i<count; i++) {
			dbuf_write(unc_pixels, pixbuf, bypp);
		}
		nbytes_decompressed += bypp*count;
	}

done:
	dbuf_flush(unc_pixels);
	if(failure_flag) {
		de_err(c, "Decompression failed");
		return 0;
	}
	return 1;
}

static void de_run_alias_pix(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	i64 w, h;
	i64 pos;
	i64 depth;
	i64 unc_image_size;
	i64 bypp;
	dbuf *unc_pixels = NULL;

	pos = 0;
	w = de_getu16be_p(&pos);
	h = de_getu16be_p(&pos);
	de_dbg_dimensions(c, w, h);
	if(!de_good_image_dimensions(c, w, h)) goto done;

	// The field@4 is sometimes given as "x offset", other times as
	// "first scanline" (and other times as "unused").
	// Maybe it's a difference between Alias PIX and Vivid formats?
	// Anyway, we expect it to be 0, and we ignore it (and the field@6).
	pos += 4;

	depth = de_getu16be_p(&pos);
	de_dbg(c, "depth: %d", (int)depth);
	if(depth!=8 && depth!=24) {
		de_err(c, "Unsupported image type");
		goto done;
	}

	bypp = depth/8;
	unc_image_size = w*h*bypp;
	unc_pixels = dbuf_create_membuf(c, unc_image_size, 0x1);
	dbuf_enable_wbuffer(unc_pixels);

	if(!decompress_alias_pix(c, bypp, unc_pixels, unc_image_size)) {
		goto done;
	}
	de_dbg(c, "[decompressed to %"I64_FMT" bytes]", unc_pixels->len);

	if(depth==8) {
		de_color pal[256];

		de_make_grayscale_palette(pal, 256, 0);
		img = de_bitmap_create(c, w, h, 1);
		de_convert_image_paletted(unc_pixels, 0, 8, w, pal, img, 0);
	}
	else {
		img = de_bitmap_create(c, w, h, 3);
		de_convert_image_rgb(unc_pixels, 0, w*3, 3, img, DE_GETRGBFLAG_BGR);
	}

	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
	dbuf_close(unc_pixels);
}

// Rows are supposed to be compressed independently.
// We check that no run crosses over the end of the first row.
static int aliaspix_valid_first_row(deark *c, UI w, UI bypp)
{
	i64 pos = 10;
	UI pixpos = 0;
	int retval = 0;

	while(1) {
		UI count;

		if(pixpos==w) { retval = 1; goto done; }
		if(pixpos>w) goto done;
		count = (UI)de_getbyte_p(&pos);
		if(count==0) goto done;
		// TODO? We could also do some sanity checks if there are two
		// consecutive runs of the same color, that should have been
		// combined into one.
		pos += (i64)bypp;
		pixpos += count;
	}
done:
	return retval;
}

static int de_identify_alias_pix(deark *c)
{
	UI w, h, field4, field6, depth;

	depth = (UI)de_getu16be(8);
	if(depth!=24) return 0;
	w = (UI)de_getu16be(0);
	if(w<1 || w>2048) return 0;
	h = (UI)de_getu16be(2);
	if(h<1 || h>2048) return 0;
	field4 = (UI)de_getu16be(4);
	if(field4!=0) return 0;
	field6 = (UI)de_getu16be(6);
	if(field6!=0 && field6!=(h-1) && field6!=h) return 0;
	if(!aliaspix_valid_first_row(c, w, depth/8)) {
		return 0;
	}
	return 30;
}

void de_module_alias_pix(deark *c, struct deark_module_info *mi)
{
	mi->id = "alias_pix";
	mi->id_alias[0] = "vivid";
	mi->desc = "Alias PIX image, Vivid .IMG";
	mi->run_fn = de_run_alias_pix;
	mi->identify_fn = de_identify_alias_pix;
}

// **************************************************************************
// Apple volume label image
// Written by netpbm: ppmtoapplevol
// **************************************************************************

static u8 applevol_get_gray_shade(u8 clr)
{
	switch(clr) {
		// TODO: These gray shades may not be quite right. I can't find good
		// information about them.
	case 0x00: return 0xff;
	case 0xf6: return 0xee;
	case 0xf7: return 0xdd;
	case 0x2a: return 0xcc;
	case 0xf8: return 0xbb;
	case 0xf9: return 0xaa;
	case 0x55: return 0x99;
	case 0xfa: return 0x88;
	case 0xfb: return 0x77;
	case 0x80: return 0x66;
	case 0xfc: return 0x55;
	case 0xfd: return 0x44;
	case 0xab: return 0x33;
	case 0xfe: return 0x22;
	case 0xff: return 0x11;
	case 0xd6: return 0x00;
	}
	return 0xff;
}

static void de_run_applevol(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	i64 w, h;
	i64 i, j;
	i64 p;
	u8 palent;

	w = de_getu16be(1);
	h = de_getu16be(3);
	if(!de_good_image_dimensions(c, w, h)) goto done;
	img = de_bitmap_create(c, w, h, 1);

	p = 5;
	for(j=0; j<h; j++) {
		for(i=0; i<w; i++) {
			palent = de_getbyte(p+w*j+i);
			de_bitmap_setpixel_gray(img, i, j, applevol_get_gray_shade(palent));
		}
	}

	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_applevol(deark *c)
{
	u8 buf[5];

	de_read(buf, 0, sizeof(buf));

	if(buf[0]==0x01 && buf[3]==0x00 && buf[4]==0x0c)
		return 20;
	return 0;
}

void de_module_applevol(deark *c, struct deark_module_info *mi)
{
	mi->id = "applevol";
	mi->desc = "Apple volume label image";
	mi->run_fn = de_run_applevol;
	mi->identify_fn = de_identify_applevol;
}

// **************************************************************************
// TRS-80 "HR" ("High Resolution") image
// **************************************************************************

static void de_run_hr(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	de_finfo *fi = NULL;

	fi = de_finfo_create(c);
	fi->density.code = DE_DENSITY_UNK_UNITS;
	fi->density.xdens = 2;
	fi->density.ydens = 1;
	img = de_bitmap_create(c, 640, 240, 1);
	de_convert_image_bilevel(c->infile, 0, 640/8, img, 0);
	de_bitmap_write_to_file_finfo(img, fi, DE_CREATEFLAG_IS_BWIMG);
	de_bitmap_destroy(img);
	de_finfo_destroy(c, fi);
}

static int de_identify_hr(deark *c)
{
	if(de_input_file_has_ext(c, "hr")) {
		if(c->infile->len==19200) return 70;
		if(c->infile->len>19200 && c->infile->len<=19456) return 30;
	}
	return 0;
}

void de_module_hr(deark *c, struct deark_module_info *mi)
{
	mi->id = "hr";
	mi->desc = "TRS-80 HR (High Resolution) image";
	mi->run_fn = de_run_hr;
	mi->identify_fn = de_identify_hr;
}

// **************************************************************************
// RIPterm icon (.ICN)
// **************************************************************************

// Don't know what this should be, but a limit will help us decide what is
// and isn't an image.
#define MAX_RIPICON_DIMENSION 2048

static int do_one_ripicon(deark *c, i64 pos1, i64 *pbytes_consumed, int scan_mode)
{
	i64 width, height;
	de_bitmap *img = NULL;
	i64 chunk_span;
	i64 src_rowspan;
	i64 bitmap_len;
	i64 pos = pos1;
	int saved_indent_level;
	de_color pal[16];

	if(pos1+8 > c->infile->len) return 0;
	width = 1 + de_getu16le_p(&pos);
	height = 1 + de_getu16le_p(&pos);
	if(width>MAX_RIPICON_DIMENSION || height>MAX_RIPICON_DIMENSION) return 0;
	chunk_span = (width+7)/8;
	src_rowspan = 4*chunk_span;
	bitmap_len = src_rowspan * height;
	if(pos+bitmap_len > c->infile->len) return 0;

	*pbytes_consumed = 4 + bitmap_len + 2;
	if(scan_mode) return 1;

	de_dbg_indent_save(c, &saved_indent_level);
	de_dbg(c, "image at %"I64_FMT, pos1);
	de_dbg_indent(c, 1);
	de_dbg_dimensions(c, width, height);

	de_dbg(c, "bitmap at %"I64_FMT", len=%"I64_FMT, pos, bitmap_len);
	if(!de_good_image_dimensions(c, width, height)) goto done;
	img = de_bitmap_create2(c, width, chunk_span*8, height, 3);
	de_copy_std_palette(DE_PALID_PC16, 0, 0, pal, 16, 0);
	de_convert_image_paletted_planar(c->infile, pos, 4, src_rowspan, chunk_span,
		pal, img, 0);
	de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_OPT_IMAGE);

done:
	if(img) de_bitmap_destroy(img);
	de_dbg_indent_restore(c, saved_indent_level);
	return 1;
}

static void de_run_ripicon(deark *c, de_module_params *mparams)
{
	i64 pos = 0;

	while(1) {
		i64 bytes_consumed = 0;

		if(!do_one_ripicon(c, pos, &bytes_consumed, 0)) break;
		pos += bytes_consumed;
	}
}

static int de_identify_ripicon(deark *c)
{
	int has_ext = 0;
	i64 pos = 0;
	size_t i;
	static const char *exts[] = { "icn", "hot", "msk", "bgi" };

	for(i=0; i<DE_ARRAYCOUNT(exts); i++) {
		if(de_input_file_has_ext(c, exts[i])) {
			has_ext = 1;
			break;
		}
	}
	if(!has_ext) return 0;

	while(1) {
		i64 bytes_consumed = 0;

		if(!do_one_ripicon(c, pos, &bytes_consumed, 1)) break;
		pos += bytes_consumed;
		if(pos == c->infile->len) return 50;
	}
	return 0;
}

void de_module_ripicon(deark *c, struct deark_module_info *mi)
{
	mi->id = "ripicon";
	mi->desc = "RIP/RIPscrip/RIPterm Icon / BGI image";
	mi->run_fn = de_run_ripicon;
	mi->identify_fn = de_identify_ripicon;
}

// **************************************************************************
// LSS16 image (Used by SYSLINUX)
// **************************************************************************

struct lss16ctx {
	i64 pos;
	int nextnibble_valid;
	u8 nextnibble;
};

static u8 lss16_get_nibble(deark *c, struct lss16ctx *d)
{
	u8 n;
	if(d->nextnibble_valid) {
		d->nextnibble_valid = 0;
		return d->nextnibble;
	}
	n = de_getbyte(d->pos);
	d->pos++;
	// The low nibble of each byte is interpreted first.
	// Record the high nibble, and return the low nibble.
	d->nextnibble = (n&0xf0)>>4;
	d->nextnibble_valid = 1;
	return n&0x0f;
}

static void de_run_lss16(deark *c, de_module_params *mparams)
{
	struct lss16ctx *d = NULL;
	de_bitmap *img = NULL;
	i64 width, height;
	i64 i;
	i64 xpos, ypos;
	u8 n;
	u8 prev;
	i64 run_len;
	u32 pal[16];

	d = de_malloc(c, sizeof(struct lss16ctx));

	d->pos = 4;
	width = de_getu16le(d->pos);
	height = de_getu16le(d->pos+2);
	de_dbg_dimensions(c, width, height);
	if(!de_good_image_dimensions(c, width, height)) goto done;

	d->pos += 4;
	de_read_simple_palette(c, c->infile, d->pos, 16, 3, pal, 16, DE_RDPALTYPE_VGA18BIT, 0);
	d->pos += 16*3;

	img = de_bitmap_create(c, width, height, 3);

	xpos=0; ypos=0;
	prev=0;
	while(d->pos<c->infile->len && ypos<height) {
		n = lss16_get_nibble(c, d);

		if(n == prev) {
			// A run of pixels
			run_len = (i64)lss16_get_nibble(c, d);
			if(run_len==0) {
				run_len = lss16_get_nibble(c, d);
				run_len |= ((i64)lss16_get_nibble(c, d)<<4);
				run_len += 16;
			}
			for(i=0; i<run_len; i++) {
				de_bitmap_setpixel_rgb(img, xpos, ypos, pal[prev]);
				xpos++;
			}
		}
		else {
			// An uncompressed pixel
			de_bitmap_setpixel_rgb(img, xpos, ypos, pal[n]);
			xpos++;
			prev = n;
		}

		// End of row reached?
		if(xpos>=width) {
			xpos=0;
			ypos++;
			d->nextnibble_valid = 0;
			prev = 0;
		}
	}

	de_bitmap_write_to_file(img, NULL, 0);
done:
	de_bitmap_destroy(img);
	de_free(c, d);
}

static int de_identify_lss16(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "\x3d\xf3\x13\x14", 4))
		return 100;
	return 0;
}

void de_module_lss16(deark *c, struct deark_module_info *mi)
{
	mi->id = "lss16";
	mi->desc = "SYSLINUX LSS16 image";
	mi->run_fn = de_run_lss16;
	mi->identify_fn = de_identify_lss16;
}

// **************************************************************************
// VBM (VDC BitMap)
// **************************************************************************

static void de_run_vbm(deark *c, de_module_params *mparams)
{
	i64 width, height;
	u8 ver;

	ver = de_getbyte(3);
	if(ver!=2) {
		// TODO: Support VBM v3.
		de_err(c, "Unsupported VBM version (%d)", (int)ver);
		return;
	}
	width = de_getu16be(4);
	height = de_getu16be(6);
	de_convert_and_write_image_bilevel2(c->infile, 8, width, height, (width+7)/8,
		DE_CVTF_WHITEISZERO, NULL, 0);
}

// Note that this function must work together with de_identify_bmp().
static int de_identify_vbm(deark *c)
{
	u8 b[4];
	de_read(b, 0, 4);
	if(de_memcmp(b, "BM\xcb", 3)) return 0;
	if(b[3]!=2 && b[3]!=3) return 0;
	if(de_input_file_has_ext(c, "vbm")) return 100;
	return 80;
}

void de_module_vbm(deark *c, struct deark_module_info *mi)
{
	mi->id = "vbm";
	mi->desc = "C64/128 VBM (VDC BitMap)";
	mi->run_fn = de_run_vbm;
	mi->identify_fn = de_identify_vbm;
}

// **************************************************************************
// PFS: 1st Publisher clip art (.ART)
// **************************************************************************

// The minimum needed to ID std. res. files
struct fp_art_stdhdr_data {
	u8 ok;
	i64 w, h;
	i64 rowspan;
};

struct fp_art_ctx {
	struct fp_art_stdhdr_data hd;
	u8 hi_res;
	u8 need_errmsg;
};

static void fp_art_read_hdr_std(deark *c, struct fp_art_stdhdr_data *hd,
	UI fld1)
{
	i64 pos = 2;
	i64 b_left, b_right, b_top, b_bottom;

	b_left = (i64)fld1;
	b_right = de_getu16le_p(&pos);
	b_top = de_getu16le_p(&pos);
	b_bottom = de_getu16le_p(&pos);
	hd->w = b_right - b_left;
	hd->h = b_bottom - b_top;
	if(hd->w<1 || hd->h<1) return;
	hd->rowspan = ((hd->w+15)/16)*2;
	hd->ok = 1;
}

static void do_fp_art_stdres(deark *c, struct fp_art_ctx *d, UI fld1)
{
	struct fp_art_stdhdr_data *hd = &d->hd;

	fp_art_read_hdr_std(c, hd, fld1);
	if(!hd->ok) {
		d->need_errmsg = 1;
		goto done;
	}

	de_dbg_dimensions(c, hd->w, hd->h);
	if(!de_good_image_dimensions(c,hd->w, hd->h)) goto done;
	de_convert_and_write_image_bilevel2(c->infile, 8, hd->w, hd->h,
		hd->rowspan, 0, NULL, 0);

done:
	;
}

static void do_fp_art_hires(deark *c, struct fp_art_ctx *d)
{
	struct fp_art_stdhdr_data *hd = &d->hd;
	i64 pos = 2;
	UI unk1;
	dbuf *unc_pixels = NULL;
	de_finfo *fi = NULL;
	i64 xdpi, ydpi;
	i64 min_rowspan, max_rowspan;
	i64 cmpr_size;
	i64 cmpr_pos;
	i64 max_unc_size;

	fi = de_finfo_create(c);
	xdpi = de_getu16le_p(&pos);
	ydpi = de_getu16le_p(&pos);
	fi->density.code = DE_DENSITY_DPI;
	fi->density.xdens = (double)xdpi;
	fi->density.ydens = (double)ydpi;
	hd->w = de_getu16le_p(&pos);
	hd->h = de_getu16le_p(&pos);
	de_dbg_dimensions(c, hd->w, hd->h);
	unk1 = (UI)de_getu16le_p(&pos);
	if(unk1!=1) {
		d->need_errmsg = 1;
		goto done;
	}
	cmpr_pos = pos;
	if(!de_good_image_dimensions(c, hd->w, hd->h)) goto done;

	// We don't know the bytes/row yet, but it should be in this range.
	min_rowspan = ((hd->w+7)/8);
	max_rowspan = min_rowspan+3; // (+2 may be sufficient)
	max_unc_size = hd->h * max_rowspan +1;
	unc_pixels = dbuf_create_membuf(c, max_unc_size, 0x1);
	dbuf_enable_wbuffer(unc_pixels);
	cmpr_size = c->infile->len - cmpr_pos;
	fmtutil_decompress_packbits(c->infile, cmpr_pos, cmpr_size, unc_pixels, NULL);
	dbuf_flush(unc_pixels);
	de_dbg(c, "decompressed %"I64_FMT" bytes to %"I64_FMT, cmpr_size, unc_pixels->len);

	// I don't know if there's any better way to figure out the bytes/row.
	// It's weird.
	if((unc_pixels->len % hd->h) != 0) {
		d->need_errmsg = 1;
		goto done;
	}
	hd->rowspan = unc_pixels->len / hd->h;
	if(hd->rowspan<min_rowspan || hd->rowspan>max_rowspan) {
		d->need_errmsg = 1;
		goto done;
	}
	de_dbg(c, "bytes/row: %"I64_FMT, hd->rowspan);
	de_dbg2(c, "padding bytes/row: %d", (int)(hd->rowspan-min_rowspan));

	de_convert_and_write_image_bilevel2(unc_pixels, 0, hd->w, hd->h, hd->rowspan,
		0, fi, 0);

done:
	dbuf_close(unc_pixels);
	de_finfo_destroy(c, fi);
}

static void de_run_fp_art(deark *c, de_module_params *mparams)
{
	struct fp_art_ctx *d = NULL;
	UI fld1;

	d = de_malloc(c, sizeof(struct fp_art_ctx));
	fld1 = (UI)de_getu16le(0);
	if(fld1==0xffff) {
		d->hi_res = 1;
	}
	de_declare_fmtf(c, "PFS: 1st Publisher Art, %s res.",
		(d->hi_res?"high":"standard"));

	if(d->hi_res) {
		do_fp_art_hires(c, d);
	}
	else {
		do_fp_art_stdres(c, d, fld1);
	}

	if(d) {
		if(d->need_errmsg) {
			de_err(c, "Bad or unsupported ART file");
		}
		de_free(c, d);
	}
}

static int fp_art_id_stdres(deark *c, UI fld1)
{
	struct fp_art_stdhdr_data hd;

	de_zeromem(&hd, sizeof(struct fp_art_stdhdr_data));
	fp_art_read_hdr_std(c, &hd, fld1);
	if(!hd.ok) return 0;
	if(8 + hd.rowspan*hd.h == c->infile->len) {
		return 74;
	}
	if(8 + hd.rowspan*(hd.h+1) == c->infile->len) {
		// e.g. BANNER.ART from v3.0
		return 24;
	}
	return 0;
}

static int fp_art_id_hires(deark *c)
{
	UI n;
	i64 pos = 2;

	n = (UI)de_getu16le_p(&pos); // xres?
	if(n!=300) return 0;
	n = (UI)de_getu16le_p(&pos); // yres?
	if(n!=300) return 0;
	n = (UI)de_getu16le_p(&pos); // width
	if(n<1 || n>8192) return 0;
	n = (UI)de_getu16le_p(&pos); // height
	if(n<1 || n>8192) return 0;
	n = (UI)de_getu16le_p(&pos); // ?
	if(n!=1) return 0;
	return 59;
}

static int de_identify_fp_art(deark *c)
{
	UI fld1;

	if(!de_input_file_has_ext(c, "art")) return 0;
	fld1 = (UI)de_getu16le(0);
	if(fld1==0xffff) {
		return fp_art_id_hires(c);
	}
	return fp_art_id_stdres(c, fld1);
}

void de_module_fp_art(deark *c, struct deark_module_info *mi)
{
	mi->id = "fp_art";
	mi->desc = "PFS: 1st Publisher clip art (.ART)";
	mi->run_fn = de_run_fp_art;
	mi->identify_fn = de_identify_fp_art;
}

// **************************************************************************
// YBM
// **************************************************************************

static void de_run_ybm(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	i64 npwidth, pdwidth, height;
	i64 i, j;
	i64 words_per_row;
	i64 pos;

	pos = 2;
	npwidth = de_getu16be_p(&pos);
	height = de_getu16be_p(&pos);
	de_dbg_dimensions(c, npwidth, height);
	if(!de_good_image_dimensions(c, npwidth, height)) goto done;
	pdwidth = de_pad_to_n(npwidth, 16);
	words_per_row = pdwidth/16;

	img = de_bitmap_create2(c, npwidth, pdwidth, height, 1);

	for(j=0; j<height; j++) {
		for(i=0; i<words_per_row; i++) {
			u8 x;

			// This encoding is unusual: LSB-first 16-bit integers.
			x = de_getbyte_p(&pos);
			de_unpack_pixels_bilevel_from_byte(img, i*16+8, j, x, 8,
				DE_CVTF_WHITEISZERO|DE_CVTF_LSBFIRST|DE_CVTF_ONLYWHITE);
			x = de_getbyte_p(&pos);
			de_unpack_pixels_bilevel_from_byte(img, i*16, j, x, 8,
				DE_CVTF_WHITEISZERO|DE_CVTF_LSBFIRST|DE_CVTF_ONLYWHITE);
		}
	}
	de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_IS_BWIMG);

done:
	de_bitmap_destroy(img);
}

static int de_identify_ybm(deark *c)
{
	i64 width, height;
	i64 rowspan;

	if(dbuf_memcmp(c->infile, 0, "!!", 2))
		return 0;
	width = de_getu16be(2);
	height = de_getu16be(4);
	rowspan = ((width+15)/16)*2;
	if(6+height*rowspan == c->infile->len)
		return 100;
	return 0;
}

void de_module_ybm(deark *c, struct deark_module_info *mi)
{
	mi->id = "ybm";
	mi->desc = "Bennet Yee's face format, a.k.a. YBM";
	mi->run_fn = de_run_ybm;
	mi->identify_fn = de_identify_ybm;
}

// **************************************************************************
// OLPC .565 firmware icon
// **************************************************************************

static void de_run_olpc565(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	i64 width, height;
	i64 i, j;
	i64 rowspan;
	u8 b0, b1;
	u32 clr;

	width = de_getu16le(4);
	height = de_getu16le(6);
	if(!de_good_image_dimensions(c, width, height)) goto done;
	rowspan = width*2;

	img = de_bitmap_create(c, width, height, 3);

	for(j=0; j<height; j++) {
		for(i=0; i<width; i++) {
			b0 = de_getbyte(8 + j*rowspan + i*2);
			b1 = de_getbyte(8 + j*rowspan + i*2 + 1);
			clr = (((u32)b1)<<8) | b0;
			clr = de_rgb565_to_888(clr);
			de_bitmap_setpixel_rgb(img, i, j, clr);
		}
	}
	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_olpc565(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "C565", 4))
		return 100;
	return 0;
}

void de_module_olpc565(deark *c, struct deark_module_info *mi)
{
	mi->id = "olpc565";
	mi->desc = "OLPC .565 firmware icon";
	mi->run_fn = de_run_olpc565;
	mi->identify_fn = de_identify_olpc565;
}

// **************************************************************************
// InShape .IIM
// **************************************************************************

static void de_run_iim(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	i64 width, height;
	i64 i, j;
	i64 n, bpp;
	i64 rowspan;
	u32 clr;

	// This code is based on reverse engineering, and may be incorrect.

	n = de_getu16be(8); // Unknown field
	bpp = de_getu16be(10);
	if(n!=4 || bpp!=24) {
		de_dbg(c, "This type of IIM image is not supported");
		goto done;
	}
	width = de_getu16be(12);
	height = de_getu16be(14);
	if(!de_good_image_dimensions(c, width, height)) goto done;
	rowspan = width*3;

	img = de_bitmap_create(c, width, height, 3);

	for(j=0; j<height; j++) {
		for(i=0; i<width; i++) {
			clr = dbuf_getRGB(c->infile, 16+j*rowspan+i*3, 0);
			de_bitmap_setpixel_rgb(img, i, j, clr);
		}
	}
	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_iim(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "IS_IMAGE", 8))
		return 100;
	return 0;
}

void de_module_iim(deark *c, struct deark_module_info *mi)
{
	mi->id = "iim";
	mi->desc = "InShape IIM";
	mi->run_fn = de_run_iim;
	mi->identify_fn = de_identify_iim;
}

// **************************************************************************
// PM (format supported by the XV image viewer)
// **************************************************************************

static void de_run_pm_xv(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	int is_le;
	i64 width, height;
	i64 nplanes;
	i64 nbands;
	i64 pixelformat;
	i64 commentsize;
	i64 i, j;
	i64 plane;
	i64 rowspan;
	i64 planespan;
	i64 pos;
	u8 b;

	if(!dbuf_memcmp(c->infile, 0, "WEIV", 4))
		is_le = 1;
	else
		is_le = 0;

	nplanes = dbuf_geti32x(c->infile, 4, is_le);
	de_dbg(c, "planes: %d", (int)nplanes);

	height = dbuf_geti32x(c->infile, 8, is_le);
	width = dbuf_geti32x(c->infile, 12, is_le);
	de_dbg_dimensions(c, width, height);
	if(!de_good_image_dimensions(c, width, height)) goto done;

	nbands = dbuf_geti32x(c->infile, 16, is_le);
	de_dbg(c, "bands: %d", (int)nbands);

	pixelformat = dbuf_geti32x(c->infile, 20, is_le);
	de_dbg(c, "pixel format: 0x%04x", (unsigned int)pixelformat);

	commentsize = dbuf_geti32x(c->infile, 24, is_le);
	de_dbg(c, "comment size: %d", (int)commentsize);

	pos = 28;

	if((pixelformat==0x8001 && nplanes==3 && nbands==1) ||
		(pixelformat==0x8001 && nplanes==1 && nbands==1))
	{
		;
	}
	else {
		de_err(c, "Unsupported image type (pixel format=0x%04x, "
			"planes=%d, bands=%d)", (unsigned int)pixelformat,
			(int)nplanes, (int)nbands);
		goto done;
	}

	rowspan = width;
	planespan = rowspan*height;

	img = de_bitmap_create(c, width, height, (int)nplanes);

	for(plane=0; plane<nplanes; plane++) {
		for(j=0; j<height; j++) {
			for(i=0; i<width; i++) {
				b = de_getbyte(pos + plane*planespan + j*rowspan + i);
				if(nplanes==3) {
					de_bitmap_setsample(img, i, j, plane, b);
				}
				else {
					de_bitmap_setpixel_gray(img, i, j, b);
				}
			}
		}
	}
	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_pm_xv(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "VIEW", 4))
		return 15;
	if(!dbuf_memcmp(c->infile, 0, "WEIV", 4))
		return 15;
	return 0;
}

void de_module_pm_xv(deark *c, struct deark_module_info *mi)
{
	mi->id = "pm_xv";
	mi->desc = "PM (XV)";
	mi->run_fn = de_run_pm_xv;
	mi->identify_fn = de_identify_pm_xv;
}

// **************************************************************************
// Calamus Raster Graphic - CRG
// **************************************************************************

// Warning: The CRG decoder is based on reverse engineering, may not be
// correct, and is definitely incomplete.

static void de_run_crg(deark *c, de_module_params *mparams)
{
	i64 width, height;
	i64 rowspan;
	i64 pos;
	u8 b1, b2;
	i64 count;
	i64 cmpr_img_start;
	i64 num_cmpr_bytes;
	dbuf *unc_pixels = NULL;

	width = de_getu32be(20);
	height = de_getu32be(24);
	de_dbg_dimensions(c, width, height);
	if(!de_good_image_dimensions(c, width, height)) goto done;

	b1 = de_getbyte(32);
	if(b1!=0x01) {
		de_err(c, "Unsupported CRG format");
		goto done;
	}

	num_cmpr_bytes = de_getu32be(38);
	de_dbg(c, "compressed data size: %d", (int)num_cmpr_bytes);
	cmpr_img_start = 42;

	if(cmpr_img_start + num_cmpr_bytes > c->infile->len) {
		num_cmpr_bytes = c->infile->len - cmpr_img_start;
	}

	// Uncompress the image
	rowspan = (width+7)/8;
	unc_pixels = dbuf_create_membuf(c, height*rowspan, 1);

	pos = cmpr_img_start;
	while(pos < cmpr_img_start + num_cmpr_bytes) {
		b1 = de_getbyte(pos++);
		if(b1<=0x7f) { // Uncompressed bytes
			count = 1+(i64)b1;
			dbuf_copy(c->infile, pos, count, unc_pixels);
			pos += count;
		}
		else { // A compressed run
			b2 = de_getbyte(pos++);
			count = (i64)(b1-127);
			dbuf_write_run(unc_pixels, b2, count);
		}
	}
	de_dbg(c, "decompressed to %d bytes", (int)unc_pixels->len);

	de_convert_and_write_image_bilevel2(unc_pixels, 0, width, height, rowspan,
		DE_CVTF_WHITEISZERO, NULL, 0);

done:
	dbuf_close(unc_pixels);
}

static int de_identify_crg(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "CALAMUSCRG", 10))
		return 100;
	return 0;
}

void de_module_crg(deark *c, struct deark_module_info *mi)
{
	mi->id = "crg";
	mi->desc = "Calamus Raster Graphic";
	mi->run_fn = de_run_crg;
	mi->identify_fn = de_identify_crg;
}

// **************************************************************************
// farbfeld
// **************************************************************************

static void de_run_farbfeld(deark *c, de_module_params *mparams)
{
	de_bitmap *imghi = NULL; // high 8 bits of each sample
	de_bitmap *imglo = NULL; // low 8 bits of each sample
	i64 width, height;
	i64 i, j, k;
	i64 ppos;
	u8 sh[4];
	u8 sl[4];

	width = de_getu32be(8);
	height = de_getu32be(12);
	de_dbg_dimensions(c, width, height);
	if(!de_good_image_dimensions(c, width, height)) return;

	imghi = de_bitmap_create(c, width, height, 4);
	imglo = de_bitmap_create(c, width, height, 4);

	for(j=0; j<height; j++) {
		for(i=0; i<width; i++) {
			ppos = 16 + 8*(width*j + i);
			for(k=0; k<4; k++) {
				sh[k] = de_getbyte(ppos+2*k);
				sl[k] = de_getbyte(ppos+2*k+1);
			}
			de_bitmap_setpixel_rgba(imghi, i, j,
				DE_MAKE_RGBA(sh[0],sh[1],sh[2],sh[3]));
			de_bitmap_setpixel_rgba(imglo, i, j,
				DE_MAKE_RGBA(sl[0],sl[1],sl[2],sl[3]));
		}
	}
	de_bitmap16_write_to_file_finfo(imghi, imglo, NULL, DE_CREATEFLAG_OPT_IMAGE);
	de_bitmap_destroy(imghi);
	de_bitmap_destroy(imglo);
}

static int de_identify_farbfeld(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "farbfeld", 8))
		return 100;
	return 0;
}

void de_module_farbfeld(deark *c, struct deark_module_info *mi)
{
	mi->id = "farbfeld";
	mi->desc = "farbfeld image";
	mi->run_fn = de_run_farbfeld;
	mi->identify_fn = de_identify_farbfeld;
}

// **************************************************************************
// HSI Raw image format (from Image Alchemy / Handmade Software)
// **************************************************************************

static void de_run_hsiraw(deark *c, de_module_params *mparams)
{
	i64 w, h;
	i64 num_pal_colors;
	i64 pos;
	i64 ver;
	i64 hdpi, vdpi;
	i64 cmpr;
	i64 alpha_info;
	de_bitmap *img = NULL;
	de_color pal[256];
	int is_grayscale;

	ver = de_getu16be(6);
	de_dbg(c, "version: %d", (int)ver);
	if(ver!=4) {
		de_warn(c, "HSI Raw version %d might not be supported correctly", (int)ver);
	}

	w = de_getu16be(8);
	if(w==0) {
		// MPlayer extension?
		de_dbg2(c, "reading 32-bit width");
		w = de_getu32be(28);
	}
	h = de_getu16be(10);
	de_dbg_dimensions(c, w, h);
	num_pal_colors = de_getu16be(12);
	de_dbg(c, "number of palette colors: %d", (int)num_pal_colors);

	hdpi = de_geti16be(14);
	vdpi = de_geti16be(16);
	de_dbg(c, "density: %d"DE_CHAR_TIMES"%d", (int)hdpi, (int)vdpi);
	// [18: Gamma]
	cmpr = de_getu16be(20);
	de_dbg(c, "compression: %d", (int)cmpr);
	alpha_info = de_getu16be(22);
	de_dbg(c, "alpha: %d", (int)alpha_info);

	if(num_pal_colors>256 || cmpr!=0 || alpha_info!=0) {
		de_err(c, "This type of HSI Raw image is not supported");
		goto done;
	}
	if(!de_good_image_dimensions(c, w, h)) goto done;

	pos = 32;
	de_zeromem(pal, sizeof(pal));
	if(num_pal_colors==0) { // 24-bit RGB
		is_grayscale = 0;
	}
	else { // 8-bit paletted
		de_read_simple_palette(c, c->infile, pos, num_pal_colors, 3, pal, 256,
			DE_RDPALTYPE_24BIT, 0);
		pos += 3*num_pal_colors;
		is_grayscale = de_is_grayscale_palette(pal, num_pal_colors);
	}

	img = de_bitmap_create(c, w, h, is_grayscale?1:3);

	if(num_pal_colors==0) {
		de_convert_image_rgb(c->infile, pos, 3*w, 3, img, 0);
	}
	else {
		de_convert_image_paletted(c->infile, pos, 8, w, pal, img, 0);
	}

	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_hsiraw(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "mhwanh", 6))
		return 100;
	return 0;
}

void de_module_hsiraw(deark *c, struct deark_module_info *mi)
{
	mi->id = "hsiraw";
	mi->desc = "HSI Raw";
	mi->run_fn = de_run_hsiraw;
	mi->identify_fn = de_identify_hsiraw;
}

// **************************************************************************
// QDV (Giffer)
// **************************************************************************

static void de_run_qdv(deark *c, de_module_params *mparams)
{
	i64 w, h;
	i64 num_pal_colors;
	i64 pos;
	de_bitmap *img = NULL;
	de_color pal[256];

	// Warning: This decoder is based on reverse engineering, and may be
	// incorrect or incomplete.

	w = de_getu16be(0);
	h = de_getu16be(2);
	de_dbg_dimensions(c, w, h);
	if(!de_good_image_dimensions(c, w, h)) goto done;

	num_pal_colors = 1 + (i64)de_getbyte(4);
	de_dbg(c, "number of palette colors: %d", (int)num_pal_colors);

	pos = 5;
	de_read_simple_palette(c, c->infile, pos, num_pal_colors, 3, pal, 256,
		DE_RDPALTYPE_24BIT, DE_RDPALFLAG_INITPAL);
	pos += 3*num_pal_colors;

	img = de_bitmap_create(c, w, h, 3);
	de_convert_image_paletted(c->infile, pos, 8, w, pal, img, 0);
	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
}

static int de_identify_qdv(deark *c)
{
	i64 w, h;
	i64 num_pal_colors;

	w = de_getu16be(0);
	h = de_getu16be(2);
	num_pal_colors = 1 + (i64)de_getbyte(4);
	if(5+num_pal_colors*3+w*h != c->infile->len)
		return 0;
	if(de_input_file_has_ext(c, "qdv"))
		return 100;
	return 30;
}

void de_module_qdv(deark *c, struct deark_module_info *mi)
{
	mi->id = "qdv";
	mi->desc = "QDV (Giffer)";
	mi->run_fn = de_run_qdv;
	mi->identify_fn = de_identify_qdv;
}

// **************************************************************************
// VITec image format
// **************************************************************************

static void de_run_vitec(deark *c, de_module_params *mparams)
{
	i64 npwidth, pdwidth, h;
	i64 i, j, plane;
	de_bitmap *img = NULL;
	i64 samplesperpixel;
	i64 rowspan, planespan;
	i64 pos;
	u8 b;
	i64 h1size, h2size;
	int saved_indent_level;

	// This code is based on reverse engineering, and may be incorrect.

	de_dbg_indent_save(c, &saved_indent_level);
	de_warn(c, "VITec image support is experimental, and may not work correctly.");

	pos = 4;
	h1size = de_getu32be(pos);
	de_dbg(c, "header 1 at %d, len=%d", (int)pos, (int)h1size);
	// Don't know what's in this part of the header. Just ignore it.
	pos += h1size;
	if(pos>=c->infile->len) goto done;

	h2size = de_getu32be(pos);
	de_dbg(c, "header 2 at %d, len=%d", (int)pos, (int)h2size);
	de_dbg_indent(c, 1);

	// pos+4: Bits size?
	// pos+24: Unknown field, usually 7

	npwidth = de_getu32be(pos+36);
	h = de_getu32be(pos+40);
	de_dbg_dimensions(c, npwidth, h);
	if(!de_good_image_dimensions(c, npwidth, h)) goto done;

	// pos+52: Unknown field, 1 in grayscale images

	samplesperpixel = de_getu32be(pos+56);
	de_dbg(c, "samples/pixel: %d", (int)samplesperpixel);
	if(samplesperpixel!=1 && samplesperpixel!=3) {
		de_err(c, "Unsupported samples/pixel: %d", (int)samplesperpixel);
		goto done;
	}

	pos += h2size;
	if(pos>=c->infile->len) goto done;
	de_dbg_indent(c, -1);

	de_dbg(c, "bitmap at %d", (int)pos);
	pdwidth = de_pad_to_n(npwidth, 8);
	img = de_bitmap_create2(c, npwidth, pdwidth, h, (int)samplesperpixel);
	rowspan = pdwidth;
	planespan = rowspan*h;

	for(plane=0; plane<samplesperpixel; plane++) {
		for(j=0; j<h; j++) {
			for(i=0; i<pdwidth; i++) {
				b = de_getbyte(pos + plane*planespan + j*rowspan + i);
				if(samplesperpixel==3) {
					de_bitmap_setsample(img, i, j, plane, b);
				}
				else {
					de_bitmap_setpixel_gray(img, i, j, b);
				}
			}
		}
	}

	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
	de_dbg_indent_restore(c, saved_indent_level);
}

static int de_identify_vitec(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "\x00\x5b\x07\x20", 4))
		return 100;
	return 0;
}

void de_module_vitec(deark *c, struct deark_module_info *mi)
{
	mi->id = "vitec";
	mi->desc = "VITec image format";
	mi->run_fn = de_run_vitec;
	mi->identify_fn = de_identify_vitec;
}

// **************************************************************************
// HS2 module
//
// .HS2 format is associated with a program called POSTERING.
// **************************************************************************

static void de_run_hs2(deark *c, de_module_params *mparams)
{
	i64 width, height;
	i64 rowspan;

	rowspan = 105;
	width = rowspan*8;
	height = (c->infile->len+(rowspan-1))/rowspan;
	de_convert_and_write_image_bilevel(c->infile, 0, width, height, rowspan, 0, NULL, 0);
}

static int de_identify_hs2(deark *c)
{
	if(!de_input_file_has_ext(c, "hs2")) return 0;
	if(c->infile->len>0 && (c->infile->len%105 == 0)) {
		return 15;
	}
	return 0;
}

void de_module_hs2(deark *c, struct deark_module_info *mi)
{
	mi->id = "hs2";
	mi->desc = "HS2 (POSTERING)";
	mi->run_fn = de_run_hs2;
	mi->identify_fn = de_identify_hs2;
}


// **************************************************************************
// Lumena CEL
// **************************************************************************

static void de_run_lumena_cel(deark *c, de_module_params *mparams)
{
	i64 width, height;
	i64 rowspan;
	i64 i, j;
	u32 clr;
	u8 a;
	int is_16bit = 0;
	int is_32bit = 0;
	de_bitmap *img = NULL;
	const i64 headersize = 4;
	i64 bypp;

	width = de_getu16le(0);
	height = de_getu16le(2);
	if(!de_good_image_dimensions_noerr(c, width, height)) goto done;

	// TODO: Support multi-image files
	is_16bit = (c->infile->len == headersize + width*height*2);
	is_32bit = (c->infile->len == headersize + width*height*4);
	if(!is_16bit && !is_32bit) {
		de_warn(c, "Cannot detect bits/pixel, assuming 32");
		is_32bit = 1;
	}

	bypp = (is_32bit) ? 4 : 2;
	de_dbg(c, "bytes/pixel: %d", (int)bypp);
	rowspan = width * bypp;

	img = de_bitmap_create(c, width, height, is_32bit?4:3);

	for(j=0; j<height; j++) {
		for(i=0; i<width; i++) {
			i64 pos = headersize + j*rowspan + i*bypp;
			if(is_32bit) {
				clr = dbuf_getRGB(c->infile, pos, 0);
				a = de_getbyte(pos + 3);
				clr = DE_SET_ALPHA(clr, a);
			}
			else {
				clr = (u32)de_getu16le(pos);
				clr = de_rgb555_to_888(clr);
			}
			de_bitmap_setpixel_rgba(img, i, j, clr);
		}
	}

	de_bitmap_optimize_alpha(img, 0x3);
	de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_FLIP_IMAGE);

done:
	de_bitmap_destroy(img);
}

static int de_identify_lumena_cel(deark *c)
{
	i64 width, height;
	int is_16bit = 0;
	int is_32bit = 0;

	if(!de_input_file_has_ext(c, "cel")) return 0;
	width = de_getu16le(0);
	height = de_getu16le(2);

	is_16bit = (c->infile->len == 4 + width*height*2);
	is_32bit = (c->infile->len == 4 + width*height*4);

	if(is_16bit || is_32bit)
		return 60;

	return 0;
}

void de_module_lumena_cel(deark *c, struct deark_module_info *mi)
{
	mi->id = "lumena_cel";
	mi->desc = "Lumena CEL";
	mi->run_fn = de_run_lumena_cel;
	mi->identify_fn = de_identify_lumena_cel;
}

// **************************************************************************
// Tandy DeskMate Paint .PNT
// **************************************************************************

static void de_run_deskmate_pnt(deark *c, de_module_params *mparams)
{
	i64 w, h;
	i64 rowspan;
	i64 pos = 0;
	int is_compressed;
	de_bitmap *img = NULL;
	dbuf *unc_pixels = NULL;
	i64 unc_pixels_size;
	de_color pal[16];

	pos += 22;
	de_dbg(c, "image at %"I64_FMT, pos);
	w = 312;
	h = 176;
	rowspan = w/2;
	unc_pixels_size = rowspan * h;

	de_copy_std_palette(DE_PALID_PC16, 0, 0, pal, 16, 0);

	is_compressed = (pos+unc_pixels_size != c->infile->len);
	de_dbg(c, "compressed: %d", is_compressed);

	if(is_compressed) {
		unc_pixels = dbuf_create_membuf(c, unc_pixels_size, 0x1);
		while(1) {
			i64 count;
			u8 val;

			if(pos >= c->infile->len) break; // out of source data
			if(unc_pixels->len >= unc_pixels_size) break; // enough dst data
			val = de_getbyte_p(&pos);
			count = (i64)de_getbyte_p(&pos);
			dbuf_write_run(unc_pixels, val, count);
		}
	}
	else {
		unc_pixels = dbuf_open_input_subfile(c->infile, pos, unc_pixels_size);
	}

	img = de_bitmap_create(c, w, h, 3);
	de_convert_image_paletted(unc_pixels, 0, 4, rowspan, pal, img, 0);
	de_bitmap_write_to_file(img, NULL, 0);

	dbuf_close(unc_pixels);
	de_bitmap_destroy(img);
}

static int de_identify_deskmate_pnt(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, "\x13" "PNT", 4)) return 100;
	return 0;
}

void de_module_deskmate_pnt(deark *c, struct deark_module_info *mi)
{
	mi->id = "deskmate_pnt";
	mi->desc = "Tandy DeskMate Paint";
	mi->run_fn = de_run_deskmate_pnt;
	mi->identify_fn = de_identify_deskmate_pnt;
}

// **************************************************************************
// Magic Desk icon (.ICN)
// **************************************************************************

static void de_run_mdesk_icn(deark *c, de_module_params *mparams)
{
	const char *s;
	int pal_req = 0;
	u8 toybox_mode = 0;
	u8 b2;
	de_bitmap *img = NULL;
	de_color pal[16];
	static const de_color pal_mdesk[16] = {
		0xff000000U, 0xffaa0000U, 0xff00aa00U, 0xffaaaa00U,
		0xff0000aaU, 0xffaa00aaU, 0xff00aaaaU, 0xff7d7d7dU,
		0xffbababaU, 0xffff5555U, 0xff55ff55U, 0xffffff55U,
		0xff5555ffU, 0xffff55ffU, 0xff55ffffU, 0xffffffffU
	};

	s = de_get_ext_option(c, "mdesk_icn:pal");
	if(s) {
		pal_req = de_atoi(s);
	}

	if(pal_req<1 || pal_req>4) pal_req = 4; // default = prefer Magic Desk

	b2 = de_getbyte(2);
	de_dbg2(c, "byte[2]: 0x%02x", (UI)b2);

	if(pal_req==1) { //  ToyBox only
		toybox_mode = 1;
	}
	else if(pal_req==2) { // Magic Desk only
		toybox_mode = 0;
	}
	else if(pal_req==3) { // prefer ToyBox
		toybox_mode = (b2 != 0x03);
	}
	else {
		toybox_mode = (b2 == 0x00);
	}

	de_declare_fmtf(c, "%s icon", (toybox_mode?"ToyBox":"Magic Desk"));

	if(toybox_mode) {
		de_copy_std_palette(DE_PALID_PC16, 0, 0, pal, 16, 0);
	}
	else {
		de_memcpy(pal, pal_mdesk, sizeof(pal));
	}

	img = de_bitmap_create(c, 32, 32, 3);
	de_convert_image_paletted(c->infile, 3, 4, 16, pal, img, 0);
	de_bitmap_transpose(img);
	de_bitmap_write_to_file(img, NULL, 0);
	de_bitmap_destroy(img);
}

static int de_identify_mdesk_icn(deark *c)
{
	u8 b2;
	int has_ext;
	int b2_ok;

	if(c->infile->len!=515) return 0;
	if(de_getu16be(0)!=0x1f1f) return 0;
	b2 = de_getbyte(2);

	has_ext = (de_input_file_has_ext(c, "icn") ||
		de_input_file_has_ext(c, "tbi"));
	b2_ok = (b2==0x00 || b2==0x03 || b2==0xff);

	if(has_ext && b2_ok) {
		return 90;
	}
	return 14;
}

static void de_help_mdesk_icn(deark *c)
{
	de_msg(c, "-opt mdesk_icn:pal=<n> : Refer to documentation");
}

void de_module_mdesk_icn(deark *c, struct deark_module_info *mi)
{
	mi->id = "mdesk_icn";
	mi->desc = "Magic Desk icon";
	mi->run_fn = de_run_mdesk_icn;
	mi->identify_fn = de_identify_mdesk_icn;
	mi->help_fn = de_help_mdesk_icn;
}

// **************************************************************************
// Autodesk Animator PIC/CEL
// **************************************************************************

static void de_run_animator_pic(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	de_finfo *fi = NULL;
	i64 pos = 2;
	i64 w, h;
	i64 imgsize;
	de_color pal[256];

	w = de_getu16le_p(&pos);
	h = de_getu16le_p(&pos);
	de_dbg_dimensions(c, w, h);
	if(!de_good_image_dimensions(c, w, h)) goto done;
	pos += 4; // xcoord/ycoord
	pos += 1; // depth
	pos += 1; // compression type
	imgsize = de_getu32le_p(&pos);
	de_dbg(c, "image size: %"I64_FMT, imgsize);
	pos += 16;
	de_read_simple_palette(c, c->infile, pos, 256, 3, pal, 256, DE_RDPALTYPE_VGA18BIT, 0);
	pos += 3*256;

	fi = de_finfo_create(c);
	fi->density.code = DE_DENSITY_UNK_UNITS;
	fi->density.xdens = 240.0;
	fi->density.ydens = 200.0;
	img = de_bitmap_create(c, w, h, 3);
	de_convert_image_paletted(c->infile, pos, 8, w, pal, img, 0);;
	de_bitmap_write_to_file_finfo(img, fi, 0);

done:
	de_bitmap_destroy(img);
	de_finfo_destroy(c, fi);
}

static int de_identify_animator_pic(deark *c)
{
	if(de_getu16le(0)!=0x9119) return 0;
	if(de_getbyte(10)!=0x08) return 0;
	if(de_getbyte(11)!=0x00) return 0;
	if(de_input_file_has_ext(c, "pic") ||
		de_input_file_has_ext(c, "cel"))
	{
		return 100;
	}
	return 80;
}

void de_module_animator_pic(deark *c, struct deark_module_info *mi)
{
	mi->id = "animator_pic";
	mi->desc = "Autodesk Animator PIC/CEL";
	mi->run_fn = de_run_animator_pic;
	mi->identify_fn = de_identify_animator_pic;
}

// **************************************************************************
// DGI (Digi-Pic 2)
// **************************************************************************

static void do_dgi_convert_quadrant(deark *c, dbuf *imgbuf, i64 srcoffs, i64 dstoffs)
{
	i64 j;

	for(j=0; j<50; j++) {
		dbuf_copy_at(c->infile, srcoffs+j*160, 80, imgbuf, dstoffs+j*640);
		dbuf_copy_at(c->infile, srcoffs+j*160+80, 80, imgbuf, dstoffs+j*640+320);
		dbuf_copy_at(c->infile, srcoffs+8000+j*160, 80, imgbuf, dstoffs+j*640+160);
		dbuf_copy_at(c->infile, srcoffs+8000+j*160+80, 80, imgbuf, dstoffs+j*640+480);
	}
}

static void de_run_dgi(deark *c, de_module_params *mparams)
{
	dbuf *imgbuf = NULL;
	de_bitmap *img = NULL;
	de_finfo *fi = NULL;
	de_color pal[4];

	imgbuf = dbuf_create_membuf(c, 64000, 0x1);

	// Full 640x400 image is stored as four 320x200-pixel quadrants.
	// Each quadrant is interlaced.
	do_dgi_convert_quadrant(c, imgbuf, 0, 0);
	do_dgi_convert_quadrant(c, imgbuf, 16000, 80);
	do_dgi_convert_quadrant(c, imgbuf, 32008, 32000);
	do_dgi_convert_quadrant(c, imgbuf, 48008, 32080);

	de_copy_std_palette(DE_PALID_CGA, 1, 0, pal, 4, 0);

	fi = de_finfo_create(c);
	img = de_bitmap_create(c, 640, 400, 3);
	de_convert_image_paletted(imgbuf, 0, 2, 160, pal, img, 0);

	// DGI targets CGA 320x200.
	fi->density.code = DE_DENSITY_UNK_UNITS;
	fi->density.xdens = 240;
	fi->density.ydens = 200;

	de_bitmap_write_to_file_finfo(img, fi, 0);

	dbuf_close(imgbuf);
	de_bitmap_destroy(img);
	de_finfo_destroy(c, fi);
}

static int de_identify_dgi(deark *c)
{
	int has_ext;

	if(c->infile->len < 64008) return 0;
	has_ext = de_input_file_has_ext(c, "dgi");
	if(!has_ext && c->infile->len!=64008) return 0;
	if(dbuf_memcmp(c->infile, 32000, (const void*)"\x01\x04\0\0\0\0\0\0", 8)) return 0;
	if(has_ext && c->infile->len==64008) return 100;
	return 20; // either no .dgi extension, or file unexpectedly large (but not both)
}

void de_module_dgi(deark *c, struct deark_module_info *mi)
{
	mi->id = "dgi";
	mi->desc = "DGI (Digi-Pic)";
	mi->run_fn = de_run_dgi;
	mi->identify_fn = de_identify_dgi;
}

// **************************************************************************
// CompuServe RLE
// **************************************************************************

static u8 get_cserve_rle_fmt(deark *c, u8 strictmode)
{
	u8 buf[64];
	u8 fmt = 0;
	UI k;

	de_read(buf, 0, 3);
	if(buf[0]!=0x1b || buf[1]!='G') return 0;
	if(buf[2]=='M') fmt = 1;
	else if(buf[2]=='H') fmt = 2;
	else if(buf[2]=='C') {
		// Ref: http://cd.textfiles.com/gigabytesw/010a/crle.zip
		fmt = 3;
	}
	if(!fmt) return 0;
	if(!strictmode) return fmt;
	de_read(buf, 3, 64);
	for(k=0; k<64; k++) {
		if(buf[k]<0x20 || buf[k]>0x7f) return 0;
		if(fmt==3) {
			if((k%2) && buf[k]>0x23) {
				// Invalid color
				return 0;
			}
		}
		else {
			if(k>0 && buf[k]==0x20 && buf[k-1]==0x20) {
				// Shouldn't have two 0-length runs in a row
				return 0;
			}
		}
	}
	return fmt;
}

static void de_run_cserve_rle(deark *c, de_module_params *mparams)
{
	u8 fmt;
	const char *name;
	i64 w, expected_h, actual_h;
	i64 max_npixels;
	i64 npixels_expected;
	i64 npixels_found;
	i64 pos;
	i64 count;
	u8 parity;
	u8 next_color;
	dbuf *unc_pixels = NULL;
	de_bitmap *img = NULL;
	de_color pal[256];

	fmt = get_cserve_rle_fmt(c, 0);
	if(fmt==1) {
		w = 128;
		expected_h = 96;
	}
	else if(fmt==2 || fmt==3) {
		w = 256;
		expected_h = 192;
	}
	else {
		de_err(c, "Not a CompuServe RLE file");
		goto done;
	}

	if(fmt==1) name = "med. res.";
	else if(fmt==3) name = "color";
	else name = "high res.";
	de_declare_fmtf(c, "CompuServe RLE, %s", name);

	de_dbg(c, "width: %"I64_FMT, w);
	npixels_expected = w*expected_h;
#define CSRLE_MAX_H 1920 // arbitrary
	max_npixels = w*CSRLE_MAX_H;
	unc_pixels = dbuf_create_membuf(c, max_npixels, 0x1);
	dbuf_enable_wbuffer(unc_pixels);
	pos = 3;
	npixels_found = 0;
	next_color = 1; // 1=black, 2=white, 0=unset
	parity = 0;
	count = 0;
	while(1) {
		u8 x;

		if(npixels_found >= max_npixels) {
			break;
		}
		if(pos >= c->infile->len) break;

		x = de_getbyte_p(&pos);
		if(x>=0x20) {
			if(parity==0) {
				count = (i64)x - 0x20;
				if(fmt==3) {
					parity = !parity;
					continue;
				}
			}
			if(parity==1 && fmt==3) {
				// (0x1f instead of 0x20 because our palette is offset by 1.)
				next_color = x - 0x1f;
				parity = !parity;
			}
			dbuf_write_run(unc_pixels, next_color, count);
			npixels_found += count;
			if(fmt!=3) {
				next_color = (next_color==1)?2:1;
			}
		}
		else if(x==0x1b) {
			break;
		}
		// TODO: Could do more error checking
	}

	dbuf_flush(unc_pixels);
	actual_h = expected_h;
	if(npixels_found != npixels_expected) {
		de_warn(c, "Expected %"I64_FMT" pixels, found %"I64_FMT"%s", npixels_expected,
			npixels_found,
			((npixels_found>npixels_expected && !c->padpix)?" (try -padpix)":""));
		if(npixels_found>npixels_expected && c->padpix) {
			actual_h = de_pad_to_n(npixels_found, w)/w;
		}
	}

	img = de_bitmap_create(c, w, actual_h, 3);
	de_zeromem(pal, sizeof(pal));
	// Images that don't end cleanly are common enough that we go to
	// the trouble of making missing pixels a special color.
	if(fmt==3) {
		pal[0] = DE_MAKE_RGB(255,128,128);
		de_copy_std_palette(DE_PALID_CGA, 3, 0, &pal[1], 4, 0);
	}
	else {
		pal[0] = DE_MAKE_RGB(255,0,255);
		pal[1] = DE_STOCKCOLOR_BLACK;
		pal[2] = DE_STOCKCOLOR_WHITE;
	}
	de_convert_image_paletted(unc_pixels, 0, 8, w, pal, img, 0);
	de_bitmap_write_to_file(img, NULL, 0);

done:
	dbuf_close(unc_pixels);
	de_bitmap_destroy(img);
}

static int de_identify_cserve_rle(deark *c)
{
	u8 has_ext;
	u8 fmt;

	fmt = get_cserve_rle_fmt(c, 1);
	if(fmt==0) return 0;
	has_ext = de_input_file_has_ext(c, "rle");
	if(fmt==3) {
		return has_ext?60:0;
	}
	return has_ext?92:32;
}

void de_module_cserve_rle(deark *c, struct deark_module_info *mi)
{
	mi->id = "cserve_rle";
	mi->desc = "CompuServe RLE";
	mi->run_fn = de_run_cserve_rle;
	mi->identify_fn = de_identify_cserve_rle;
}

// **************************************************************************
// Lotus Manuscript graphics (.bit, .rle)
// **************************************************************************

struct lotus_mscr_ctx {
	i64 w, h;
	i64 rowspan;
	de_finfo *fi;
};

// Warning: This algorithm is based on reverse engineering, and might not
// be quite right.
static void do_lotus_mscr_rle(deark *c, struct lotus_mscr_ctx *d)
{
	i64 npixels_expected;
	i64 npixels_found;
	i64 pos;
	dbuf *unc_pixels = NULL;
	de_bitmap *img = NULL;
	de_color pal[256];

	npixels_expected = d->w*d->h;
	unc_pixels = dbuf_create_membuf(c, npixels_expected, 0x1);
	dbuf_enable_wbuffer(unc_pixels);
	pos = 9;
	npixels_found = 0;

	while(1) {
		i64 count;
		u8 color;
		u8 x;

		if(npixels_found >= npixels_expected) break;
		if(pos >= c->infile->len) break;

		x = de_getbyte_p(&pos);
		color = x>>7;
		count = (i64)(x & 0x7f);
		if(count==0) count = 128;

		dbuf_write_run(unc_pixels, color, count);
		npixels_found += count;
	}

	dbuf_flush(unc_pixels);
	if(npixels_found != npixels_expected) {
		de_warn(c, "Expected %"I64_FMT" pixels, found %"I64_FMT, npixels_expected,
			npixels_found);
	}

	img = de_bitmap_create(c, d->w, d->h, 1);
	de_zeromem(pal, sizeof(pal));
	pal[0] = DE_STOCKCOLOR_WHITE;
	pal[1] = DE_STOCKCOLOR_BLACK;
	de_convert_image_paletted(unc_pixels, 0, 8, d->w, pal, img, 0);
	de_bitmap_write_to_file_finfo(img, d->fi, DE_CREATEFLAG_IS_BWIMG);

	dbuf_close(unc_pixels);
	de_bitmap_destroy(img);
}

static void de_run_lotus_mscr(deark *c, de_module_params *mparams)
{
	i64 pos = 0;
	i64 dens1, dens2;
	u8 x;
	u8 is_rle;
	struct lotus_mscr_ctx *d = NULL;

	d = de_malloc(c, sizeof(struct lotus_mscr_ctx));
	d->fi = de_finfo_create(c);
	x = de_getbyte_p(&pos);
	if(x=='B') {
		is_rle = 0;
	}
	else if(x=='R') {
		is_rle = 1;
	}
	else {
		de_err(c, "Not a Lotus Manuscript file");
		goto done;
	}
	de_dbg(c, "compressed: %u", (UI)is_rle);
	dens1 = de_getu16le_p(&pos);
	dens2 = de_getu16le_p(&pos);
	de_dbg(c, "dpi: %d, %d", (int)dens1, (int)dens2);
	// TODO: Figure out which dpi is which.
	// ART2WP thinks x is first, but that's a little surprising when the y
	// *dimension* is first, so I'm not trusting it.
	if(dens1==dens2 && dens1>=50 && dens1<=1200) {
		d->fi->density.code = DE_DENSITY_DPI;
		d->fi->density.xdens = (double)dens1;
		d->fi->density.ydens = d->fi->density.xdens;
	}
	d->h = de_getu16le_p(&pos);
	d->rowspan = de_getu16le_p(&pos);
	d->w = d->rowspan * 8;
	de_dbg_dimensions(c, d->w, d->h);
	if(!de_good_image_dimensions(c, d->w, d->h)) goto done;
	if(is_rle) {
		do_lotus_mscr_rle(c, d);
	}
	else {
		de_convert_and_write_image_bilevel(c->infile, pos, d->w, d->h, d->rowspan,
			DE_CVTF_WHITEISZERO, d->fi, 0);
	}

done:
	if(d) {
		de_finfo_destroy(c, d->fi);
		de_free(c, d);
	}
}

static int de_identify_lotus_mscr(deark *c)
{
	u8 is_rle, x;
	i64 d1, d2, h, rb;

	x = de_getbyte(0);
	if(x=='B' && de_input_file_has_ext(c, "bit")) {
		is_rle = 0;
	}
	else if(x=='R' && de_input_file_has_ext(c, "rle")) {
		is_rle = 1;
	}
	else {
		return 0;
	}

	d1 = de_getu16le(1);
	d2 = de_getu16le(3);
	if(is_rle) {
		// RLE is particularly hard to detect, because we can't use the file size.
		if(d1!=d2) return 0;
		if(d1!=72 && d1!=300) return 0;
	}
	else {
		if(d1>=600 || d2>600) return 0;
	}

	h = de_getu16le(5);
	rb = de_getu16le(7);
	if(h==0 || rb==0 || h>3300 || rb>320) return 0;
	if(is_rle) {
		return 12;
	}
	else {
		if(9+h*rb == c->infile->len) return 75;
	}

	return 0;
}

void de_module_lotus_mscr(deark *c, struct deark_module_info *mi)
{
	mi->id = "lotus_mscr";
	mi->desc = "Lotus Manuscript graphics";
	mi->run_fn = de_run_lotus_mscr;
	mi->identify_fn = de_identify_lotus_mscr;
}

// **************************************************************************
// Fastgraph SPR
// Minimal support.
// **************************************************************************

struct fastgraph_ctx {
	u8 is_ppr;
	u8 pal_is_set;
	i64 w, h; // 0 if unknown
	i64 img_startpos;
	i64 max_unc_bytes;
	de_color pal[256];
	u8 max_color_used;
};

static void decompress_fastgraph_spr(deark *c, struct fastgraph_ctx *d,
	dbuf *unc_pixels, i64 pos1)
{
	i64 pos = pos1;
	i64 unc_total = 0;

	d->max_color_used = 1;

	while(1) {
		u8 clr;
		i64 count;

		if(pos >= c->infile->len) break;
		if(unc_total >= d->max_unc_bytes) break;

		clr = de_getbyte_p(&pos);
		count = (i64)de_getbyte_p(&pos);
		if(count>0) {
			if(clr > d->max_color_used) {
				d->max_color_used = clr;
			}
			dbuf_write_run(unc_pixels, clr, count);
		}
		unc_total += count;
	}

	dbuf_flush(unc_pixels);
}

// This is a pixel-oriented 4bpp format. We expand it to 8 bits per pixel.
static void decompress_fastgraph_ppr(deark *c, struct fastgraph_ctx *d,
	dbuf *unc_pixels, i64 pos1)
{
	i64 pos = pos1;
	i64 unc_total = 0;

	d->max_color_used = 1;

	while(1) {
		u8 b;
		u8 clr[2];
		i64 count;
		UI k;

		if(pos >= c->infile->len) break;
		if(unc_total >= d->max_unc_bytes) break;

		b = de_getbyte_p(&pos);
		clr[0] = b>>4;
		clr[1] = b & 0x0f;

		for(k=0; k<2; k++) {
			count = (i64)de_getbyte_p(&pos);
			if(count>0) {
				if(clr[k] > d->max_color_used) {
					d->max_color_used = clr[k];
				}
				dbuf_write_run(unc_pixels, clr[k], count);
				unc_total += count;
			}
		}
	}

	dbuf_flush(unc_pixels);
}

static int has_fastgraph_spr_sig(deark *c)
{
	return !dbuf_memcmp(c->infile, 0, (const u8*)"F\0A\0S\0T\0G\0", 10);
}

// Caller sets
//  d->is_ppr
//  optional: ->w, or both ->w and ->h
//  optional: ->pal_is_set and ->pal
//  optional: ->img_startpos
static void do_fastgraph_internal(deark *c, struct fastgraph_ctx *d)
{
	dbuf *unc_pixels = NULL;
	de_bitmap *img = NULL;
	const char *s;

	if(d->w==0) {
		s = de_get_ext_option(c, "width");
		if(s) {
			d->w = de_atoi64(s);
		}
	}
	if(d->h==0) {
		s = de_get_ext_option(c, "height");
		if(s) {
			d->h = de_atoi64(s);
		}
	}

	if(d->w==0) {
		de_err(c, "Image width unknown. Try \"-opt width=...\".");
		goto done;
	}

	if(d->h>0) {
		de_dbg_dimensions(c, d->w, d->h);
	}
	else {
		de_dbg(c, "width: %"I64_FMT, d->w);
	}

	if(d->w<1 || d->w>c->max_image_dimension ||
		d->h<0 || d->h>c->max_image_dimension)
	{
		de_err(c, "Bad image dimensions");
		goto done;
	}

	if(d->h==0) {
		d->max_unc_bytes = DE_MAX_SANE_OBJECT_SIZE;
		unc_pixels = dbuf_create_membuf(c, 0, 0);
	}
	else {
		d->max_unc_bytes = d->w * d->h;
		unc_pixels = dbuf_create_membuf(c, d->max_unc_bytes, 0x1);
	}

	if(d->is_ppr) {
		decompress_fastgraph_ppr(c, d, unc_pixels, d->img_startpos);
	}
	else {
		decompress_fastgraph_spr(c, d, unc_pixels, d->img_startpos);
	}

	de_dbg(c, "decompressed %"I64_FMT" pixels", unc_pixels->len);
	if(d->h==0) {
		d->h = de_pad_to_n(unc_pixels->len, d->w) / d->w;
		de_dbg(c, "height: %"I64_FMT, d->h);
	}

	if(!de_good_image_dimensions(c, d->w, d->h)) goto done;

	img = de_bitmap_create(c, d->w, d->h, d->pal_is_set?3:1);
	if(!d->pal_is_set) {
		de_info(c, "Note: Color map unknown. Rendering as grayscale.");
		de_make_grayscale_palette(d->pal, 1+(i64)d->max_color_used, 0);
	}

	de_convert_image_paletted(unc_pixels, 0, 8, d->w, d->pal, img, 0);
	de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_FLIP_IMAGE |
		DE_CREATEFLAG_OPT_IMAGE);

done:
	dbuf_close(unc_pixels);
	de_bitmap_destroy(img);
}

static void de_run_fastgraph_spr(deark *c, de_module_params *mparams)
{
	struct fastgraph_ctx *d = NULL;

	d = de_malloc(c, sizeof(struct fastgraph_ctx));
	d->is_ppr = 0;

	if(has_fastgraph_spr_sig(c)) {
		d->w = 1 + 256*(i64)de_getbyte(18) + (i64)de_getbyte(16);
		d->h = 1 + 256*(i64)de_getbyte(22) + (i64)de_getbyte(20);
		d->img_startpos = 26;
	}

	do_fastgraph_internal(c, d);

	de_free(c, d);
}

static int de_identify_fastgraph_spr(deark *c)
{
	if(has_fastgraph_spr_sig(c)) {
		return 90;
	}
	return 0;
}

static void help_fastgraph_common(deark *c)
{
	de_msg(c, "-opt width=<n> : Hint: image width");
	de_msg(c, "-opt height=<n> : Hint: image height");
}

static void de_help_fastgraph_spr(deark *c)
{
	help_fastgraph_common(c);
}

void de_module_fastgraph_spr(deark *c, struct deark_module_info *mi)
{
	mi->id = "fastgraph_spr";
	mi->desc = "Fastgraph SPR";
	mi->run_fn = de_run_fastgraph_spr;
	mi->identify_fn = de_identify_fastgraph_spr;
	mi->help_fn = de_help_fastgraph_spr;
	mi->flags |= DE_MODFLAG_HIDDEN;
}

// **************************************************************************
// Fastgraph PPR
// Minimal support.
// **************************************************************************

static void de_run_fastgraph_ppr(deark *c, de_module_params *mparams)
{
	struct fastgraph_ctx *d = NULL;

	d = de_malloc(c, sizeof(struct fastgraph_ctx));
	d->is_ppr = 1;

	do_fastgraph_internal(c, d);

	de_free(c, d);
}

static void de_help_fastgraph_ppr(deark *c)
{
	help_fastgraph_common(c);
}

void de_module_fastgraph_ppr(deark *c, struct deark_module_info *mi)
{
	mi->id = "fastgraph_ppr";
	mi->desc = "Fastgraph PPR";
	mi->run_fn = de_run_fastgraph_ppr;
	mi->identify_fn = NULL;
	mi->help_fn = de_help_fastgraph_ppr;
	mi->flags |= DE_MODFLAG_HIDDEN;
}

// **************************************************************************
// Young Picasso .YP
// **************************************************************************

static int has_yp_sig(deark *c)
{
	return !dbuf_memcmp(c->infile, 0, (const u8*)"P\0\0P\0\0R\0\0", 9);
}

static void de_run_yp(deark *c, de_module_params *mparams)
{
	struct fastgraph_ctx *d = NULL;
	de_bitmap *img = NULL;
	de_finfo *fi = NULL;
	dbuf *unc_pixels = NULL;

	d = de_malloc(c, sizeof(struct fastgraph_ctx));
	// TODO?: Maybe we should call do_fastgraph_internal() instead of
	// just decompress_fastgraph_ppr(), but it would be a bit messy.
	d->is_ppr = 1;

	if(has_yp_sig(c)) {
		d->w = (i64)de_getbyte(9) * 256;
		d->w += (i64)de_getbyte(12);
		d->h = (i64)de_getbyte(15) * 256;
		d->h += (i64)de_getbyte(18);
		d->img_startpos = 24; // (Not actually necessary)
	}
	else {
		d->w = 320;
		d->h = 200;
	}

	de_dbg_dimensions(c, d->w, d->h);
	if(!de_good_image_dimensions(c, d->w, d->h)) goto done;

	d->max_unc_bytes = d->w * d->h;
	unc_pixels = dbuf_create_membuf(c, d->max_unc_bytes, 0x1);
	dbuf_enable_wbuffer(unc_pixels);
	decompress_fastgraph_ppr(c, d, unc_pixels, d->img_startpos);

	fi = de_finfo_create(c);
	fi->density.code = DE_DENSITY_UNK_UNITS;
	fi->density.xdens = 6.0;
	fi->density.ydens = 5.0;

	de_copy_std_palette(DE_PALID_PC16, 0, 0, d->pal, 16, 0);
	img = de_bitmap_create(c, d->w, d->h, 3);
	de_convert_image_paletted(unc_pixels, 0, 8, d->w, d->pal, img, 0);
	de_bitmap_write_to_file_finfo(img, fi, DE_CREATEFLAG_FLIP_IMAGE |
		DE_CREATEFLAG_OPT_IMAGE);

done:
	dbuf_close(unc_pixels);
	de_bitmap_destroy(img);
	de_finfo_destroy(c, fi);
	de_free(c, d);
}

static int de_identify_yp(deark *c)
{
	if(has_yp_sig(c)) {
		if(de_input_file_has_ext(c, "yp")) return 100;
		return 75;
	}
	return 0;
}

void de_module_young_picasso(deark *c, struct deark_module_info *mi)
{
	mi->id = "young_picasso";
	mi->desc = "Young Picasso .YP";
	mi->run_fn = de_run_yp;
	mi->identify_fn = de_identify_yp;
}

// **************************************************************************
// Icon Manager .ICA (Impact Software, Leonard A. Gray)
// **************************************************************************

struct iconmgr_icon_ctx {
	i64 icon_pos;
	i64 total_len;
	de_ucstring *name;
};

struct iconmgr_ica_ctx {
	u8 ver;
	u8 need_errmsg;
	de_encoding input_encoding;
	i64 icon_count;
	i64 reported_file_size;
	de_color pal[16];
};

static void iconmgr_icon_destroy(deark *c, struct iconmgr_icon_ctx *md)
{
	if(!md) return;
	ucstring_destroy(md->name);
	de_free(c, md);
}

// On fatal error, leaves md->total_len set to 0.
static void do_iconmgr_icon(deark *c, struct iconmgr_ica_ctx *d,
	struct iconmgr_icon_ctx *md)
{
	de_bitmap *img = NULL;
	de_bitmap *mask = NULL;
	de_finfo *fi = NULL;
	i64 pos;
	i64 bpp, xpos, ypos;
	int saved_indent_level;

	de_dbg_indent_save(c, &saved_indent_level);
	de_dbg(c, "icon at %"I64_FMT, md->icon_pos);
	de_dbg_indent(c, 1);
	pos = md->icon_pos;

	// Try to detect if we've gone off the rails.
	// TODO: Are bit depths other than 4 possible?
	bpp = de_getbyte_p(&pos);
	xpos = de_getu16le_p(&pos);
	ypos = de_getu16le_p(&pos);
	if(bpp!=4 || xpos>=4096 || ypos>=4096) {
		d->need_errmsg = 1;
		goto done;
	}

	if(d->ver==2) {
		md->name = ucstring_create(c);
		dbuf_read_to_ucstring(c->infile, pos, 26, md->name, DE_CONVFLAG_STOP_AT_NUL,
			d->input_encoding);
		de_dbg(c, "name: \"%s\"", ucstring_getpsz_d(md->name));
		pos += 26;
	}

	de_dbg(c, "bitmap at %"I64_FMT, pos);
	img = de_bitmap_create(c, 32, 32, 4);
	de_convert_image_paletted(c->infile, pos, 4, 16, d->pal, img, 0);
	pos += 512;

	de_dbg(c, "mask at %"I64_FMT, pos);
	mask = de_bitmap_create(c, 32, 32, 1);
	de_convert_image_bilevel(c->infile, pos, 4, mask, 0);
	pos += 128;

	de_bitmap_apply_mask(img, mask, DE_BITMAPFLAG_WHITEISTRNS);

	fi = de_finfo_create(c);
	if(c->filenames_from_file && ucstring_isnonempty(md->name)) {
		de_finfo_set_name_from_ucstring(c, fi, md->name, 0);
	}

	de_bitmap_write_to_file_finfo(img, fi, DE_CREATEFLAG_FLIP_IMAGE|DE_CREATEFLAG_OPT_IMAGE);
	md->total_len = pos - md->icon_pos;

done:
	de_bitmap_destroy(img);
	de_bitmap_destroy(mask);
	de_finfo_destroy(c, fi);
	de_dbg_indent_restore(c, saved_indent_level);
}

static void de_run_iconmgr_ica(deark *c, de_module_params *mparams)
{
	struct iconmgr_ica_ctx *d = NULL;
	struct iconmgr_icon_ctx *md = NULL;
	u8 b;
	i64 pos;

	d = de_malloc(c, sizeof(struct iconmgr_ica_ctx));
	d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_WINDOWS1252);

	b = de_getbyte(2);
	if(b=='M') {
		d->ver = 1;
	}
	else if(b=='2') {
		d->ver = 2;
	}
	else {
		d->need_errmsg = 1;
		goto done;
	}

	d->icon_count = de_getu16le(4);
	de_dbg(c, "count: %d", (int)d->icon_count);
	d->reported_file_size = de_getu32le(4);

	de_copy_std_palette(DE_PALID_WIN16, 0, 0, d->pal, 16, 0);
	pos = 18;
	while(1) {
		if(pos>=c->infile->len || pos>=d->reported_file_size) goto done;
		if(md) {
			iconmgr_icon_destroy(c, md);
			md = NULL;
		}
		md = de_malloc(c, sizeof(struct iconmgr_icon_ctx));
		md->icon_pos = pos;
		do_iconmgr_icon(c, d, md);
		if(md->total_len<=0) goto done;
		pos += md->total_len;
	}

done:
	iconmgr_icon_destroy(c, md);
	if(d) {
		if(d->need_errmsg) {
			de_err(c, "Bad or unsupported file");
		}
		de_free(c, d);
	}
}

static int de_identify_iconmgr_ica(deark *c)
{
	u8 b;

	if(dbuf_memcmp(c->infile, 0, "IC", 2)) return 0;
	b = de_getbyte(2);
	if(de_getbyte(3) != 0) return 0;
	if(b=='M' || b=='2') {
		return 90;
	}
	return 0;
}

void de_module_iconmgr_ica(deark *c, struct deark_module_info *mi)
{
	mi->id = "iconmgr_ica";
	mi->desc = "Icon Manager Archive (.ica)";
	mi->run_fn = de_run_iconmgr_ica;
	mi->identify_fn = de_identify_iconmgr_ica;
}

// **************************************************************************
// ThumbsPlus (v1.x-2.x) thumbnail database (.tud)
// **************************************************************************

#define THUMBSPLUS_W  96
#define THUMBSPLUS_H  72
#define THUMBSPLUS_IMGSIZE (THUMBSPLUS_W*THUMBSPLUS_H)

struct thumbsplus_icon_ctx {
	i64 icon_pos;
	i64 data_len;
	i64 total_len;
	struct de_stringreaderdata *orig_name_srd;
	de_ucstring *name_token;
	struct de_timestamp mod_dt;
};

struct thumbsplus_ctx {
	u8 ver;
	u8 need_errmsg;
	de_encoding input_encoding;
	i64 icon_count;
	i64 reported_file_size;
	de_color pal[256];
};

static void thumbsplus_icon_destroy(deark *c, struct thumbsplus_icon_ctx *md)
{
	if(!md) return;
	de_destroy_stringreaderdata(c, md->orig_name_srd);
	ucstring_destroy(md->name_token);
	de_free(c, md);
}

static void thumbsplus_decompress(deark *c, struct thumbsplus_ctx *d,
	struct thumbsplus_icon_ctx *md, i64 pos1, i64 len, dbuf *unc_pixels)
{
	i64 endpos = pos1 + len;
	i64 pos = pos1;

	while(1) {
		i64 count;
		u8 val;
		u8 b;

		if(pos >= endpos) break;
		b = de_getbyte_p(&pos);
		if(b>=236) {
			if(b==0xff) {
				count = (i64)de_getbyte_p(&pos);
			}
			else {
				count = (i64)b - 233;
			}
			val = de_getbyte_p(&pos);
			dbuf_write_run(unc_pixels, val, count);
		}
		else {
			dbuf_writebyte(unc_pixels, b);
		}
	}
}

// On success, sets md->total_len.
static void do_thumbsplus_icon(deark *c, struct thumbsplus_ctx *d,
	struct thumbsplus_icon_ctx *md)
{
	de_bitmap *img = NULL;
	de_finfo *fi = NULL;
	dbuf *unc_pixels = NULL;
	i64 pos;
	i64 mod_time_raw, mod_date_raw;
	int saved_indent_level;

	de_dbg_indent_save(c, &saved_indent_level);
	de_dbg(c, "icon at %"I64_FMT, md->icon_pos);
	de_dbg_indent(c, 1);
	pos = md->icon_pos;
	pos += 2; // ?

	mod_date_raw = de_getu16le_p(&pos);
	mod_time_raw = de_getu16le_p(&pos);
	de_dos_datetime_to_timestamp(&md->mod_dt, mod_date_raw, mod_time_raw);
	md->mod_dt.tzcode = DE_TZCODE_LOCAL;
	datetime_dbgmsg(c, &md->mod_dt, "mod time");

	md->data_len = de_getu16le_p(&pos);
	de_dbg(c, "cmpr len: %"I64_FMT, md->data_len);
	pos += 12; // ?

	md->orig_name_srd = dbuf_read_string(c->infile, pos, 260, 260,
		DE_CONVFLAG_STOP_AT_NUL, d->input_encoding);
	if(!md->orig_name_srd->found_nul) {
		d->need_errmsg = 1;
		goto done;
	}
	de_dbg(c, "name: \"%s\"", ucstring_getpsz_d(md->orig_name_srd->str));
	pos += md->orig_name_srd->bytes_consumed;

	md->name_token = ucstring_create(c);
	get_base_filename(md->orig_name_srd->str, md->name_token);

	de_dbg(c, "bitmap at %"I64_FMT, pos);
	unc_pixels = dbuf_create_membuf(c, THUMBSPLUS_IMGSIZE+2, 0x1);
	dbuf_enable_wbuffer(unc_pixels);
	thumbsplus_decompress(c, d, md, pos, md->data_len, unc_pixels);
	dbuf_flush(unc_pixels);
	if(unc_pixels->len != THUMBSPLUS_IMGSIZE) {
		// We're strict about this, because it's how we detect if we've gone off
		// the rails.
		de_err(c, "Decompression failed");
		goto done;
	}

	img = de_bitmap_create(c, THUMBSPLUS_W, THUMBSPLUS_H, 3);
	de_convert_image_paletted(unc_pixels, 0, 8, THUMBSPLUS_W, d->pal, img, 0);

	fi = de_finfo_create(c);
	if(c->filenames_from_file && ucstring_isnonempty(md->name_token)) {
		de_finfo_set_name_from_ucstring(c, fi, md->name_token, 0);
	}
	fi->timestamp[DE_TIMESTAMPIDX_MODIFY] = md->mod_dt;
	de_bitmap_write_to_file_finfo(img, fi, DE_CREATEFLAG_FLIP_IMAGE|DE_CREATEFLAG_OPT_IMAGE);

	pos += md->data_len;
	md->total_len = pos - md->icon_pos;

done:
	dbuf_close(unc_pixels);
	de_bitmap_destroy(img);
	de_finfo_destroy(c, fi);
	de_dbg_indent_restore(c, saved_indent_level);
}

static void make_thumbsplus_pal(deark *c, struct thumbsplus_ctx *d)
{
	static const u8 tppal[236*3] = {
		0x00,0x00,0x00,0xff,0xff,0xff,0x09,0x09,0x09,0x13,0x13,0x13,0x1d,0x1d,0x1d,0x27,0x27,0x27,
		0x31,0x31,0x31,0x3b,0x3b,0x3b,0x44,0x44,0x44,0x4e,0x4e,0x4e,0x58,0x58,0x58,0x62,0x62,0x62,
		0x6c,0x6c,0x6c,0x76,0x76,0x76,0x89,0x89,0x89,0x93,0x93,0x93,0x9d,0x9d,0x9d,0xa7,0xa7,0xa7,
		0xb0,0xb0,0xb0,0xba,0xba,0xba,0xc4,0xc4,0xc4,0xce,0xce,0xce,0xd7,0xd7,0xd7,0xe1,0xe1,0xe1,
		0xeb,0xeb,0xeb,0xf5,0xf5,0xf5,0x03,0x13,0x03,0x06,0x27,0x06,0x09,0x3a,0x09,0x0c,0x4e,0x0c,
		0x0f,0x62,0x0f,0x12,0x75,0x12,0x15,0x89,0x15,0x18,0x9c,0x18,0x1b,0xb0,0x1b,0x1e,0xc4,0x1e,
		0x21,0xd7,0x21,0x24,0xeb,0x24,0x38,0xff,0x38,0x49,0xff,0x49,0x59,0xff,0x59,0x6a,0xff,0x6a,
		0x7a,0xff,0x7a,0x8b,0xff,0x8b,0x9b,0xff,0x9b,0xac,0xff,0xac,0xbc,0xff,0xbc,0xcd,0xff,0xcd,
		0xdd,0xff,0xdd,0xee,0xff,0xee,0x03,0x03,0x13,0x06,0x06,0x27,0x09,0x09,0x3a,0x0c,0x0c,0x4e,
		0x0f,0x0f,0x62,0x12,0x12,0x75,0x15,0x15,0x89,0x18,0x18,0x9c,0x1b,0x1b,0xb0,0x1e,0x1e,0xc4,
		0x21,0x21,0xd7,0x24,0x24,0xeb,0x38,0x38,0xff,0x49,0x49,0xff,0x59,0x59,0xff,0x6a,0x6a,0xff,
		0x7a,0x7a,0xff,0x8b,0x8b,0xff,0x9b,0x9b,0xff,0xac,0xac,0xff,0xbc,0xbc,0xff,0xcd,0xcd,0xff,
		0xdd,0xdd,0xff,0xee,0xee,0xff,0x13,0x13,0x03,0x27,0x27,0x06,0x3a,0x3a,0x09,0x4e,0x4e,0x0c,
		0x62,0x62,0x0f,0x75,0x75,0x12,0x89,0x89,0x15,0x9c,0x9c,0x18,0xb0,0xb0,0x1b,0xc4,0xc4,0x1e,
		0xd7,0xd7,0x21,0xeb,0xeb,0x24,0xff,0xff,0x38,0xff,0xff,0x49,0xff,0xff,0x59,0xff,0xff,0x6a,
		0xff,0xff,0x7a,0xff,0xff,0x8b,0xff,0xff,0x9b,0xff,0xff,0xac,0xff,0xff,0xbc,0xff,0xff,0xcd,
		0xff,0xff,0xdd,0xff,0xff,0xee,0x0d,0x03,0x13,0x1b,0x06,0x27,0x29,0x09,0x3a,0x37,0x0c,0x4e,
		0x45,0x0f,0x62,0x53,0x12,0x75,0x60,0x15,0x89,0x6e,0x18,0x9c,0x7c,0x1b,0xb0,0x8a,0x1e,0xc4,
		0x98,0x21,0xd7,0xa6,0x24,0xeb,0xb9,0x38,0xff,0xbf,0x49,0xff,0xc5,0x59,0xff,0xcb,0x6a,0xff,
		0xd0,0x7a,0xff,0xd6,0x8b,0xff,0xdc,0x9b,0xff,0xe2,0xac,0xff,0xe7,0xbc,0xff,0xed,0xcd,0xff,
		0xf3,0xdd,0xff,0xf9,0xee,0xff,0x03,0x13,0x13,0x06,0x27,0x27,0x09,0x3a,0x3a,0x0c,0x4e,0x4e,
		0x0f,0x62,0x62,0x12,0x75,0x75,0x15,0x89,0x89,0x18,0x9c,0x9c,0x1b,0xb0,0xb0,0x1e,0xc4,0xc4,
		0x21,0xd7,0xd7,0x24,0xeb,0xeb,0x38,0xff,0xff,0x49,0xff,0xff,0x59,0xff,0xff,0x6a,0xff,0xff,
		0x7a,0xff,0xff,0x8b,0xff,0xff,0x9b,0xff,0xff,0xac,0xff,0xff,0xbc,0xff,0xff,0xcd,0xff,0xff,
		0xdd,0xff,0xff,0xee,0xff,0xff,0x12,0x0a,0x03,0x24,0x15,0x06,0x37,0x20,0x09,0x49,0x2b,0x0c,
		0x5c,0x35,0x0f,0x6e,0x40,0x12,0x81,0x4b,0x15,0x93,0x56,0x18,0xa6,0x60,0x1b,0xb8,0x6b,0x1e,
		0xcb,0x76,0x21,0xdd,0x81,0x24,0xf1,0x94,0x38,0xf2,0x9d,0x49,0xf3,0xa6,0x59,0xf4,0xaf,0x6a,
		0xf5,0xb8,0x7a,0xf6,0xc1,0x8b,0xf8,0xc9,0x9b,0xf9,0xd2,0xac,0xfa,0xdb,0xbc,0xfb,0xe4,0xcd,
		0xfc,0xed,0xdd,0xfd,0xf6,0xee,0x13,0x03,0x03,0x27,0x06,0x06,0x3a,0x09,0x09,0x4e,0x0c,0x0c,
		0x62,0x0f,0x0f,0x75,0x12,0x12,0x89,0x15,0x15,0x9c,0x18,0x18,0xb0,0x1b,0x1b,0xc4,0x1e,0x1e,
		0xd7,0x21,0x21,0xeb,0x24,0x24,0xff,0x38,0x38,0xff,0x49,0x49,0xff,0x59,0x59,0xff,0x6a,0x6a,
		0xff,0x7a,0x7a,0xff,0x8b,0x8b,0xff,0x9b,0x9b,0xff,0xac,0xac,0xff,0xbc,0xbc,0xff,0xcd,0xcd,
		0xff,0xdd,0xdd,0xff,0xee,0xee,0x12,0x0c,0x08,0x25,0x18,0x10,0x38,0x24,0x19,0x4a,0x30,0x21,
		0x5d,0x3c,0x29,0x70,0x48,0x32,0x82,0x54,0x3a,0x95,0x60,0x42,0xa8,0x6c,0x4b,0xba,0x78,0x53,
		0xcd,0x84,0x5b,0xe2,0x98,0x6f,0xe4,0xa1,0x7b,0xe7,0xa9,0x87,0xe9,0xb2,0x93,0xeb,0xba,0x9f,
		0xee,0xc3,0xab,0xf0,0xcb,0xb7,0xf3,0xd4,0xc3,0xf5,0xdc,0xcf,0xf7,0xe5,0xdb,0xfa,0xed,0xe7,
		0xfc,0xf6,0xf3,0x12,0x14,0x18,0x24,0x28,0x30,0x36,0x3c,0x48,0x48,0x50,0x60,0x75,0x7d,0x8e,
		0x91,0x97,0xa5,0xac,0xb1,0xbb,0xc8,0xcb,0xd2,0xe3,0xe5,0xe8,0x10,0x14,0x0f,0x21,0x28,0x1e,
		0x32,0x3c,0x2d,0x42,0x50,0x3c,0x53,0x64,0x4b,0x7d,0x8e,0x75,0x97,0xa5,0x91,0xb1,0xbb,0xac,
		0xcb,0xd2,0xc8,0xe5,0xe8,0xe3 };

	de_copy_palette_from_rgb24(tppal, d->pal, 236);
}

static void de_run_thumbsplus(deark *c, de_module_params *mparams)
{
	struct thumbsplus_ctx *d = NULL;
	struct thumbsplus_icon_ctx *md = NULL;
	i64 pos;

	d = de_malloc(c, sizeof(struct thumbsplus_ctx));
	d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_WINDOWS1252);

	pos = 4;
	d->icon_count = de_getu32le_p(&pos);
	de_dbg(c, "count: %"I64_FMT, d->icon_count);
	d->reported_file_size = de_getu32le_p(&pos);

	make_thumbsplus_pal(c, d);

	pos = 16;
	while(1) {
		if(pos>=c->infile->len || pos>=d->reported_file_size) goto done;
		if(md) {
			thumbsplus_icon_destroy(c, md);
			md = NULL;
		}
		md = de_malloc(c, sizeof(struct thumbsplus_icon_ctx));
		md->icon_pos = pos;
		do_thumbsplus_icon(c, d, md);
		if(md->total_len<=0) goto done;
		pos += md->total_len;
	}

done:
	thumbsplus_icon_destroy(c, md);
	if(d) {
		if(d->need_errmsg) {
			de_err(c, "Bad or unsupported file");
		}
		de_free(c, d);
	}
}

static int de_identify_thumbsplus(deark *c)
{
	if((UI)de_getu32be(0) == 0xbebadabeU) {
		return 95;
	}
	return 0;
}

void de_module_thumbsplus(deark *c, struct deark_module_info *mi)
{
	mi->id = "thumbsplus";
	mi->desc = "ThumbsPlus v1-2 database (.tud)";
	mi->run_fn = de_run_thumbsplus;
	mi->identify_fn = de_identify_thumbsplus;
}

// **************************************************************************
// FM Towns icon
// **************************************************************************

struct fmtownsicn_table_ctx {
	i64 idx;
	i64 num_icons;
	i64 icon_arr_offs;
};

struct fmtownsicn_ctx {
	u8 fatalerrflag;
	u8 need_errmsg;
	int is_le;
	i64 num_tables;
	de_color pal16[16];
	de_color pal2[2];
};

static void do_fmtownsicn_icon(deark *c, struct fmtownsicn_ctx *d,
	struct fmtownsicn_table_ctx *tbl, i64 idx, i64 pos1)
{
	int saved_indent_level;
	i64 pos = pos1;
	de_bitmap *img = NULL;
	UI createflags = 0;
	i64 w, h;
	i64 rowspan;
	i64 isize;
	i64 fg_pos;
	UI ncolors;
	UI bits_per_pixel;

	de_dbg_indent_save(c, &saved_indent_level);
	if(pos1>=c->infile->len) {
		d->fatalerrflag = 1;
		d->need_errmsg = 1;
		goto done;
	}
	de_dbg(c, "icon header (table #%u icon #%u) at %"I64_FMT, (UI)tbl->idx,
		(UI)idx, pos1);
	de_dbg_indent(c, 1);
	pos += 2;
	pos += 4;
	w = de_getu16le_p(&pos);
	h = de_getu16le_p(&pos);
	de_dbg_dimensions(c, w, h);
	ncolors = (UI)de_getu32le_p(&pos);
	de_dbg(c, "num colors: %u", ncolors);
	isize = de_getu16le_p(&pos);
	de_dbg(c, "size: %"I64_FMT, isize);
	pos += 4;
	fg_pos = de_getu32le_p(&pos);
	de_dbg(c, "fg pos: %"I64_FMT, fg_pos);

	if(fg_pos <= pos1 || fg_pos + isize > c->infile->len) {
		d->fatalerrflag = 1;
		d->need_errmsg = 1;
		goto done;
	}

	if(ncolors==2) {
		bits_per_pixel = 1;
	}
	else if(ncolors==16) {
		bits_per_pixel = 4;
	}
	else {
		de_err(c, "Unsupported image type");
		goto done;
	}

	if(!de_good_image_dimensions(c, w, h)) goto done;
	img = de_bitmap_create(c, w, h, 3);


	if(bits_per_pixel==1) {
		rowspan = de_pad_to_n(w*bits_per_pixel, 8) / 8;
		de_convert_image_paletted(c->infile, fg_pos, 1, rowspan,
			d->pal2, img, 0);
		createflags |= DE_CREATEFLAG_IS_BWIMG;
	}
	else { // 4
		rowspan = de_pad_to_n(w*bits_per_pixel, 32) / 8;
		de_convert_image_paletted(c->infile, fg_pos, 4, rowspan,
			d->pal16, img, 0x01);
		createflags |= DE_CREATEFLAG_OPT_IMAGE;
	}

	de_bitmap_write_to_file(img, NULL, createflags);

done:
	de_bitmap_destroy(img);
	de_dbg_indent_restore(c, saved_indent_level);
}

static void do_fmtownsicn_table(deark *c, struct fmtownsicn_ctx *d, i64 idx,
	i64 pos1)
{
	int saved_indent_level;
	i64 pos = pos1;
	struct fmtownsicn_table_ctx *tbl = NULL;
	i64 i;

	de_dbg_indent_save(c, &saved_indent_level);

	if(pos1+32>c->infile->len) {
		d->fatalerrflag = 1;
		d->need_errmsg = 1;
		goto done;
	}

	tbl = de_malloc(c, sizeof(struct fmtownsicn_table_ctx));
	tbl->idx = idx;

	de_dbg(c, "table #%"I64_FMT" at %"I64_FMT, idx, pos1);
	de_dbg_indent(c, 1);
	pos += 2; // id
	tbl->num_icons = de_getu16le_p(&pos);
	de_dbg(c, "num icons: %u", (UI)tbl->num_icons);
	pos += 2;
	pos += 2;

	tbl->icon_arr_offs = de_getu32le_p(&pos);
	de_dbg(c, "offset of icon array: %"I64_FMT, tbl->icon_arr_offs);
	if(tbl->icon_arr_offs <= pos1) {
		d->fatalerrflag = 1;
		d->need_errmsg = 1;
		goto done;
	}

	for(i=0; i<tbl->num_icons; i++) {
		if(d->fatalerrflag) goto done;
		do_fmtownsicn_icon(c, d, tbl, i, tbl->icon_arr_offs+48*i);
	}

done:
	de_free(c, tbl);
	de_dbg_indent_restore(c, saved_indent_level);
}

static void fmtownsicn_make_palettes(deark *c, struct fmtownsicn_ctx *d)
{
	static const u8 pal16_std[16*3] = {
		0x22,0x22,0x22, 0x22,0x77,0xbb, 0xcc,0x66,0x44, 0xff,0xcc,0xaa,
		0x99,0x99,0x99, 0x00,0xcc,0x77, 0xcc,0xcc,0xcc, 0x77,0x77,0x77,
		0x00,0x00,0x00, 0x88,0xbb,0xee, 0xdd,0x00,0x00, 0x00,0x00,0xaa,
		0x55,0x55,0x55, 0x00,0xff,0xff, 0xff,0xdd,0x00, 0xff,0xff,0xff
	};

	de_make_grayscale_palette(d->pal2, 2, 0x1);
	de_copy_palette_from_rgb24(pal16_std, d->pal16, 16);
}

static void do_fmtownsicn_ICNFILE(deark *c, struct fmtownsicn_ctx *d)
{
	i64 i;
	i64 pos1 = 0;
	i64 pos = pos1;

	de_dbg(c, "header at %"I64_FMT, pos1);
	de_dbg_indent(c, 1);
	pos += 10;
	pos += 2; // version
	d->num_tables = de_getu16le_p(&pos);
	de_dbg(c, "num tables: %u", (UI)d->num_tables);
	de_dbg_indent(c, -1);

	for(i=0; i<d->num_tables; i++) {
		if(d->fatalerrflag) goto done;
		do_fmtownsicn_table(c, d, i, 32+32*i);
	}

done:
	;
}

static void do_fmtownsicn_FJ(deark *c, struct fmtownsicn_ctx *d)
{
	i64 num_icons;
	i64 num_icons_capacity;
	i64 w, h;
	i64 rowspan;
	i64 icon_size;
	UI bits_per_pixel;
	i64 i;
	i64 pos1 = 0;
	i64 pos = pos1;

	de_dbg(c, "header at %"I64_FMT, pos1);
	de_dbg_indent(c, 1);
	pos += 8;

	bits_per_pixel = (UI)dbuf_getu16x(c->infile, pos, d->is_le);
	pos += 2;
	de_dbg(c, "bits per pixel: %u", bits_per_pixel);

	w = dbuf_getu16x(c->infile, pos, d->is_le);
	pos += 2;
	h = dbuf_getu16x(c->infile, pos, d->is_le);
	pos += 2;
	de_dbg_dimensions(c, w, h);

	num_icons = dbuf_getu16x(c->infile, pos, d->is_le);
	//pos += 2;
	de_dbg(c, "num icons: %"I64_FMT, num_icons);

	de_dbg_indent(c, -1);

	if(w!=32 || h!=32 || bits_per_pixel!=4) {
		d->need_errmsg = 1;
		goto done;
	}
	rowspan = 16;
	icon_size = 2+rowspan*h;

	if(c->infile->len<16 ||
		((c->infile->len-16) % icon_size)!=0)
	{
		d->need_errmsg = 1;
		goto done;
	}

	num_icons_capacity = (c->infile->len-16) / icon_size;
	if(num_icons_capacity < num_icons) {
		de_warn(c, "Expected %"I64_FMT" icons, only found %"I64_FMT,
			num_icons, num_icons_capacity);
		num_icons = num_icons_capacity;
	}

	for(i=0; i<num_icons; i++) {
		de_bitmap *img;
		UI id;

		pos = 16+i*icon_size;
		de_dbg(c, "icon at %"I64_FMT, pos);
		de_dbg_indent(c, 1);
		id = (UI)dbuf_getu16x(c->infile, pos, d->is_le);
		de_dbg(c, "id: 0x%04x", id);

		img = de_bitmap_create(c, w, h, 3);
		de_convert_image_paletted(c->infile, pos+2, 4, rowspan,
			d->pal16, img, 0x01);
		de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_OPT_IMAGE);
		de_bitmap_destroy(img);
		de_dbg_indent(c, -1);
	}
done:
	;
}

static void de_run_fmtowns_icn(deark *c, de_module_params *mparams)
{
	struct fmtownsicn_ctx *d = NULL;
	u8 b;

	d = de_malloc(c, sizeof(struct fmtownsicn_ctx));
	fmtownsicn_make_palettes(c, d);

	b = de_getbyte(6);
	if(b=='E') {
		do_fmtownsicn_ICNFILE(c, d);
	}
	else if(b=='2') {
		d->is_le = 1;
		do_fmtownsicn_FJ(c, d);
	}
	else if(b=='J') {
		d->is_le = 0;
		do_fmtownsicn_FJ(c, d);
	}
	else {
		d->need_errmsg = 1;
		goto done;
	}

done:
	if(d) {
		if(d->need_errmsg) {
			de_err(c, "Bad or unsupported file");
		}
		de_free(c, d);
	}
}

static int de_identify_fmtowns_icn(deark *c)
{
	u8 buf[10];

	de_read(buf, 0, sizeof(buf));
	if(!de_memcmp(buf, (const void*)"ICNFILE\0\0\x1a", 10)) {
		return 100;
	}
	if(!de_memcmp(buf, (const void*)"CRI-FJ2 ", 8)) {
		return 100;
	}
	if(!de_memcmp(buf, (const void*)"CRI-FUJI", 8)) {
		return 100;
	}
	return 0;
}

void de_module_fmtowns_icn(deark *c, struct deark_module_info *mi)
{
	mi->id = "fmtowns_icn";
	mi->desc = "FM Towns icons";
	mi->run_fn = de_run_fmtowns_icn;
	mi->identify_fn = de_identify_fmtowns_icn;
}

// **************************************************************************
// .HEL
// An animation format used on FM Towns OS.
// **************************************************************************

struct hel_ctx {
	u8 includedups;
	u8 change_flag;
	i64 w, h;
	de_bitmap *canvas;
	de_bitmap *frame_data;
};

static void hel_apply_frame(deark *c, struct hel_ctx *d)
{
	i64 i, j;
	de_color px1, px2;

	for(j=0; j<d->h; j++) {
		for(i=0; i<d->w; i++) {
			px2 = de_bitmap_getpixel(d->frame_data, i, j);
			// If pixel is "white", negate the canvas pixel.
			if(DE_COLOR_K(px2)!=0) {
				de_colorsample s;

				px1 = de_bitmap_getpixel(d->canvas, i, j);
				s = (DE_COLOR_K(px1)==0) ? 0xff : 0;
				de_bitmap_setpixel_gray(d->canvas, i, j, s);
				d->change_flag = 1;
			}
		}
	}
}

static void de_run_fmtowns_hel(deark *c, de_module_params *mparams)
{
	struct hel_ctx *d = NULL;
	i64 rowspan;
	i64 framesize;
	i64 nframes;
	i64 frame_idx;

	d = de_malloc(c, sizeof(struct hel_ctx));
	d->includedups = (u8)de_get_ext_option_bool(c, "fmtowns_hel:includedups", 0);
	d->w = 160;
	d->h = 120;
	rowspan = d->w/8;
	framesize = rowspan*d->h;
	d->canvas = de_bitmap_create(c, d->w, d->h, 1);
	d->frame_data = de_bitmap_create(c, d->w, d->h, 1);

	nframes = de_getu32le(8) + 1;
	de_dbg(c, "num frames: %"I64_FMT, nframes);
	for(frame_idx=0; frame_idx<nframes; frame_idx++) {
		i64 framestart = 12 + framesize*frame_idx;

		if(framestart+framesize > c->infile->len) goto done;
		de_dbg(c, "frame %"I64_FMT" at %"I64_FMT, frame_idx, framestart);
		de_dbg_indent(c, 1);
		de_convert_image_bilevel(c->infile, framestart, rowspan, d->frame_data, 0);
		d->change_flag = 0;
		hel_apply_frame(c, d);
		if(d->change_flag==0 && frame_idx!=0 && !d->includedups) {
			de_dbg(c, "[suppressing duplicate frame]");
		}
		else {
			de_bitmap_write_to_file(d->canvas, NULL, DE_CREATEFLAG_IS_BWIMG);
		}
		de_dbg_indent(c, -1);
	}

done:
	if(d) {
		de_bitmap_destroy(d->frame_data);
		de_bitmap_destroy(d->canvas);
		de_free(c, d);
	}
}

static int de_identify_fmtowns_hel(deark *c)
{
	if(dbuf_memcmp(c->infile, 0, (const void*)"he1\0\x01\0\0\0", 8)) {
		return 0;
	}
	return 100;
}

static void de_help_fmtowns_hel(deark *c)
{
	de_msg(c, "-opt fmtowns_hel:includedups : Do not suppress duplicate frames");
}

void de_module_fmtowns_hel(deark *c, struct deark_module_info *mi)
{
	mi->id = "fmtowns_hel";
	mi->desc = "FM Towns animation (.hel)";
	mi->run_fn = de_run_fmtowns_hel;
	mi->identify_fn = de_identify_fmtowns_hel;
	mi->help_fn = de_help_fmtowns_hel;
}

// **************************************************************************
// PixFolio catalog (Allen C. Kempe) (Win3.x version)
// **************************************************************************

struct pixfolio_ctx {
	de_encoding input_encoding;
	u8 opt_undelete;
	u8 fatalerrflag;
	u8 need_errmsg;
	UI deleted_count;
	de_ucstring *tmpstr;
};

struct pixfolio_item_ctx {
	i64 pos;
	i64 total_len;
	struct de_stringreaderdata *basefn_srd;
	u8 blank_basefn;
	struct de_bmpinfo bi;
};

static void pixfolio_item_destroy(deark *c, struct pixfolio_item_ctx *md)
{
	if(!md) return;
	de_destroy_stringreaderdata(c, md->basefn_srd);
	de_free(c, md);
}

static void read_pixfolio_timestamp(deark *c, struct pixfolio_ctx *d,
	struct pixfolio_item_ctx *md)
{
	de_ucstring *dtstr = NULL;
	de_ucstring *tmstr = NULL;

	dtstr = ucstring_create(c);
	tmstr = ucstring_create(c);

	dbuf_read_to_ucstring(c->infile, md->pos+343, 10, dtstr, DE_CONVFLAG_STOP_AT_NUL,
		DE_ENCODING_LATIN1);
	dbuf_read_to_ucstring(c->infile, md->pos+354, 8, tmstr, DE_CONVFLAG_STOP_AT_NUL,
		DE_ENCODING_LATIN1);
	de_dbg(c, "timestamp: \"%s %s\"", ucstring_getpsz_d(dtstr),
		ucstring_getpsz_d(tmstr));

	// We don't do anything with the timestamp, because:
	// * There are at least 3 different date formats ("MM/DD/YYYY", "DD/MM/YYYY",
	// "MM-DD-YYYY") and seemingly no way to tell which was used.
	// * The time of day seems to be a 12-hour clock with no AM/PM flag.

	ucstring_destroy(tmstr);
	ucstring_destroy(dtstr);
}

static void pixfolio_check_blank_name(deark *c, struct pixfolio_ctx *d,
	struct pixfolio_item_ctx *md)
{
	size_t k;

	if(!md->basefn_srd) return;
	if(md->basefn_srd->sz_strlen!=12) return;

	for(k=0; k<12; k++) {
		if(md->basefn_srd->sz[k]!=0x20) return;
	}
	md->blank_basefn = 1;
}

// If successfully parsed, sets md->total_len
static void do_pixfolio_item(deark *c, struct pixfolio_ctx *d,
	struct pixfolio_item_ctx *md)
{
	i64 pos;
	i64 dibpos;
	i64 dib_len1, dib_len2, dib_len;
	i64 descr_len;
	i64 xtra_len;
	i64 amt_to_copy1, amt_to_copy2;
	dbuf *outf = NULL;
	de_finfo *fi = NULL;
	int ret;
	int errflag = 0;
	int saved_indent_level;

	de_dbg_indent_save(c, &saved_indent_level);

	de_dbg(c, "item at %"I64_FMT, md->pos);
	de_dbg_indent(c, 1);

	fi = de_finfo_create(c);
	md->basefn_srd = dbuf_read_string(c->infile, md->pos, 12, 12, DE_CONVFLAG_STOP_AT_NUL,
		d->input_encoding);
	de_dbg(c, "name: \"%s\"", ucstring_getpsz_d(md->basefn_srd->str));
	pixfolio_check_blank_name(c, d, md);

	ucstring_empty(d->tmpstr);
	dbuf_read_to_ucstring(c->infile, md->pos+25, 64, d->tmpstr, DE_CONVFLAG_STOP_AT_NUL,
		d->input_encoding);
	de_dbg(c, "path: \"%s\"", ucstring_getpsz_d(d->tmpstr));

	read_pixfolio_timestamp(c, d, md);

	pos = md->pos + 363;
	// TODO: The "dib_len" fields need more research. They are usually equal.
	// Sometimes, one of them is 0, so we use the other one.
	dib_len1 = de_getu32le_p(&pos); // Is this 32 or 16 bit?
	de_dbg(c, "dib len1: %"I64_FMT, dib_len1);
	dib_len2 = de_getu16le_p(&pos);
	de_dbg(c, "dib len2: %"I64_FMT, dib_len2);
	if(dib_len1 && dib_len2 && (dib_len1 != dib_len2)) {
		d->fatalerrflag = 1;
		d->need_errmsg = 1;
		goto done;
	}
	dib_len = dib_len2 ? dib_len2 : dib_len1;

	descr_len = de_getu16le_p(&pos);
	de_dbg(c, "descr len: %"I64_FMT, descr_len);
	xtra_len = de_getu16le_p(&pos); // TODO: What is this segment
	de_dbg(c, "xtra len: %"I64_FMT, xtra_len);

	md->total_len = 373 + descr_len + 1 + dib_len + xtra_len;
	pos += descr_len + 1;
	dibpos = pos;
	de_dbg(c, "dib pos: %"I64_FMT, dibpos);
	if(dibpos+dib_len > c->infile->len) {
		errflag = 1;
		goto done;
	}

	if(dib_len==0) {
		de_dbg(c, "[no image]");
		goto done;
	}

	if(md->blank_basefn) {
		de_dbg(c, "[deleted]");
		d->deleted_count++;
		if(!d->opt_undelete) goto done;
	}

	// Expecting a 40-byte BITMAPINFOHEADER here. If it's not, assume
	// we've gone off the rails.
	if((UI)de_getu32le(dibpos) != 40) {
		d->fatalerrflag = 1;
		d->need_errmsg = 1;
		goto done;
	}

	ret = fmtutil_get_bmpinfo(c, c->infile, &md->bi, dibpos,
		c->infile->len - dibpos, 0);
	if(!ret) {
		errflag = 1;
		goto done;
	}

	amt_to_copy1 = dib_len;
	amt_to_copy2 = md->bi.size_of_headers_and_pal + md->bi.sizeImage_field;
	amt_to_copy1 = de_min_int(amt_to_copy1, amt_to_copy2);

	if(md->blank_basefn) {
		de_finfo_set_name_from_sz(c, fi, "deleted", 0, DE_ENCODING_LATIN1);
	}
	else if(c->filenames_from_file) {
		de_finfo_set_name_from_ucstring(c, fi, md->basefn_srd->str, 0);
	}

	outf = dbuf_create_output_file(c, "bmp", fi, 0);
	fmtutil_generate_bmpfileheader(c, outf, &md->bi, 14+amt_to_copy1);
	dbuf_copy(c->infile, dibpos, amt_to_copy1, outf);

done:
	if(md->total_len<=0) {
		d->fatalerrflag = 1;
	}
	if(errflag) {
		de_err(c, "Failed to decode item at %"I64_FMT, md->pos);
	}
	dbuf_close(outf);
	de_finfo_destroy(c, fi);
	de_dbg_indent_restore(c, saved_indent_level);
}

static void de_run_pixfolio(deark *c, de_module_params *mparams)
{
	struct pixfolio_ctx *d = NULL;
	struct pixfolio_item_ctx *md = NULL;
	i64 pos;

	d = de_malloc(c, sizeof(struct pixfolio_ctx));
	d->input_encoding = de_get_input_encoding(c, NULL, DE_ENCODING_WINDOWS1252);
	d->opt_undelete = (u8)de_get_ext_option_bool(c, "pixfolio:undelete", 0);
	d->tmpstr = ucstring_create(c);

	pos = 0;
	while(1) {
		if(d->fatalerrflag) goto done;
		if(pos+373 > c->infile->len) goto done;
		if(md) {
			pixfolio_item_destroy(c, md);
			md = NULL;
		}
		md = de_malloc(c, sizeof(struct pixfolio_item_ctx));
		md->pos = pos;
		do_pixfolio_item(c, d, md);
		if(md->total_len<=0) goto done;
		pos += md->total_len;
	}

done:
	pixfolio_item_destroy(c, md);
	if(d) {
		if(d->deleted_count!=0 && !d->opt_undelete) {
			de_info(c, "Note: Deleted items found. Use \"-opt pixfolio:undelete\" "
				"to extract them.");
		}
		if(d->need_errmsg) {
			de_err(c, "Bad or unsupported file");
		}
		ucstring_destroy(d->tmpstr);
		de_free(c, d);
	}
}

static int de_identify_pixfolio(deark *c)
{
	u8 buf[20];
	i64 n1, n2;

	// This probably doesn't identify *every* PixFolio CAT file.
	// It assumes the first item contains an image, which is quite possibly not
	// always true.
	if(de_getbyte(0)<0x20) return 0;
	if(!de_input_file_has_ext(c, "cat")) return 0;
	de_read(buf, 343, 20);
	if(buf[2]!='/' && buf[2]!='-') return 0;
	if(buf[13]!=':') return 0;
	n1 = de_getu16le(369); // Descr len
	n2 = de_getu32le(373+n1+1); // DIB header size
	if(n2 != 40) return 0;
	n2 = de_getu16le(373+n1+1+12); // #planes
	if(n2 != 1) return 0;
	return 80;
}

static void de_help_pixfolio(deark *c)
{
	de_msg(c, "-opt pixfolio:undelete : Also extract deleted images");
}

void de_module_pixfolio(deark *c, struct deark_module_info *mi)
{
	mi->id = "pixfolio";
	mi->desc = "PixFolio catalog";
	mi->run_fn = de_run_pixfolio;
	mi->identify_fn = de_identify_pixfolio;
	mi->help_fn = de_help_pixfolio;
}

// **************************************************************************
// Apple II icons archive
// **************************************************************************

struct a2icons_ctx {
	de_color pal[16];
	de_color maskpal[16];

	i64 group_startpos;
	i64 group_len;
	i64 group_endpos;

	i64 last_icon_len;
};

static void do_a2i_one_icon(deark *c, struct a2icons_ctx *d, i64 pos1)
{
	de_bitmap *img =  NULL;
	de_bitmap *mask =  NULL;
	int saved_indent_level;
	UI icon_type;
	i64 w, h;
	i64 rowspan;

	de_dbg_indent_save(c, &saved_indent_level);
	de_dbg(c, "icon at %"I64_FMT, pos1);
	de_dbg_indent(c, 1);
	icon_type = (UI)de_getu16le(pos1);
	de_dbg(c, "icon type: 0x%04x", icon_type);
	d->last_icon_len = de_getu16le(pos1+2);
	de_dbg(c, "icon size: %"I64_FMT, d->last_icon_len);
	h = de_getu16le(pos1+4);
	w = de_getu16le(pos1+6);
	de_dbg_dimensions(c, w, h);
	if(!de_good_image_dimensions(c, w, h)) goto done;
	rowspan = de_pad_to_n(w*4, 8)/8;

	img = de_bitmap_create(c, w, h, 4);
	de_convert_image_paletted(c->infile, pos1+8, 4, rowspan, d->pal, img, 0);

	mask = de_bitmap_create(c, w, h, 1);
	de_convert_image_paletted(c->infile, pos1+8+d->last_icon_len, 4,
		rowspan, d->maskpal, mask, 0);

	de_bitmap_apply_mask(img, mask, 0);
	de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_OPT_IMAGE);

done:
	de_bitmap_destroy(img);
	de_bitmap_destroy(mask);
	de_dbg_indent_restore(c, saved_indent_level);
}

#define A2I_GROUP_HDR_SIZE 86

static void do_a2i_icon_group(deark *c, struct a2icons_ctx *d)
{
	int saved_indent_level;
	i64 curpos;

	de_dbg_indent_save(c, &saved_indent_level);
	de_dbg(c, "icon group at %"I64_FMT, d->group_startpos);
	de_dbg_indent(c, 1);
	de_dbg(c, "len: %"I64_FMT, d->group_len);

	curpos = d->group_startpos+A2I_GROUP_HDR_SIZE;
	while(1) {
		if(curpos >= d->group_endpos) goto done;
		d->last_icon_len = 0;
		do_a2i_one_icon(c, d, curpos);
		if(d->last_icon_len==0) goto done;
		curpos = curpos + 8 + d->last_icon_len*2;
	}

done:
	de_dbg_indent_restore(c, saved_indent_level);
}

static void de_run_apple2icons(deark *c, de_module_params *mparams)
{
	struct a2icons_ctx *d = NULL;
	i64 curpos;
	i64 i;
	// Some colors here are just educated guesses.
	static const u8 dflt_pal[16*3] = {
		0,0,0, 0,0,128, 128,128,0, 128,128,128,
		128,0,0, 128,0,128, 255,128,0, 255,128,128,
		0,128,0, 0,128,128, 128,255,0, 128,255,128,
		192,192,192, 128,128,255, 255,255,128, 255,255,255 };

	d = de_malloc(c, sizeof(struct a2icons_ctx));
	de_copy_palette_from_rgb24(dflt_pal, d->pal, 16);

	d->maskpal[0] = DE_STOCKCOLOR_BLACK;
	for(i=1; i<16; i++) {
		d->maskpal[i] = DE_STOCKCOLOR_WHITE;
	}

	curpos = 26;
	while(1) {
		if(curpos+A2I_GROUP_HDR_SIZE > c->infile->len) goto done;
		d->group_startpos = curpos;
		d->group_len = de_getu16le(curpos);
		if(d->group_len<A2I_GROUP_HDR_SIZE) goto done;
		d->group_endpos = d->group_startpos+d->group_len;
		if(d->group_endpos > c->infile->len) goto done;

		do_a2i_icon_group(c, d);
		curpos = d->group_endpos;
	}

done:
	de_free(c, d);
}

void de_module_apple2icons(deark *c, struct deark_module_info *mi)
{
	mi->id = "apple2icons";
	mi->desc = "Apple II icons archive";
	mi->run_fn = de_run_apple2icons;
	mi->identify_fn = NULL;
}

// **************************************************************************
// PIXIT executable graphics
// **************************************************************************

static int is_px_sig_at(deark *c, i64 pos)
{
	return !dbuf_memcmp(c->infile, pos, (const void*)"PX", 2);
}

struct pixit_ctx {
	u8 need_errmsg;
	u8 is_raw_px_fmt;
	i64 pxpos;
	i64 w, h;
	de_color pal[256];
};

static void de_run_pixit(deark *c, de_module_params *mparams)
{
	struct pixit_ctx *d = NULL;
	i64 n;
	i64 palpos;
	i64 pixelspos;
	de_bitmap *img = NULL;

	d = de_malloc(c, sizeof(struct pixit_ctx));

	if(is_px_sig_at(c, 0)) {
		d->is_raw_px_fmt = 1;
		d->pxpos = 0;
	}

	if(!d->is_raw_px_fmt) {
		if(de_getbyte(17)!=0xba) {
			d->need_errmsg = 1;
			goto done;
		}

		n = de_getu16le(18);
		d->pxpos = n-272;

		if(!is_px_sig_at(c, n-272)) {
			d->need_errmsg = 1;
			goto done;
		}
	}

	de_dbg(c, "image pos: %"I64_FMT, d->pxpos);

	d->w = de_getu16le(d->pxpos+2);
	d->h = de_getu16le(d->pxpos+4);
	de_dbg_dimensions(c, d->w, d->h);
	if(!de_good_image_dimensions(c, d->w, d->h)) goto done;

	palpos = d->pxpos+16;
	de_read_simple_palette(c, c->infile, palpos, 256, 3, d->pal, 256,
		DE_RDPALTYPE_VGA18BIT, 0);

	img = de_bitmap_create(c, d->w, d->h, 3);
	pixelspos = palpos + 768;
	de_dbg(c, "pixels pos: %"I64_FMT, pixelspos);
	de_convert_image_paletted(c->infile, pixelspos, 8, d->w, d->pal, img, 0);
	de_bitmap_write_to_file(img, NULL, DE_CREATEFLAG_OPT_IMAGE);

done:
	de_bitmap_destroy(img);
	if(d) {
		if(d->need_errmsg) {
			de_err(c, "Failed to decode PIXIT file");
		}
		de_free(c, d);
	}
}

static int de_identify_pixit(deark *c)
{
	i64 n;
	u8 b0;

	b0 = de_getbyte(0);
	if(b0=='P') {
		// Raw PX (.PIX) intermediate format. Identified only if it is exactly
		// 320x200 pixels. It's possible to create files with other dimensions,
		// but they don't work correctly, at least not with the original PIXIT.
		if(c->infile->len != (16+768+320*200)) return 0;
		if(dbuf_memcmp(c->infile, 1, (const void*)"\x58\x40\x01\xc8\x00", 5)) {
			return 0;
		}
		return 90;
	}
	else if(b0==0xbc) {
		// A COM-based executable image.
		if(c->infile->len<800 || c->infile->len>65280) return 0;
		if(dbuf_memcmp(c->infile, 1, (const void*)"\x00\x01\xb8\x13\x00\xcd\x10", 7)) {
			return 0;
		}
		if(de_getbyte(17)!=0xba) return 0;
		n = de_getu16le(18);
		if(!is_px_sig_at(c, n-272)) return 0;
		return 100;
	}
	return 0;
}

void de_module_pixit(deark *c, struct deark_module_info *mi)
{
	mi->id = "pixit";
	mi->desc = "PIXIT/pix320 executable image";
	mi->run_fn = de_run_pixit;
	mi->identify_fn = de_identify_pixit;
}

// **************************************************************************
// DXP image
// Signature "DXP1".
// Most likely related to a framework named Dexter, by Paul Burkey and
// Shoecake Games.
// **************************************************************************

struct dxp_ctx {
	i64 w, h;
	i64 cmpr_pos;
	i64 cmpr_size;
	i64 rowspan;
	UI bpp;
	UI bypp;
	UI cmpr_meth;
	u8 errflag;
	u8 need_errmsg;
	dbuf *unc_pixels;
};

static void dxp_decompress(deark *c, struct dxp_ctx *d)
{
	struct de_dfilter_in_params dcmpri;
	struct de_dfilter_out_params dcmpro;
	struct de_dfilter_results dres;
	struct de_deflate_params inflparams;
	i64 unc_len;

	if(d->unc_pixels) goto done;
	de_zeromem(&inflparams, sizeof(struct de_deflate_params));
	de_dfilter_init_objects(c, &dcmpri, &dcmpro, &dres);

	dcmpri.f = c->infile;
	dcmpri.pos = d->cmpr_pos;
	dcmpri.len = d->cmpr_size;
	unc_len = d->rowspan*d->h;
	d->unc_pixels = dbuf_create_membuf(c, unc_len, 0x1);
	dcmpro.f = d->unc_pixels;
	dcmpro.len_known = 1;
	dcmpro.expected_len = unc_len;
	inflparams.flags = DE_DEFLATEFLAG_ISZLIB;
	fmtutil_decompress_deflate_ex(c, &dcmpri, &dcmpro, &dres, &inflparams);
	if(dres.errcode) {
		de_err(c, "%s", de_dfilter_get_errmsg(c, &dres));
		d->errflag = 1;
		goto done;
	}

done:
	;
}

static void dxp_convert_image16(deark *c, struct dxp_ctx *d, de_bitmap *img)
{
	i64 i, j;
	i64 pos = 0;

	for(j=0; j<d->h; j++) {
		for(i=0; i<d->w; i++) {
			u32 x;
			de_color clr;

			// Strange that this is little-endian.
			x = (u32)dbuf_getu16le_p(d->unc_pixels, &pos);
			clr = de_rgb565_to_888(x);
			de_bitmap_setpixel_rgb(img, i, j, clr);
		}
	}
}

static void de_run_dxp_image(deark *c, de_module_params *mparams)
{
	de_bitmap *img = NULL;
	struct dxp_ctx *d = NULL;
	i64 pos;

	d = de_malloc(c, sizeof(struct dxp_ctx));
	pos = 6;
	d->w = de_getu16be_p(&pos);
	d->h = de_getu16be_p(&pos);
	de_dbg_dimensions(c, d->w, d->h);
	d->bpp = (UI)de_getbyte_p(&pos);
	de_dbg(c, "bits/pixel: %u", d->bpp);
	d->bypp = (d->bpp+7)/8;
	d->rowspan = d->w*(i64)d->bypp;
	d->cmpr_meth = (UI)de_getbyte_p(&pos);
	de_dbg(c, "cmpr meth: %u", d->cmpr_meth);
	d->cmpr_size = de_getu32be_p(&pos);
	de_dbg(c, "cmpr size: %"I64_FMT, d->cmpr_size);
	d->cmpr_pos = 16;

	if(d->cmpr_pos + d->cmpr_size > c->infile->len) {
		de_err(c, "Invalid or truncated file");
		goto done;
	}
	if(!de_good_image_dimensions(c, d->w, d->h)) goto done;

	if((d->cmpr_meth!=0 && d->cmpr_meth!=1) ||
		(d->bpp!=16 && d->bpp!=24))
	{
		d->need_errmsg = 1;
		goto done;
	}

	if(d->cmpr_meth==1) {
		dxp_decompress(c, d);
		if(d->errflag) goto done;
	}

	if(!d->unc_pixels) {
		d->unc_pixels = dbuf_open_input_subfile(c->infile, d->cmpr_pos, d->cmpr_size);
	}

	img = de_bitmap_create(c, d->w, d->h, 3);
	if(d->bpp==16) {
		dxp_convert_image16(c, d, img);
	}
	else {
		de_convert_image_rgb(d->unc_pixels, 0, d->rowspan, (i64)d->bypp, img, 0);
	}
	de_bitmap_write_to_file(img, NULL, 0);

done:
	de_bitmap_destroy(img);
	if(d) {
		if(d->need_errmsg) {
			de_err(c, "Unsupported DXP image");
		}
		dbuf_close(d->unc_pixels);
		de_free(c, d);
	}
}

static int de_identify_dxp_image(deark *c)
{
	if(!dbuf_memcmp(c->infile, 0, (const void*)"DXP1\x01", 5)) {
		return 100;
	}
	return 0;
}

void de_module_dxp_image(deark *c, struct deark_module_info *mi)
{
	mi->id = "dxp_image";
	mi->desc = "DXP image (Dexter)";
	mi->run_fn = de_run_dxp_image;
	mi->identify_fn = de_identify_dxp_image;
}
