#include "mupdf/fitz.h"

static const unsigned char web_palette[] = {
	0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x66, 0x00, 0x00, 0x99, 0x00, 0x00, 0xCC, 0x00, 0x00, 0xFF, 0x00, 0x00,
	0x00, 0x00, 0x33, 0x33, 0x00, 0x33, 0x66, 0x00, 0x33, 0x99, 0x00, 0x33, 0xCC, 0x00, 0x33, 0xFF, 0x00, 0x33,
	0x00, 0x00, 0x66, 0x33, 0x00, 0x66, 0x66, 0x00, 0x66, 0x99, 0x00, 0x66, 0xCC, 0x00, 0x66, 0xFF, 0x00, 0x66,
	0x00, 0x00, 0x99, 0x33, 0x00, 0x99, 0x66, 0x00, 0x99, 0x99, 0x00, 0x99, 0xCC, 0x00, 0x99, 0xFF, 0x00, 0x99,
	0x00, 0x00, 0xCC, 0x33, 0x00, 0xCC, 0x66, 0x00, 0xCC, 0x99, 0x00, 0xCC, 0xCC, 0x00, 0xCC, 0xFF, 0x00, 0xCC,
	0x00, 0x00, 0xFF, 0x33, 0x00, 0xFF, 0x66, 0x00, 0xFF, 0x99, 0x00, 0xFF, 0xCC, 0x00, 0xFF, 0xFF, 0x00, 0xFF,
	0x00, 0x33, 0x00, 0x33, 0x33, 0x00, 0x66, 0x33, 0x00, 0x99, 0x33, 0x00, 0xCC, 0x33, 0x00, 0xFF, 0x33, 0x00,
	0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x66, 0x33, 0x33, 0x99, 0x33, 0x33, 0xCC, 0x33, 0x33, 0xFF, 0x33, 0x33,
	0x00, 0x33, 0x66, 0x33, 0x33, 0x66, 0x66, 0x33, 0x66, 0x99, 0x33, 0x66, 0xCC, 0x33, 0x66, 0xFF, 0x33, 0x66,
	0x00, 0x33, 0x99, 0x33, 0x33, 0x99, 0x66, 0x33, 0x99, 0x99, 0x33, 0x99, 0xCC, 0x33, 0x99, 0xFF, 0x33, 0x99,
	0x00, 0x33, 0xCC, 0x33, 0x33, 0xCC, 0x66, 0x33, 0xCC, 0x99, 0x33, 0xCC, 0xCC, 0x33, 0xCC, 0xFF, 0x33, 0xCC,
	0x00, 0x33, 0xFF, 0x33, 0x33, 0xFF, 0x66, 0x33, 0xFF, 0x99, 0x33, 0xFF, 0xCC, 0x33, 0xFF, 0xFF, 0x33, 0xFF,
	0x00, 0x66, 0x00, 0x33, 0x66, 0x00, 0x66, 0x66, 0x00, 0x99, 0x66, 0x00, 0xCC, 0x66, 0x00, 0xFF, 0x66, 0x00,
	0x00, 0x66, 0x33, 0x33, 0x66, 0x33, 0x66, 0x66, 0x33, 0x99, 0x66, 0x33, 0xCC, 0x66, 0x33, 0xFF, 0x66, 0x33,
	0x00, 0x66, 0x66, 0x33, 0x66, 0x66, 0x66, 0x66, 0x66, 0x99, 0x66, 0x66, 0xCC, 0x66, 0x66, 0xFF, 0x66, 0x66,
	0x00, 0x66, 0x99, 0x33, 0x66, 0x99, 0x66, 0x66, 0x99, 0x99, 0x66, 0x99, 0xCC, 0x66, 0x99, 0xFF, 0x66, 0x99,
	0x00, 0x66, 0xCC, 0x33, 0x66, 0xCC, 0x66, 0x66, 0xCC, 0x99, 0x66, 0xCC, 0xCC, 0x66, 0xCC, 0xFF, 0x66, 0xCC,
	0x00, 0x66, 0xFF, 0x33, 0x66, 0xFF, 0x66, 0x66, 0xFF, 0x99, 0x66, 0xFF, 0xCC, 0x66, 0xFF, 0xFF, 0x66, 0xFF,
	0x00, 0x99, 0x00, 0x33, 0x99, 0x00, 0x66, 0x99, 0x00, 0x99, 0x99, 0x00, 0xCC, 0x99, 0x00, 0xFF, 0x99, 0x00,
	0x00, 0x99, 0x33, 0x33, 0x99, 0x33, 0x66, 0x99, 0x33, 0x99, 0x99, 0x33, 0xCC, 0x99, 0x33, 0xFF, 0x99, 0x33,
	0x00, 0x99, 0x66, 0x33, 0x99, 0x66, 0x66, 0x99, 0x66, 0x99, 0x99, 0x66, 0xCC, 0x99, 0x66, 0xFF, 0x99, 0x66,
	0x00, 0x99, 0x99, 0x33, 0x99, 0x99, 0x66, 0x99, 0x99, 0x99, 0x99, 0x99, 0xCC, 0x99, 0x99, 0xFF, 0x99, 0x99,
	0x00, 0x99, 0xCC, 0x33, 0x99, 0xCC, 0x66, 0x99, 0xCC, 0x99, 0x99, 0xCC, 0xCC, 0x99, 0xCC, 0xFF, 0x99, 0xCC,
	0x00, 0x99, 0xFF, 0x33, 0x99, 0xFF, 0x66, 0x99, 0xFF, 0x99, 0x99, 0xFF, 0xCC, 0x99, 0xFF, 0xFF, 0x99, 0xFF,
	0x00, 0xCC, 0x00, 0x33, 0xCC, 0x00, 0x66, 0xCC, 0x00, 0x99, 0xCC, 0x00, 0xCC, 0xCC, 0x00, 0xFF, 0xCC, 0x00,
	0x00, 0xCC, 0x33, 0x33, 0xCC, 0x33, 0x66, 0xCC, 0x33, 0x99, 0xCC, 0x33, 0xCC, 0xCC, 0x33, 0xFF, 0xCC, 0x33,
	0x00, 0xCC, 0x66, 0x33, 0xCC, 0x66, 0x66, 0xCC, 0x66, 0x99, 0xCC, 0x66, 0xCC, 0xCC, 0x66, 0xFF, 0xCC, 0x66,
	0x00, 0xCC, 0x99, 0x33, 0xCC, 0x99, 0x66, 0xCC, 0x99, 0x99, 0xCC, 0x99, 0xCC, 0xCC, 0x99, 0xFF, 0xCC, 0x99,
	0x00, 0xCC, 0xCC, 0x33, 0xCC, 0xCC, 0x66, 0xCC, 0xCC, 0x99, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFF, 0xCC, 0xCC,
	0x00, 0xCC, 0xFF, 0x33, 0xCC, 0xFF, 0x66, 0xCC, 0xFF, 0x99, 0xCC, 0xFF, 0xCC, 0xCC, 0xFF, 0xFF, 0xCC, 0xFF,
	0x00, 0xFF, 0x00, 0x33, 0xFF, 0x00, 0x66, 0xFF, 0x00, 0x99, 0xFF, 0x00, 0xCC, 0xFF, 0x00, 0xFF, 0xFF, 0x00,
	0x00, 0xFF, 0x33, 0x33, 0xFF, 0x33, 0x66, 0xFF, 0x33, 0x99, 0xFF, 0x33, 0xCC, 0xFF, 0x33, 0xFF, 0xFF, 0x33,
	0x00, 0xFF, 0x66, 0x33, 0xFF, 0x66, 0x66, 0xFF, 0x66, 0x99, 0xFF, 0x66, 0xCC, 0xFF, 0x66, 0xFF, 0xFF, 0x66,
	0x00, 0xFF, 0x99, 0x33, 0xFF, 0x99, 0x66, 0xFF, 0x99, 0x99, 0xFF, 0x99, 0xCC, 0xFF, 0x99, 0xFF, 0xFF, 0x99,
	0x00, 0xFF, 0xCC, 0x33, 0xFF, 0xCC, 0x66, 0xFF, 0xCC, 0x99, 0xFF, 0xCC, 0xCC, 0xFF, 0xCC, 0xFF, 0xFF, 0xCC,
	0x00, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0x66, 0xFF, 0xFF, 0x99, 0xFF, 0xFF, 0xCC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static const unsigned char vga_palette[] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0x00, 0xAA, 0xAA,
	0xAA, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0xAA, 0x55, 0x00, 0xAA, 0xAA, 0xAA,
	0x55, 0x55, 0x55, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0x55, 0xFF, 0xFF,
	0xFF, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF,
};

static const unsigned char gray_palette[] = {
	0x00, 0x00, 0x00, 0x54, 0x54, 0x54,
	0xA8, 0xA8, 0xA8, 0xFF, 0xFF, 0xFF,
};

static const unsigned char bw_palette[] = {
	0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
};

enum {
	BI_RLE24 = -1,
	BI_RGB = 0,
	BI_RLE8 = 1,
	BI_RLE4 = 2,
	BI_BITFIELDS = 3,
	BI_JPEG = 4,
	BI_PNG = 5,
	BI_ALPHABITS = 6,
	BI_UNSUPPORTED = 42,
};

struct info
{
	int filesize;
	int offset;
	int topdown;
	int width, height;
	int xres, yres;
	int bitcount;
	int compression;
	int colors;
	int rmask, gmask, bmask, amask;
	unsigned char palette[256 * 3];

	int extramasks;
	int palettetype;
	unsigned char *samples;

	int rshift, gshift, bshift, ashift;
	int rbits, gbits, bbits, abits;
};

#define read8(p) ((p)[0])
#define read16(p) (((p)[1] << 8) | (p)[0])
#define read32(p) (((p)[3] << 24) | ((p)[2] << 16) | ((p)[1] << 8) | (p)[0])

static unsigned char *
bmp_read_file_header(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	if (end - p < 14)
		fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in file header in bmp image");

	if (memcmp(&p[0], "BM", 2))
		fz_throw(ctx, FZ_ERROR_GENERIC, "invalid signature in bmp image");

	info->filesize = read32(p + 2);
	info->offset = read32(p + 10);

	return p + 14;
}

static unsigned char *
bmp_read_bitmap_core_header(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	int size;

	size = read32(p + 0);
	if (size != 12)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported core header size in bmp image");

	if (size >= 12)
	{
		if (end - p < 12)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap core header in bmp image");

		info->width = read16(p + 4);
		info->height = read16(p + 6);
		info->bitcount = read16(p + 10);
	}

	info->xres = 2835;
	info->yres = 2835;
	info->compression = BI_RGB;
	info->palettetype = 0;

	return p + size;
}

static unsigned char *
bmp_read_bitmap_os2_header(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	int size;

	size = read32(p + 0);
	if (size != 16 && size != 64)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported os2 header size in bmp image");

	if (size >= 16)
	{
		if (end - p < 16)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap os2 header in bmp image");

		info->width = read32(p + 4);
		info->height = read32(p + 8);
		info->bitcount = read16(p + 14);

		info->compression = BI_RGB;
	}
	if (size >= 64)
	{
		if (end - p < 64)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap os2 header in bmp image");

		info->compression = read32(p + 16);
		info->xres = read32(p + 24);
		info->yres = read32(p + 28);
		info->colors = read32(p + 32);

		/* 4 in this header is interpreted as 24 bit RLE encoding */
		if (info->compression < 0)
			info->compression = BI_UNSUPPORTED;
		else if (info->compression == 4)
			info->compression = BI_RLE24;
	}

	info->palettetype = 1;

	return p + size;
}

static void maskinfo(unsigned int mask, int *shift, int *bits)
{
	*bits = 0;
	*shift = 0;
	if (mask) {
		while ((mask & 1) == 0) {
			*shift += 1;
			mask >>= 1;
		}
		while ((mask & 1) == 1) {
			*bits += 1;
			mask >>= 1;
		}
	}
}

static unsigned char *
bmp_read_bitmap_info_header(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	int size;

	size = read32(p + 0);
	if (size != 40 && size != 52 && size != 56 && size != 64 &&
			size != 108 && size != 124)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported info header size in bmp image");

	if (size >= 40)
	{
		if (end - p < 40)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap info header in bmp image");

		info->width = read32(p + 4);
		info->topdown = (p[8 + 3] & 0x80) != 0;
		if (info->topdown)
			info->height = -read32(p + 8);
		else
			info->height = read32(p + 8);
		info->bitcount = read16(p + 14);
		info->compression = read32(p + 16);
		info->xres = read32(p + 24);
		info->yres = read32(p + 28);
		info->colors = read32(p + 32);

		if (size == 40 && info->compression == BI_BITFIELDS && (info->bitcount == 16 || info->bitcount == 32))
			info->extramasks = 1;
		else if (size == 40 && info->compression == BI_ALPHABITS && (info->bitcount == 16 || info->bitcount == 32))
			info->extramasks = 1;

		if (info->bitcount == 16) {
			info->rmask = 0x00007c00;
			info->gmask = 0x000003e0;
			info->bmask = 0x0000001f;
			info->amask = 0x00000000;
		} else if (info->bitcount == 32) {
			info->rmask = 0x00ff0000;
			info->gmask = 0x0000ff00;
			info->bmask = 0x000000ff;
			info->amask = 0x00000000;
		}
	}
	if (size >= 52)
	{
		if (end - p < 52)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap info header in bmp image");

		if (info->compression == BI_BITFIELDS) {
			info->rmask = read32(p + 40);
			info->gmask = read32(p + 44);
			info->bmask = read32(p + 48);
		}
	}
	if (size >= 56)
	{
		if (end - p < 56)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap info header in bmp image");

		if (info->compression == BI_BITFIELDS) {
			info->amask = read32(p + 52);
		}
	}

	info->palettetype = 1;

	return p + size;
}

static unsigned char *
bmp_read_extra_masks(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	int size = 0;

	if (info->compression == BI_BITFIELDS)
	{
		size = 12;
		if (end - p < 12)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in mask header in bmp image");

		info->rmask = read32(p + 0);
		info->gmask = read32(p + 4);
		info->bmask = read32(p + 8);
	}
	else if (info->compression == BI_ALPHABITS)
	{
		size = 16;
		if (end - p < 16)
			fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in mask header in bmp image");

		/* ignore alpha mask */
		info->rmask = read32(p + 0);
		info->gmask = read32(p + 4);
		info->bmask = read32(p + 8);
	}

	return p + size;
}

static int
bmp_palette_is_gray(fz_context *ctx, struct info *info, int readcolors)
{
	int i;
	for (i = 0; i < readcolors; i++)
	{
		int rgdiff = fz_absi(info->palette[3 * i + 0] - info->palette[3 * i + 1]);
		int gbdiff = fz_absi(info->palette[3 * i + 1] - info->palette[3 * i + 2]);
		int rbdiff = fz_absi(info->palette[3 * i + 0] - info->palette[3 * i + 2]);
		if (rgdiff > 2 || gbdiff > 2 || rbdiff > 2)
			return 0;
	}
	return 1;
}

static void
bmp_load_default_palette(fz_context *ctx, struct info *info, int readcolors)
{
	int i;

	fz_warn(ctx, "color table too short; loading default palette");

	if (info->bitcount == 8)
	{
		if (!bmp_palette_is_gray(ctx, info, readcolors))
			memcpy(&info->palette[readcolors * 3], &web_palette[readcolors * 3],
					sizeof(web_palette) - readcolors * 3);
		else
			for (i = readcolors; i < 256; i++)
			{
				info->palette[3 * i + 0] = i;
				info->palette[3 * i + 1] = i;
				info->palette[3 * i + 2] = i;
			}
	}
	else if (info->bitcount == 4)
	{
		if (!bmp_palette_is_gray(ctx, info, readcolors))
			memcpy(&info->palette[readcolors * 3], &vga_palette[readcolors * 3],
					sizeof(vga_palette) - readcolors * 3);
		else
			for (i = readcolors; i < 16; i++)
			{
				info->palette[3 * i + 0] = (i << 4) | i;
				info->palette[3 * i + 1] = (i << 4) | i;
				info->palette[3 * i + 2] = (i << 4) | i;
			}
	}
	else if (info->bitcount == 2)
		memcpy(info->palette, gray_palette, sizeof(gray_palette));
	else if (info->bitcount == 1)
		memcpy(info->palette, bw_palette, sizeof(bw_palette));
}

static unsigned char *
bmp_read_color_table(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	int i, colors, readcolors;

	if (info->bitcount > 8)
		return p;

	if (info->colors == 0)
		colors = 1 << info->bitcount;
	else
		colors = info->colors;

	colors = fz_mini(colors, 1 << info->bitcount);

	if (info->palettetype == 0)
	{
		readcolors = fz_mini(colors, (end - p) / 3);
		for (i = 0; i < readcolors; i++)
		{
			info->palette[3 * i + 0] = read8(p + i * 3 + 2);
			info->palette[3 * i + 1] = read8(p + i * 3 + 1);
			info->palette[3 * i + 2] = read8(p + i * 3 + 0);
		}
		if (readcolors < colors)
			bmp_load_default_palette(ctx, info, readcolors);
		return p + readcolors * 3;
	}
	else
	{
		readcolors = fz_mini(colors, (end - p) / 4);
		for (i = 0; i < readcolors; i++)
		{
			/* ignore alpha channel */
			info->palette[3 * i + 0] = read8(p + i * 4 + 2);
			info->palette[3 * i + 1] = read8(p + i * 4 + 1);
			info->palette[3 * i + 2] = read8(p + i * 4 + 0);
		}
		if (readcolors < colors)
			bmp_load_default_palette(ctx, info, readcolors);
		return p + readcolors * 4;
	}

	return p;
}

static unsigned char *
bmp_decompress_rle24(fz_context *ctx, struct info *info, unsigned char *p, unsigned char **end)
{
	unsigned char *sp, *dp, *ep, *decompressed;
	int width = info->width;
	int height = info->height;
	int stride;
	int x, i;

	stride = (width*3 + 3) / 4 * 4;

	sp = p;
	dp = decompressed = fz_calloc(ctx, height, stride);
	ep = dp + height * stride;
	x = 0;

	while (sp + 2 <= *end)
	{
		if (sp[0] == 0 && sp[1] == 0)
		{ /* end of line */
			if (x*3 < stride)
				dp += stride - x*3;
			sp += 2;
			x = 0;
		}
		else if (sp[0] == 0 && sp[1] == 1)
		{ /* end of bitmap */
			dp = ep;
			break;
		}
		else if (sp[0] == 0 && sp[1] == 2)
		{ /* delta */
			int deltax, deltay;
			if (sp + 4 > *end)
				break;
			deltax = sp[2];
			deltay = sp[3];
			dp += deltax*3 + deltay * stride;
			sp += 4;
			x += deltax;
		}
		else if (sp[0] == 0 && sp[1] >= 3)
		{ /* absolute */
			int n = sp[1] * 3;
			int nn = (n + 1) / 2 * 2;
			if (sp + 2 + nn > *end)
				break;
			if (dp + n > ep) {
				fz_warn(ctx, "buffer overflow in bitmap data in bmp image");
				break;
			}
			sp += 2;
			for (i = 0; i < n; i++)
				dp[i] = sp[i];
			dp += n;
			sp += (n + 1) / 2 * 2;
			x += n;
		}
		else
		{ /* encoded */
			int n = sp[0] * 3;
			if (sp + 1 + 3 > *end)
				break;
			if (dp + n > ep) {
				fz_warn(ctx, "buffer overflow in bitmap data in bmp image");
				break;
			}
			for (i = 0; i < n / 3; i++) {
				dp[i * 3 + 0] = sp[1];
				dp[i * 3 + 1] = sp[2];
				dp[i * 3 + 2] = sp[3];
			}
			dp += n;
			sp += 1 + 3;
			x += n;
		}
	}

	if (dp < ep)
		fz_warn(ctx, "premature end of bitmap data in bmp image");

	info->compression = BI_RGB;
	info->bitcount = 24;
	*end = ep;
	return decompressed;
}

static unsigned char *
bmp_decompress_rle8(fz_context *ctx, struct info *info, unsigned char *p, unsigned char **end)
{
	unsigned char *sp, *dp, *ep, *decompressed;
	int width = info->width;
	int height = info->height;
	int stride;
	int x, i;

	stride = (width + 3) / 4 * 4;

	sp = p;
	dp = decompressed = fz_calloc(ctx, height, stride);
	ep = dp + height * stride;
	x = 0;

	while (sp + 2 <= *end)
	{
		if (sp[0] == 0 && sp[1] == 0)
		{ /* end of line */
			if (x < stride)
				dp += stride - x;
			sp += 2;
			x = 0;
		}
		else if (sp[0] == 0 && sp[1] == 1)
		{ /* end of bitmap */
			dp = ep;
			break;
		}
		else if (sp[0] == 0 && sp[1] == 2)
		{ /* delta */
			int deltax, deltay;
			if (sp + 4 > *end)
				break;
			deltax = sp[2];
			deltay = sp[3];
			dp += deltax + deltay * stride;
			sp += 4;
			x += deltax;
		}
		else if (sp[0] == 0 && sp[1] >= 3)
		{ /* absolute */
			int n = sp[1];
			int nn = (n + 1) / 2 * 2;
			if (sp + 2 + nn > *end)
				break;
			if (dp + n > ep) {
				fz_warn(ctx, "buffer overflow in bitmap data in bmp image");
				break;
			}
			sp += 2;
			for (i = 0; i < n; i++)
				dp[i] = sp[i];
			dp += n;
			sp += (n + 1) / 2 * 2;
			x += n;
		}
		else
		{ /* encoded */
			int n = sp[0];
			if (dp + n > ep) {
				fz_warn(ctx, "buffer overflow in bitmap data in bmp image");
				break;
			}
			for (i = 0; i < n; i++)
				dp[i] = sp[1];
			dp += n;
			sp += 2;
			x += n;
		}
	}

	if (dp < ep)
		fz_warn(ctx, "premature end of bitmap data in bmp image");

	info->compression = BI_RGB;
	info->bitcount = 8;
	*end = ep;
	return decompressed;
}

static unsigned char *
bmp_decompress_rle4(fz_context *ctx, struct info *info, unsigned char *p, unsigned char **end)
{
	unsigned char *sp, *dp, *ep, *decompressed;
	int width = info->width;
	int height = info->height;
	int stride;
	int i, x;

	stride = ((width + 1) / 2 + 3) / 4 * 4;

	sp = p;
	dp = decompressed = fz_calloc(ctx, height, stride);
	ep = dp + height * stride;
	x = 0;

	while (sp + 2 <= *end)
	{
		if (sp[0] == 0 && sp[1] == 0)
		{ /* end of line */
			int xx = x / 2;
			if (xx < stride)
				dp += stride - xx;
			sp += 2;
			x = 0;
		}
		else if (sp[0] == 0 && sp[1] == 1)
		{ /* end of bitmap */
			dp = ep;
			break;
		}
		else if (sp[0] == 0 && sp[1] == 2)
		{ /* delta */
			int deltax, deltay, startlow;
			if (sp + 4 > *end)
				break;
			deltax = sp[2];
			deltay = sp[3];
			startlow = x & 1;
			dp += (deltax + startlow) / 2 + deltay * stride;
			sp += 4;
			x += deltax;
		}
		else if (sp[0] == 0 && sp[1] >= 3)
		{ /* absolute */
			int n = sp[1];
			int nn = ((n + 1) / 2 + 1) / 2 * 2;
			if (sp + 2 + nn > *end)
				break;
			if (dp + n / 2 > ep) {
				fz_warn(ctx, "buffer overflow in bitmap data in bmp image");
				break;
			}
			sp += 2;
			for (i = 0; i < n; i++, x++)
			{
				int val = i & 1 ? (sp[i/2]) & 0xF : (sp[i/2] >> 4) & 0xF;
				if (x & 1)
					*dp++ |= val;
				else
					*dp |= val << 4;
			}
			sp += nn;
		}
		else
		{ /* encoded */
			int n = sp[0];
			int hi = (sp[1] >> 4) & 0xF;
			int lo = sp[1] & 0xF;
			if (dp + n / 2 > ep) {
				fz_warn(ctx, "buffer overflow in bitmap data in bmp image");
				break;
			}
			for (i = 0; i < n; i++, x++)
			{
				int val = i & 1 ? lo : hi;
				if (x & 1)
					*dp++ |= val;
				else
					*dp |= val << 4;
			}
			sp += 2;
		}
	}

	info->compression = BI_RGB;
	info->bitcount = 4;
	*end = ep;
	return decompressed;
}

static fz_pixmap *
bmp_read_bitmap(fz_context *ctx, struct info *info, unsigned char *p, unsigned char *end)
{
	const int mults[] = { 0, 8191, 2730, 1170, 546, 264, 130, 64 };
	fz_pixmap *pix;
	unsigned char *decompressed = NULL;
	unsigned char *ssp, *ddp;
	int bitcount, width, height;
	int sstride, dstride;
	int rmult, gmult, bmult, amult;
	int rtrunc, gtrunc, btrunc, atrunc;
	int x, y;

	if (info->compression == BI_RLE8)
		ssp = decompressed = bmp_decompress_rle8(ctx, info, p, &end);
	else if (info->compression == BI_RLE4)
		ssp = decompressed = bmp_decompress_rle4(ctx, info, p, &end);
	else if (info->compression == BI_RLE24)
		ssp = decompressed = bmp_decompress_rle24(ctx, info, p, &end);
	else
		ssp = p;

	bitcount = info->bitcount;
	width = info->width;
	height = info->height;

	sstride = ((width * bitcount + 31) / 32) * 4;

	if (ssp + sstride * height > end)
	{
		fz_free(ctx, decompressed);
		fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap data in bmp image");
	}

	fz_try(ctx)
		pix = fz_new_pixmap(ctx, fz_device_rgb(ctx), width, height, 1);
	fz_catch(ctx)
	{
		fz_free(ctx, decompressed);
		fz_rethrow(ctx);
	}

	ddp = pix->samples;
	dstride = pix->stride;
	if (!info->topdown)
	{
		ddp = pix->samples + (height - 1) * dstride;
		dstride = -dstride;
	}

	/* These only apply for components in 16-bit and 32-bit mode
	   1-bit (1 * 8191) / 32
	   2-bit (3 * 2730) / 32
	   3-bit (7 * 1170) / 32
	   4-bit (15 * 546) / 32
	   5-bit (31 * 264) / 32
	   6-bit (63 * 130) / 32
	   7-bit (127 * 64) / 32
	*/
	rmult = info->rbits < 8 ? mults[info->rbits] : 1;
	gmult = info->gbits < 8 ? mults[info->gbits] : 1;
	bmult = info->bbits < 8 ? mults[info->bbits] : 1;
	amult = info->abits < 8 ? mults[info->abits] : 1;
	rtrunc = info->rbits < 8 ? 5 : (info->rbits - 8);
	gtrunc = info->gbits < 8 ? 5 : (info->gbits - 8);
	btrunc = info->bbits < 8 ? 5 : (info->bbits - 8);
	atrunc = info->abits < 8 ? 5 : (info->abits - 8);

	for (y = 0; y < height; y++)
	{
		unsigned char *sp = ssp + y * sstride;
		unsigned char *dp = ddp + y * dstride;

		switch (bitcount)
		{
		case 32:
			for (x = 0; x < width; x++)
			{
				unsigned int sample = (sp[3] << 24) | (sp[2] << 16) | (sp[1] << 8) | sp[0];
				unsigned int r = (sample & info->rmask) >> info->rshift;
				unsigned int g = (sample & info->gmask) >> info->gshift;
				unsigned int b = (sample & info->bmask) >> info->bshift;
				unsigned int a = info->abits == 0 ? 255 : (sample & info->amask) >> info->ashift;
				*dp++ = (r * rmult) >> rtrunc;
				*dp++ = (g * gmult) >> gtrunc;
				*dp++ = (b * bmult) >> btrunc;
				*dp++ = info->abits == 0 ? a : (a * amult) >> atrunc;
				sp += 4;
			}
			break;
		case 24:
			for (x = 0; x < width; x++)
			{
				*dp++ = sp[2];
				*dp++ = sp[1];
				*dp++ = sp[0];
				*dp++ = 255;
				sp += 3;
			}
			break;
		case 16:
			for (x = 0; x < width; x++)
			{
				unsigned int sample = (sp[1] << 8) | sp[0];
				unsigned int r = (sample & info->rmask) >> info->rshift;
				unsigned int g = (sample & info->gmask) >> info->gshift;
				unsigned int b = (sample & info->bmask) >> info->bshift;
				unsigned int a = (sample & info->amask) >> info->ashift;
				*dp++ = (r * rmult) >> rtrunc;
				*dp++ = (g * gmult) >> gtrunc;
				*dp++ = (b * bmult) >> btrunc;
				*dp++ = info->abits == 0 ? 255 : (a * amult) >> atrunc;
				sp += 2;
			}
			break;
		case 8:
			for (x = 0; x < width; x++)
			{
				*dp++ = info->palette[3 * sp[0] + 0];
				*dp++ = info->palette[3 * sp[0] + 1];
				*dp++ = info->palette[3 * sp[0] + 2];
				*dp++ = 255;
				sp++;
			}
			break;
		case 4:
			for (x = 0; x < width; x++)
			{
				int idx;
				switch (x & 1)
				{
				case 0: idx = (sp[0] >> 4) & 0x0f; break;
				case 1: idx = (sp[0] >> 0) & 0x0f; sp++; break;
				}
				*dp++ = info->palette[3 * idx + 0];
				*dp++ = info->palette[3 * idx + 1];
				*dp++ = info->palette[3 * idx + 2];
				*dp++ = 255;
			}
			break;
		case 2:
			for (x = 0; x < width; x++)
			{
				int idx;
				switch (x & 3)
				{
				case 0: idx = (sp[0] >> 6) & 0x03; break;
				case 1: idx = (sp[0] >> 4) & 0x03; break;
				case 2: idx = (sp[0] >> 2) & 0x03; break;
				case 3: idx = (sp[0] >> 0) & 0x03; sp++; break;
				}
				*dp++ = info->palette[3 * idx + 0];
				*dp++ = info->palette[3 * idx + 1];
				*dp++ = info->palette[3 * idx + 2];
				*dp++ = 255;
			}
			break;
		case 1:
			for (x = 0; x < width; x++)
			{
				int idx;
				switch (x & 7)
				{
				case 0: idx = (sp[0] >> 7) & 0x01; break;
				case 1: idx = (sp[0] >> 6) & 0x01; break;
				case 2: idx = (sp[0] >> 5) & 0x01; break;
				case 3: idx = (sp[0] >> 4) & 0x01; break;
				case 4: idx = (sp[0] >> 3) & 0x01; break;
				case 5: idx = (sp[0] >> 2) & 0x01; break;
				case 6: idx = (sp[0] >> 1) & 0x01; break;
				case 7: idx = (sp[0] >> 0) & 0x01; sp++; break;
				}
				*dp++ = info->palette[3 * idx + 0];
				*dp++ = info->palette[3 * idx + 1];
				*dp++ = info->palette[3 * idx + 2];
				*dp++ = 255;
			}
			break;
		}
	}

	fz_free(ctx, decompressed);
	fz_premultiply_pixmap(ctx, pix);
	return pix;
}

static fz_pixmap *
bmp_read_image(fz_context *ctx, struct info *info, unsigned char *p, size_t total, int only_metadata)
{
	unsigned char *begin = p;
	unsigned char *end = p + total;
	int size;

	memset(info, 0x00, sizeof (*info));

	p = bmp_read_file_header(ctx, info, p, end);

	info->filesize = fz_mini(info->filesize, (int)total);

	if (end - p < 4)
		fz_throw(ctx, FZ_ERROR_GENERIC, "premature end in bitmap core header in bmp image");
	size = read32(p + 0);

	if (size == 12)
		p = bmp_read_bitmap_core_header(ctx, info, p, end);
	else if (size == 40 || size == 52 || size == 56 || size == 108 || size == 124)
	{
		p = bmp_read_bitmap_info_header(ctx, info, p, end);
		if (info->extramasks)
			p = bmp_read_extra_masks(ctx, info, p, end);
	}
	else if (size == 16 || size == 64)
		p = bmp_read_bitmap_os2_header(ctx, info, p, end);
	else
		fz_throw(ctx, FZ_ERROR_GENERIC, "invalid header size (%d) in bmp image", size);

	maskinfo(info->rmask, &info->rshift, &info->rbits);
	maskinfo(info->gmask, &info->gshift, &info->gbits);
	maskinfo(info->bmask, &info->bshift, &info->bbits);
	maskinfo(info->amask, &info->ashift, &info->abits);

	if (info->width <= 0 || info->width > SHRT_MAX || info->height <= 0 || info->height > SHRT_MAX)
		fz_throw(ctx, FZ_ERROR_GENERIC, "dimensions (%d x %d) out of range in bmp image",
				info->width, info->height);
	if (info->compression != BI_RGB && info->compression != BI_RLE8 &&
			info->compression != BI_RLE4 && info->compression != BI_BITFIELDS &&
			info->compression != BI_JPEG && info->compression != BI_PNG &&
			info->compression != BI_ALPHABITS && info->compression != BI_RLE24)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported compression method (%d) in bmp image", info->compression);
	if ((info->compression == BI_RGB && info->bitcount != 1 &&
			info->bitcount != 2 && info->bitcount != 4 &&
			info->bitcount != 8 && info->bitcount != 16 &&
			info->bitcount != 24 && info->bitcount != 32) ||
			(info->compression == BI_RLE8 && info->bitcount != 8) ||
			(info->compression == BI_RLE4 && info->bitcount != 4) ||
			(info->compression == BI_BITFIELDS && info->bitcount != 16 && info->bitcount != 32) ||
			(info->compression == BI_JPEG && info->bitcount != 0) ||
			(info->compression == BI_PNG && info->bitcount != 0) ||
			(info->compression == BI_ALPHABITS && info->bitcount != 16 && info->bitcount != 32) ||
			(info->compression == BI_RLE24 && info->bitcount != 24))
		fz_throw(ctx, FZ_ERROR_GENERIC, "invalid bits per pixel (%d) for compression (%d) in bmp image",
				info->bitcount, info->compression);
	if (info->rbits < 0 || info->rbits > info->bitcount)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported %d bit red mask in bmp image", info->rbits);
	if (info->gbits < 0 || info->gbits > info->bitcount)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported %d bit green mask in bmp image", info->gbits);
	if (info->bbits < 0 || info->bbits > info->bitcount)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported %d bit blue mask in bmp image", info->bbits);
	if (info->abits < 0 || info->abits > info->bitcount)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported %d bit alpha mask in bmp image", info->abits);

	if (only_metadata)
		return NULL;

	if (info->compression == BI_JPEG)
	{
		if (p - begin < info->offset)
			p = begin + info->offset;
		return fz_load_jpeg(ctx, p, end - p);
	}
	else if (info->compression == BI_PNG)
	{
		if (p - begin < info->offset)
			p = begin + info->offset;
		return fz_load_png(ctx, p, end - p);
	}
	else
	{
		p = bmp_read_color_table(ctx, info, p, begin + info->offset);
		if (p - begin < info->offset)
			p = begin + info->offset;
		return bmp_read_bitmap(ctx, info, p, end);
	}
}

fz_pixmap *
fz_load_bmp(fz_context *ctx, unsigned char *p, size_t total)
{
	struct info bmp;
	fz_pixmap *image;

	image = bmp_read_image(ctx, &bmp, p, total, 0);
	image->xres = bmp.xres / (1000.0f / 25.4f);
	image->yres = bmp.yres / (1000.0f / 25.4f);

	return image;
}

void
fz_load_bmp_info(fz_context *ctx, unsigned char *p, size_t total, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
{
	struct info bmp;

	bmp_read_image(ctx, &bmp, p, total, 1);

	*cspacep = fz_device_rgb(ctx);
	*wp = bmp.width;
	*hp = bmp.height;
	*xresp = bmp.xres / (1000.0f / 25.4f);
	*yresp = bmp.yres / (1000.0f / 25.4f);
}