#include "fitz-imp.h"

#include <string.h>
#include <math.h>
#include <assert.h>

/* TODO: here or public? */
static int
fz_key_storable_needs_reaping(fz_context *ctx, const fz_key_storable *ks)
{
	return ks == NULL ? 0 : (ks->store_key_refs == ks->storable.refs);
}

#define SANE_DPI 72.0f
#define INSANE_DPI 4800.0f

#define SCALABLE_IMAGE_DPI 96

struct fz_compressed_image_s
{
	fz_image super;
	fz_pixmap *tile;
	fz_compressed_buffer *buffer;
};

struct fz_pixmap_image_s
{
	fz_image super;
	fz_pixmap *tile;
};

typedef struct fz_image_key_s fz_image_key;

struct fz_image_key_s {
	int refs;
	fz_image *image;
	int l2factor;
	fz_irect rect;
};

fz_image *
fz_keep_image(fz_context *ctx, fz_image *image)
{
	return fz_keep_key_storable(ctx, &image->key_storable);
}

fz_image *
fz_keep_image_store_key(fz_context *ctx, fz_image *image)
{
	return fz_keep_key_storable_key(ctx, &image->key_storable);
}

void
fz_drop_image_store_key(fz_context *ctx, fz_image *image)
{
	fz_drop_key_storable_key(ctx, &image->key_storable);
}

static int
fz_make_hash_image_key(fz_context *ctx, fz_store_hash *hash, void *key_)
{
	fz_image_key *key = (fz_image_key *)key_;
	hash->u.pir.ptr = key->image;
	hash->u.pir.i = key->l2factor;
	hash->u.pir.r = key->rect;
	return 1;
}

static void *
fz_keep_image_key(fz_context *ctx, void *key_)
{
	fz_image_key *key = (fz_image_key *)key_;
	return fz_keep_imp(ctx, key, &key->refs);
}

static void
fz_drop_image_key(fz_context *ctx, void *key_)
{
	fz_image_key *key = (fz_image_key *)key_;
	if (fz_drop_imp(ctx, key, &key->refs))
	{
		fz_drop_image_store_key(ctx, key->image);
		fz_free(ctx, key);
	}
}

static int
fz_cmp_image_key(fz_context *ctx, void *k0_, void *k1_)
{
	fz_image_key *k0 = (fz_image_key *)k0_;
	fz_image_key *k1 = (fz_image_key *)k1_;
	return k0->image == k1->image && k0->l2factor == k1->l2factor && k0->rect.x0 == k1->rect.x0 && k0->rect.y0 == k1->rect.y0 && k0->rect.x1 == k1->rect.x1 && k0->rect.y1 == k1->rect.y1;
}

static void
fz_format_image_key(fz_context *ctx, char *s, int n, void *key_)
{
	fz_image_key *key = (fz_image_key *)key_;
	fz_snprintf(s, n, "(image %d x %d sf=%d)", key->image->w, key->image->h, key->l2factor);
}

static int
fz_needs_reap_image_key(fz_context *ctx, void *key_)
{
	fz_image_key *key = (fz_image_key *)key_;

	return fz_key_storable_needs_reaping(ctx, &key->image->key_storable);
}

static const fz_store_type fz_image_store_type =
{
	fz_make_hash_image_key,
	fz_keep_image_key,
	fz_drop_image_key,
	fz_cmp_image_key,
	fz_format_image_key,
	fz_needs_reap_image_key
};

void
fz_drop_image(fz_context *ctx, fz_image *image)
{
	fz_drop_key_storable(ctx, &image->key_storable);
}

static void
fz_mask_color_key(fz_pixmap *pix, int n, const int *colorkey)
{
	unsigned char *p = pix->samples;
	int w;
	int k, t;
	int h = pix->h;
	int stride = pix->stride - pix->w * pix->n;
	if (pix->w == 0)
		return;
	while (h--)
	{
		w = pix->w;
		do
		{
			t = 1;
			for (k = 0; k < n; k++)
				if (p[k] < colorkey[k * 2] || p[k] > colorkey[k * 2 + 1])
					t = 0;
			if (t)
				for (k = 0; k < pix->n; k++)
					p[k] = 0;
			p += pix->n;
		}
		while (--w);
		p += stride;
	}
}

static void
fz_unblend_masked_tile(fz_context *ctx, fz_pixmap *tile, fz_image *image, const fz_irect *isa)
{
	fz_pixmap *mask;
	unsigned char *s, *d = tile->samples;
	int n = tile->n;
	int k;
	int sstride, dstride = tile->stride - tile->w * tile->n;
	int h;
	fz_irect subarea;

	/* We need at least as much of the mask as there was of the tile. */
	if (isa)
		subarea = *isa;
	else
	{
		subarea.x0 = 0;
		subarea.y0 = 0;
		subarea.x1 = tile->w;
		subarea.y1 = tile->h;
	}

	mask = fz_get_pixmap_from_image(ctx, image->mask, &subarea, NULL, NULL, NULL);
	s = mask->samples;
	/* RJW: Urgh, bit of nastiness here. fz_pixmap_from_image will either return
	 * an exact match for the subarea we asked for, or the full image, and the
	 * normal way to know is that the matrix will be updated. That doesn't help
	 * us here. */
	if (image->mask->w == mask->w && image->mask->h == mask->h) {
		subarea.x0 = 0;
		subarea.y0 = 0;
	}
	if (isa)
		s += (isa->x0 - subarea.x0) * mask->n + (isa->y0 - subarea.y0) * mask->stride;
	sstride = mask->stride - tile->w * mask->n;
	h = tile->h;

	if (tile->w != 0)
	{
		while (h--)
		{
			int w = tile->w;
			do
			{
				if (*s == 0)
					for (k = 0; k < image->n; k++)
						d[k] = image->colorkey[k];
				else
					for (k = 0; k < image->n; k++)
						d[k] = fz_clampi(image->colorkey[k] + (d[k] - image->colorkey[k]) * 255 / *s, 0, 255);
				s++;
				d += n;
			}
			while (--w);
			s += sstride;
			d += dstride;
		}
	}

	fz_drop_pixmap(ctx, mask);
}

static void fz_adjust_image_subarea(fz_context *ctx, fz_image *image, fz_irect *subarea, int l2factor)
{
	int f = 1<<l2factor;
	int bpp = image->bpc * image->n;
	int mask;

	switch (bpp)
	{
	case 1: mask = 8*f; break;
	case 2: mask = 4*f; break;
	case 4: mask = 2*f; break;
	default: mask = (bpp & 7) == 0 ? f : 0; break;
	}

	if (mask != 0)
	{
		subarea->x0 &= ~(mask - 1);
		subarea->x1 = (subarea->x1 + mask - 1) & ~(mask - 1);
	}
	else
	{
		/* Awkward case - mask cannot be a power of 2. */
		mask = bpp*f;
		switch (bpp)
		{
		case 3:
		case 5:
		case 7:
		case 9:
		case 11:
		case 13:
		case 15:
		default:
			mask *= 8;
			break;
		case 6:
		case 10:
		case 14:
			mask *= 4;
			break;
		case 12:
			mask *= 2;
			break;
		}
		subarea->x0 = (subarea->x0 / mask) * mask;
		subarea->x1 = ((subarea->x1 + mask - 1) / mask) * mask;
	}

	subarea->y0 &= ~(f - 1);
	if (subarea->x1 > image->w)
		subarea->x1 = image->w;
	subarea->y1 = (subarea->y1 + f - 1) & ~(f - 1);
	if (subarea->y1 > image->h)
		subarea->y1 = image->h;
}

static void fz_compute_image_key(fz_context *ctx, fz_image *image, fz_matrix *ctm,
	fz_image_key *key, const fz_irect *subarea, int l2factor, int *w, int *h, int *dw, int *dh)
{
	key->refs = 1;
	key->image = image;
	key->l2factor = l2factor;

	if (subarea == NULL)
	{
		key->rect.x0 = 0;
		key->rect.y0 = 0;
		key->rect.x1 = image->w;
		key->rect.y1 = image->h;
	}
	else
	{
		key->rect = *subarea;
		ctx->tuning->image_decode(ctx->tuning->image_decode_arg, image->w, image->h, key->l2factor, &key->rect);
		fz_adjust_image_subarea(ctx, image, &key->rect, key->l2factor);
	}

	/* Based on that subarea, recalculate the extents */
	if (ctm)
	{
		float frac_w = (float) (key->rect.x1 - key->rect.x0) / image->w;
		float frac_h = (float) (key->rect.y1 - key->rect.y0) / image->h;
		float a = ctm->a * frac_w;
		float b = ctm->b * frac_h;
		float c = ctm->c * frac_w;
		float d = ctm->d * frac_h;
		*w = sqrtf(a * a + b * b);
		*h = sqrtf(c * c + d * d);
	}
	else
	{
		*w = image->w;
		*h = image->h;
	}

	/* Return the true sizes to the caller */
	if (dw)
		*dw = *w;
	if (dh)
		*dh = *h;
	if (*w > image->w)
		*w = image->w;
	if (*h > image->h)
		*h = image->h;

	if (*w == 0 || *h == 0)
		key->l2factor = 0;
}

fz_pixmap *
fz_decomp_image_from_stream(fz_context *ctx, fz_stream *stm, fz_compressed_image *cimg, fz_irect *subarea, int indexed, int l2factor)
{
	fz_image *image = &cimg->super;
	fz_pixmap *tile = NULL;
	size_t stride, len, i;
	unsigned char *samples = NULL;
	int f = 1<<l2factor;
	int w = image->w;
	int h = image->h;
	int matte = image->use_colorkey && image->mask;

	if (matte)
	{
		/* Can't do l2factor decoding */
		if (image->w != image->mask->w || image->h != image->mask->h)
		{
			fz_warn(ctx, "mask must be of same size as image for /Matte");
			matte = 0;
		}
		assert(l2factor == 0);
	}
	if (subarea)
	{
		fz_adjust_image_subarea(ctx, image, subarea, l2factor);
		w = (subarea->x1 - subarea->x0);
		h = (subarea->y1 - subarea->y0);
	}
	w = (w + f - 1) >> l2factor;
	h = (h + f - 1) >> l2factor;

	fz_var(tile);
	fz_var(samples);

	fz_try(ctx)
	{
		int alpha = (image->colorspace == NULL);
		if (image->use_colorkey)
			alpha = 1;
		tile = fz_new_pixmap(ctx, image->colorspace, w, h, NULL, alpha);
		if (image->interpolate & FZ_PIXMAP_FLAG_INTERPOLATE)
			tile->flags |= FZ_PIXMAP_FLAG_INTERPOLATE;
		else
			tile->flags &= ~FZ_PIXMAP_FLAG_INTERPOLATE;

		stride = (w * image->n * image->bpc + 7) / 8;
		if (h > SIZE_MAX / stride)
			fz_throw(ctx, FZ_ERROR_MEMORY, "image too large");
		samples = fz_malloc(ctx, h * stride);

		if (subarea)
		{
			int hh;
			unsigned char *s = samples;
			int stream_w = (image->w + f - 1)>>l2factor;
			size_t stream_stride = (stream_w * image->n * image->bpc + 7) / 8;
			int l_margin = subarea->x0 >> l2factor;
			int t_margin = subarea->y0 >> l2factor;
			int r_margin = (image->w + f - 1 - subarea->x1) >> l2factor;
			int b_margin = (image->h + f - 1 - subarea->y1) >> l2factor;
			int l_skip = (l_margin * image->n * image->bpc)/8;
			int r_skip = (r_margin * image->n * image->bpc + 7)/8;
			size_t t_skip = t_margin * stream_stride + l_skip;
			size_t b_skip = b_margin * stream_stride + r_skip;
			size_t l = fz_skip(ctx, stm, t_skip);
			len = 0;
			if (l == t_skip)
			{
				hh = h;
				do
				{
					l = fz_read(ctx, stm, s, stride);
					s += l;
					len += l;
					if (l < stride)
						break;
					if (--hh == 0)
						break;
					l = fz_skip(ctx, stm, r_skip + l_skip);
					if (l < (size_t)(r_skip + l_skip))
						break;
				}
				while (1);
				(void)fz_skip(ctx, stm, r_skip + b_skip);
			}
		}
		else
		{
			len = fz_read(ctx, stm, samples, h * stride);
		}

		/* Pad truncated images */
		if (len < stride * h)
		{
			fz_warn(ctx, "padding truncated image");
			memset(samples + len, 0, stride * h - len);
		}

		/* Invert 1-bit image masks */
		if (image->imagemask)
		{
			/* 0=opaque and 1=transparent so we need to invert */
			unsigned char *p = samples;
			len = h * stride;
			for (i = 0; i < len; i++)
				p[i] = ~p[i];
		}

		fz_unpack_tile(ctx, tile, samples, image->n, image->bpc, stride, indexed);

		fz_free(ctx, samples);
		samples = NULL;

		/* color keyed transparency */
		if (image->use_colorkey && !image->mask)
			fz_mask_color_key(tile, image->n, image->colorkey);

		if (indexed)
		{
			fz_pixmap *conv;
			fz_decode_indexed_tile(ctx, tile, image->decode, (1 << image->bpc) - 1);
			conv = fz_convert_indexed_pixmap_to_base(ctx, tile);
			fz_drop_pixmap(ctx, tile);
			tile = conv;
		}
		else if (image->use_decode)
		{
			fz_decode_tile(ctx, tile, image->decode);
		}

		/* pre-blended matte color */
		if (matte)
			fz_unblend_masked_tile(ctx, tile, image, subarea);
	}
	fz_catch(ctx)
	{
		fz_drop_pixmap(ctx, tile);
		fz_free(ctx, samples);
		fz_rethrow(ctx);
	}

	return tile;
}

void
fz_drop_image_base(fz_context *ctx, fz_image *image)
{
	fz_drop_colorspace(ctx, image->colorspace);
	fz_drop_image(ctx, image->mask);
	fz_free(ctx, image);
}

void
fz_drop_image_imp(fz_context *ctx, fz_storable *image_)
{
	fz_image *image = (fz_image *)image_;

	image->drop_image(ctx, image);
	fz_drop_image_base(ctx, image);
}

static void
drop_compressed_image(fz_context *ctx, fz_image *image_)
{
	fz_compressed_image *image = (fz_compressed_image *)image_;

	fz_drop_pixmap(ctx, image->tile);
	fz_drop_compressed_buffer(ctx, image->buffer);
}

static void
drop_pixmap_image(fz_context *ctx, fz_image *image_)
{
	fz_pixmap_image *image = (fz_pixmap_image *)image_;

	fz_drop_pixmap(ctx, image->tile);
}

static fz_pixmap *
compressed_image_get_pixmap(fz_context *ctx, fz_image *image_, fz_irect *subarea, int w, int h, int *l2factor)
{
	fz_compressed_image *image = (fz_compressed_image *)image_;
	int native_l2factor;
	fz_stream *stm;
	int indexed;
	fz_pixmap *tile;
	int can_sub = 0;
	int local_l2factor;

	/* If we are using matte, then the decode code requires both image and tile sizes
	 * to match. The simplest way to ensure this is to do no native l2factor decoding.
	 */
	if (image->super.use_colorkey && image->super.mask)
	{
		local_l2factor = 0;
		l2factor = &local_l2factor;
	}

	/* We need to make a new one. */
	/* First check for ones that we can't decode using streams */
	switch (image->buffer->params.type)
	{
	case FZ_IMAGE_PNG:
		tile = fz_load_png(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
		break;
	case FZ_IMAGE_GIF:
		tile = fz_load_gif(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
		break;
	case FZ_IMAGE_BMP:
		tile = fz_load_bmp(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
		break;
	case FZ_IMAGE_TIFF:
		tile = fz_load_tiff(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
		break;
	case FZ_IMAGE_PNM:
		tile = fz_load_pnm(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
		break;
	case FZ_IMAGE_JXR:
		tile = fz_load_jxr(ctx, image->buffer->buffer->data, image->buffer->buffer->len);
		break;
	case FZ_IMAGE_JPX:
		tile = fz_load_jpx(ctx, image->buffer->buffer->data, image->buffer->buffer->len, NULL);
		break;
	case FZ_IMAGE_JPEG:
		/* Scan JPEG stream and patch missing height values in header */
		{
			unsigned char *s = image->buffer->buffer->data;
			unsigned char *e = s + image->buffer->buffer->len;
			unsigned char *d;
			for (d = s + 2; s < d && d < e - 9 && d[0] == 0xFF; d += (d[2] << 8 | d[3]) + 2)
			{
				if (d[1] < 0xC0 || (0xC3 < d[1] && d[1] < 0xC9) || 0xCB < d[1])
					continue;
				if ((d[5] == 0 && d[6] == 0) || ((d[5] << 8) | d[6]) > image->super.h)
				{
					d[5] = (image->super.h >> 8) & 0xFF;
					d[6] = image->super.h & 0xFF;
				}
			}
		}
		/* fall through */

	default:
		native_l2factor = l2factor ? *l2factor : 0;
		stm = fz_open_image_decomp_stream_from_buffer(ctx, image->buffer, l2factor);
		fz_try(ctx)
		{
			if (l2factor)
				native_l2factor -= *l2factor;
			indexed = fz_colorspace_is_indexed(ctx, image->super.colorspace);
			can_sub = 1;
			tile = fz_decomp_image_from_stream(ctx, stm, image, subarea, indexed, native_l2factor);
		}
		fz_always(ctx)
			fz_drop_stream(ctx, stm);
		fz_catch(ctx)
			fz_rethrow(ctx);

		/* CMYK JPEGs in XPS documents have to be inverted */
		if (image->super.invert_cmyk_jpeg &&
			image->buffer->params.type == FZ_IMAGE_JPEG &&
			fz_colorspace_is_cmyk(ctx, image->super.colorspace) &&
			image->buffer->params.u.jpeg.color_transform)
		{
			fz_invert_pixmap(ctx, tile);
		}

		break;
	}

	if (can_sub == 0 && subarea != NULL)
	{
		subarea->x0 = 0;
		subarea->y0 = 0;
		subarea->x1 = image->super.w;
		subarea->y1 = image->super.h;
	}

	return tile;
}

static fz_pixmap *
pixmap_image_get_pixmap(fz_context *ctx, fz_image *image_, fz_irect *subarea, int w, int h, int *l2factor)
{
	fz_pixmap_image *image = (fz_pixmap_image *)image_;

	/* 'Simple' images created direct from pixmaps will have no buffer
	 * of compressed data. We cannot do any better than just returning
	 * a pointer to the original 'tile'.
	 */
	return fz_keep_pixmap(ctx, image->tile); /* That's all we can give you! */
}

static void
update_ctm_for_subarea(fz_matrix *ctm, const fz_irect *subarea, int w, int h)
{
	fz_matrix m;

	if (subarea->x0 == 0 && subarea->y0 == 0 && subarea->x1 == w && subarea->y1 == h)
		return;

	m.a = (float) (subarea->x1 - subarea->x0) / w;
	m.b = 0;
	m.c = 0;
	m.d = (float) (subarea->y1 - subarea->y0) / h;
	m.e = (float) subarea->x0 / w;
	m.f = (float) subarea->y0 / h;
	*ctm = fz_concat(m, *ctm);
}

void fz_default_image_decode(void *arg, int w, int h, int l2factor, fz_irect *subarea)
{
	(void)arg;

	if ((subarea->x1-subarea->x0)*(subarea->y1-subarea->y0) >= (w*h/10)*9)
	{
		/* Either no subarea specified, or a subarea 90% or more of the
		 * whole area specified. Use the whole image. */
		subarea->x0 = 0;
		subarea->y0 = 0;
		subarea->x1 = w;
		subarea->y1 = h;
	}
	else
	{
		/* Clip to the edges if they are within 1% */
		if (subarea->x0 <= w/100)
			subarea->x0 = 0;
		if (subarea->y0 <= h/100)
			subarea->y0 = 0;
		if (subarea->x1 >= w*99/100)
			subarea->x1 = w;
		if (subarea->y1 >= h*99/100)
			subarea->y1 = h;
	}
}

static fz_pixmap *
fz_find_image_tile(fz_context *ctx, fz_image *image, fz_image_key *key, fz_matrix *ctm)
{
	fz_pixmap *tile;
	do
	{
		tile = fz_find_item(ctx, fz_drop_pixmap_imp, key, &fz_image_store_type);
		if (tile)
		{
			update_ctm_for_subarea(ctm, &key->rect, image->w, image->h);
			return tile;
		}
		key->l2factor--;
	}
	while (key->l2factor >= 0);
	return NULL;
}

/*
	Called to get a handle to a pixmap from an image.

	image: The image to retrieve a pixmap from.

	color_params: The color parameters (or NULL for defaults).

	subarea: The subarea of the image that we actually care about (or NULL
	to indicate the whole image).

	trans: Optional, unless subarea is given. If given, then on entry this is
	the transform that will be applied to the complete image. It should be
	updated on exit to the transform to apply to the given subarea of the
	image. This is used to calculate the desired width/height for subsampling.

	w: If non-NULL, a pointer to an int to be updated on exit to the
	width (in pixels) that the scaled output will cover.

	h: If non-NULL, a pointer to an int to be updated on exit to the
	height (in pixels) that the scaled output will cover.

	Returns a non NULL pixmap pointer. May throw exceptions.
*/
fz_pixmap *
fz_get_pixmap_from_image(fz_context *ctx, fz_image *image, const fz_irect *subarea, fz_matrix *ctm, int *dw, int *dh)
{
	fz_pixmap *tile;
	int l2factor, l2factor_remaining;
	fz_image_key key;
	fz_image_key *keyp = NULL;
	int w;
	int h;

	fz_var(keyp);

	if (!image)
		return NULL;

	/* Figure out the extent. */
	if (ctm)
	{
		w = sqrtf(ctm->a * ctm->a + ctm->b * ctm->b);
		h = sqrtf(ctm->c * ctm->c + ctm->d * ctm->d);
	}
	else
	{
		w = image->w;
		h = image->h;
	}

	if (image->scalable)
	{
		/* If the image is scalable, we always want to re-render and never cache. */
		fz_irect subarea_copy;
		if (subarea)
			subarea_copy = *subarea;
		l2factor_remaining = 0;
		if (dw) *dw = w;
		if (dh) *dh = h;
		return image->get_pixmap(ctx, image, subarea ? &subarea_copy : NULL, image->w, image->h, &l2factor_remaining);
	}

	/* Clamp requested image size, since we never want to magnify images here. */
	if (w > image->w)
		w = image->w;
	if (h > image->h)
		h = image->h;

	if (image->decoded)
	{
		/* If the image is already decoded, then we can't offer a subarea,
		 * or l2factor, and we don't want to cache. */
		l2factor_remaining = 0;
		if (dw) *dw = w;
		if (dh) *dh = h;
		return image->get_pixmap(ctx, image, NULL, image->w, image->h, &l2factor_remaining);
	}

	/* What is our ideal factor? We search for the largest factor where
	 * we can subdivide and stay larger than the required size. We add
	 * a fudge factor of +2 here to allow for the possibility of
	 * expansion due to grid fitting. */
	l2factor = 0;
	if (w > 0 && h > 0)
	{
		while (image->w>>(l2factor+1) >= w+2 && image->h>>(l2factor+1) >= h+2 && l2factor < 6)
			l2factor++;
	}

	/* First, look through the store for existing tiles */
	if (subarea)
	{
		fz_compute_image_key(ctx, image, ctm, &key, subarea, l2factor, &w, &h, dw, dh);
		tile = fz_find_image_tile(ctx, image, &key, ctm);
		if (tile)
			return tile;
	}

	/* No subarea given, or no tile for subarea found; try entire image */
	fz_compute_image_key(ctx, image, ctm, &key, NULL, l2factor, &w, &h, dw, dh);
	tile = fz_find_image_tile(ctx, image, &key, ctm);
	if (tile)
		return tile;

	/* Neither subarea nor full image tile found; prepare the subarea key again */
	if (subarea)
		fz_compute_image_key(ctx, image, ctm, &key, subarea, l2factor, &w, &h, dw, dh);

	/* We'll have to decode the image; request the correct amount of downscaling. */
	l2factor_remaining = l2factor;
	tile = image->get_pixmap(ctx, image, &key.rect, w, h, &l2factor_remaining);

	/* Update the ctm to allow for subareas. */
	if (ctm)
		update_ctm_for_subarea(ctm, &key.rect, image->w, image->h);

	/* l2factor_remaining is updated to the amount of subscaling left to do */
	assert(l2factor_remaining >= 0 && l2factor_remaining <= 6);
	if (l2factor_remaining)
	{
		fz_try(ctx)
			fz_subsample_pixmap(ctx, tile, l2factor_remaining);
		fz_catch(ctx)
		{
			fz_drop_pixmap(ctx, tile);
			fz_rethrow(ctx);
		}
	}

	fz_try(ctx)
	{
		fz_pixmap *existing_tile;

		/* Now we try to cache the pixmap. Any failure here will just result
		 * in us not caching. */
		keyp = fz_malloc_struct(ctx, fz_image_key);
		keyp->refs = 1;
		keyp->image = fz_keep_image_store_key(ctx, image);
		keyp->l2factor = l2factor;
		keyp->rect = key.rect;

		existing_tile = fz_store_item(ctx, keyp, tile, fz_pixmap_size(ctx, tile), &fz_image_store_type);
		if (existing_tile)
		{
			/* We already have a tile. This must have been produced by a
			 * racing thread. We'll throw away ours and use that one. */
			fz_drop_pixmap(ctx, tile);
			tile = existing_tile;
		}
	}
	fz_always(ctx)
	{
		fz_drop_image_key(ctx, keyp);
	}
	fz_catch(ctx)
	{
		/* Do nothing */
	}

	return tile;
}

static size_t
pixmap_image_get_size(fz_context *ctx, fz_image *image)
{
	fz_pixmap_image *im = (fz_pixmap_image *)image;

	if (image == NULL)
		return 0;

	return sizeof(fz_pixmap_image) + fz_pixmap_size(ctx, im->tile);
}

size_t fz_image_size(fz_context *ctx, fz_image *im)
{
	if (im == NULL)
		return 0;

	return im->get_size(ctx, im);
}

/*
	Create an image from the given
	pixmap.

	pixmap: The pixmap to base the image upon. A new reference
	to this is taken.

	mask: NULL, or another image to use as a mask for this one.
	A new reference is taken to this image. Supplying a masked
	image as a mask to another image is illegal!
*/
fz_image *
fz_new_image_from_pixmap(fz_context *ctx, fz_pixmap *pixmap, fz_image *mask)
{
	fz_pixmap_image *image;

	image = fz_new_derived_image(ctx, pixmap->w, pixmap->h, 8, pixmap->colorspace,
				pixmap->xres, pixmap->yres, 0, 0,
				NULL, NULL, mask, fz_pixmap_image,
				pixmap_image_get_pixmap,
				pixmap_image_get_size,
				drop_pixmap_image);
	image->tile = fz_keep_pixmap(ctx, pixmap);
	image->super.decoded = 1;

	return &image->super;
}

/*
	Internal function to make a new fz_image structure
	for a derived class.

	w,h: Width and height of the created image.

	bpc: Bits per component.

	colorspace: The colorspace (determines the number of components,
	and any color conversions required while decoding).

	xres, yres: The X and Y resolutions respectively.

	interpolate: 1 if interpolation should be used when decoding
	this image, 0 otherwise.

	imagemask: 1 if this is an imagemask (i.e. transparent), 0
	otherwise.

	decode: NULL, or a pointer to to a decode array. The default
	decode array is [0 1] (repeated n times, for n color components).

	colorkey: NULL, or a pointer to a colorkey array. The default
	colorkey array is [0 255] (repeatd n times, for n color
	components).

	mask: NULL, or another image to use as a mask for this one.
	A new reference is taken to this image. Supplying a masked
	image as a mask to another image is illegal!

	size: The size of the required allocated structure (the size of
	the derived structure).

	get: The function to be called to obtain a decoded pixmap.

	get_size: The function to be called to return the storage size
	used by this image.

	drop: The function to be called to dispose of this image once
	the last reference is dropped.

	Returns a pointer to an allocated structure of the required size,
	with the first sizeof(fz_image) bytes initialised as appropriate
	given the supplied parameters, and the other bytes set to zero.
*/
fz_image *
fz_new_image_of_size(fz_context *ctx, int w, int h, int bpc, fz_colorspace *colorspace,
		int xres, int yres, int interpolate, int imagemask, float *decode,
		int *colorkey, fz_image *mask, int size,
		fz_image_get_pixmap_fn *get_pixmap,
		fz_image_get_size_fn *get_size,
		fz_drop_image_fn *drop)
{
	fz_image *image;
	int i;

	assert(mask == NULL || mask->mask == NULL);
	assert(size >= sizeof(fz_image));

	image = Memento_label(fz_calloc(ctx, 1, size), "fz_image");
	FZ_INIT_KEY_STORABLE(image, 1, fz_drop_image_imp);
	image->drop_image = drop;
	image->get_pixmap = get_pixmap;
	image->get_size = get_size;
	image->w = w;
	image->h = h;
	image->xres = xres;
	image->yres = yres;
	image->bpc = bpc;
	image->n = (colorspace ? fz_colorspace_n(ctx, colorspace) : 1);
	image->colorspace = fz_keep_colorspace(ctx, colorspace);
	image->invert_cmyk_jpeg = 1;
	image->interpolate = interpolate;
	image->imagemask = imagemask;
	image->use_colorkey = (colorkey != NULL);
	if (colorkey)
		memcpy(image->colorkey, colorkey, sizeof(int)*image->n*2);
	image->use_decode = 0;
	if (decode)
	{
		memcpy(image->decode, decode, sizeof(float)*image->n*2);
	}
	else
	{
		float maxval = fz_colorspace_is_indexed(ctx, colorspace) ? (1 << bpc) - 1 : 1;
		for (i = 0; i < image->n; i++)
		{
			image->decode[2*i] = 0;
			image->decode[2*i+1] = maxval;
		}
	}
	/* ICC spaces have the default decode arrays pickled into them.
	 * For most spaces this is fine, because [ 0 1 0 1 0 1 ] is
	 * idempotent. For Lab, however, we need to adjust it. */
	if (fz_colorspace_is_lab_icc(ctx, colorspace))
	{
		/* Undo the default decode array of [0 100 -128 127 -128 127] */
		image->decode[0] = image->decode[0]/100.0f;
		image->decode[1] = image->decode[1]/100.0f;
		image->decode[2] = (image->decode[2]+128)/255.0f;
		image->decode[3] = (image->decode[3]+128)/255.0f;
		image->decode[4] = (image->decode[4]+128)/255.0f;
		image->decode[5] = (image->decode[5]+128)/255.0f;
	}
	for (i = 0; i < image->n; i++)
	{
		if (image->decode[i * 2] != 0 || image->decode[i * 2 + 1] != 1)
			break;
	}
	if (i != image->n)
		image->use_decode = 1;
	image->mask = fz_keep_image(ctx, mask);

	return image;
}

static size_t
compressed_image_get_size(fz_context *ctx, fz_image *image)
{
	fz_compressed_image *im = (fz_compressed_image *)image;

	if (image == NULL)
		return 0;

	return sizeof(fz_pixmap_image) + fz_pixmap_size(ctx, im->tile) + (im->buffer && im->buffer->buffer ? im->buffer->buffer->cap : 0);
}

/*
	Create an image based on
	the data in the supplied compressed buffer.

	w,h: Width and height of the created image.

	bpc: Bits per component.

	colorspace: The colorspace (determines the number of components,
	and any color conversions required while decoding).

	xres, yres: The X and Y resolutions respectively.

	interpolate: 1 if interpolation should be used when decoding
	this image, 0 otherwise.

	imagemask: 1 if this is an imagemask (i.e. transparency bitmap mask), 0 otherwise.

	decode: NULL, or a pointer to to a decode array. The default
	decode array is [0 1] (repeated n times, for n color components).

	colorkey: NULL, or a pointer to a colorkey array. The default
	colorkey array is [0 255] (repeatd n times, for n color
	components).

	buffer: Buffer of compressed data and compression parameters.
	Ownership of this reference is passed in.

	mask: NULL, or another image to use as a mask for this one.
	A new reference is taken to this image. Supplying a masked
	image as a mask to another image is illegal!
*/
fz_image *
fz_new_image_from_compressed_buffer(fz_context *ctx, int w, int h,
	int bpc, fz_colorspace *colorspace,
	int xres, int yres, int interpolate, int imagemask, float *decode,
	int *colorkey, fz_compressed_buffer *buffer, fz_image *mask)
{
	fz_compressed_image *image;

	fz_try(ctx)
	{
		image = fz_new_derived_image(ctx, w, h, bpc,
					colorspace, xres, yres,
					interpolate, imagemask, decode,
					colorkey, mask, fz_compressed_image,
					compressed_image_get_pixmap,
					compressed_image_get_size,
					drop_compressed_image);
		image->buffer = buffer;
	}
	fz_catch(ctx)
	{
		fz_drop_compressed_buffer(ctx, buffer);
		fz_rethrow(ctx);
	}

	return &image->super;
}

/*
	Retrieve the underlying compressed
	data for an image.

	Returns a pointer to the underlying data buffer for an image,
	or NULL if this image is not based upon a compressed data
	buffer.

	This is not a reference counted structure, so no reference is
	returned. Lifespan is limited to that of the image itself.
*/
fz_compressed_buffer *fz_compressed_image_buffer(fz_context *ctx, fz_image *image)
{
	if (image == NULL || image->get_pixmap != compressed_image_get_pixmap)
		return NULL;
	return ((fz_compressed_image *)image)->buffer;
}

void fz_set_compressed_image_buffer(fz_context *ctx, fz_compressed_image *image, fz_compressed_buffer *buf)
{
	assert(image != NULL && image->super.get_pixmap == compressed_image_get_pixmap);
	((fz_compressed_image *)image)->buffer = buf; /* Note: compressed buffers are not reference counted */
}

fz_pixmap *fz_compressed_image_tile(fz_context *ctx, fz_compressed_image *image)
{
	if (image == NULL || image->super.get_pixmap != compressed_image_get_pixmap)
		return NULL;
	return ((fz_compressed_image *)image)->tile;
}

void fz_set_compressed_image_tile(fz_context *ctx, fz_compressed_image *image, fz_pixmap *pix)
{
	assert(image != NULL && image->super.get_pixmap == compressed_image_get_pixmap);
	fz_drop_pixmap(ctx, ((fz_compressed_image *)image)->tile);
	((fz_compressed_image *)image)->tile = fz_keep_pixmap(ctx, pix);
}

/*
	Retried the underlying fz_pixmap
	for an image.

	Returns a pointer to the underlying fz_pixmap for an image,
	or NULL if this image is not based upon an fz_pixmap.

	No reference is returned. Lifespan is limited to that of
	the image itself. If required, use fz_keep_pixmap to take
	a reference to keep it longer.
*/
fz_pixmap *fz_pixmap_image_tile(fz_context *ctx, fz_pixmap_image *image)
{
	if (image == NULL || image->super.get_pixmap != pixmap_image_get_pixmap)
		return NULL;
	return ((fz_pixmap_image *)image)->tile;
}

void fz_set_pixmap_image_tile(fz_context *ctx, fz_pixmap_image *image, fz_pixmap *pix)
{
	assert(image != NULL && image->super.get_pixmap == pixmap_image_get_pixmap);
	((fz_pixmap_image *)image)->tile = pix;
}

int
fz_recognize_image_format(fz_context *ctx, unsigned char p[8])
{
	if (p[0] == 'P' && p[1] >= '1' && p[1] <= '7')
		return FZ_IMAGE_PNM;
	if (p[0] == 0xff && p[1] == 0x4f)
		return FZ_IMAGE_JPX;
	if (p[0] == 0x00 && p[1] == 0x00 && p[2] == 0x00 && p[3] == 0x0c &&
			p[4] == 0x6a && p[5] == 0x50 && p[6] == 0x20 && p[7] == 0x20)
		return FZ_IMAGE_JPX;
	if (p[0] == 0xff && p[1] == 0xd8)
		return FZ_IMAGE_JPEG;
	if (p[0] == 137 && p[1] == 80 && p[2] == 78 && p[3] == 71 &&
			p[4] == 13 && p[5] == 10 && p[6] == 26 && p[7] == 10)
		return FZ_IMAGE_PNG;
	if (p[0] == 'I' && p[1] == 'I' && p[2] == 0xBC)
		return FZ_IMAGE_JXR;
	if (p[0] == 'I' && p[1] == 'I' && p[2] == 42 && p[3] == 0)
		return FZ_IMAGE_TIFF;
	if (p[0] == 'M' && p[1] == 'M' && p[2] == 0 && p[3] == 42)
		return FZ_IMAGE_TIFF;
	if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F')
		return FZ_IMAGE_GIF;
	if (p[0] == 'B' && p[1] == 'M')
		return FZ_IMAGE_BMP;
	if (p[0] == 0x97 && p[1] == 'J' && p[2] == 'B' && p[3] == '2' &&
		p[4] == '\r' && p[5] == '\n'  && p[6] == 0x1a && p[7] == '\n')
		return FZ_IMAGE_JBIG2;
	return FZ_IMAGE_UNKNOWN;
}

/*
	Create a new image from a
	buffer of data, inferring its type from the format
	of the data.
*/
fz_image *
fz_new_image_from_buffer(fz_context *ctx, fz_buffer *buffer)
{
	fz_compressed_buffer *bc;
	int w, h, xres, yres;
	fz_colorspace *cspace;
	size_t len = buffer->len;
	unsigned char *buf = buffer->data;
	fz_image *image = NULL;
	int type;

	if (len < 8)
		fz_throw(ctx, FZ_ERROR_GENERIC, "unknown image file format");

	type = fz_recognize_image_format(ctx, buf);
	switch (type)
	{
	case FZ_IMAGE_PNM:
		fz_load_pnm_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_JPX:
		fz_load_jpx_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_JPEG:
		fz_load_jpeg_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_PNG:
		fz_load_png_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_JXR:
		fz_load_jxr_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_TIFF:
		fz_load_tiff_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_GIF:
		fz_load_gif_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_BMP:
		fz_load_bmp_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	case FZ_IMAGE_JBIG2:
		fz_load_jbig2_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
		break;
	default:
		fz_throw(ctx, FZ_ERROR_GENERIC, "unknown image file format");
	}

	fz_try(ctx)
	{
		bc = fz_malloc_struct(ctx, fz_compressed_buffer);
		bc->buffer = fz_keep_buffer(ctx, buffer);
		bc->params.type = type;
		if (type == FZ_IMAGE_JPEG)
			bc->params.u.jpeg.color_transform = -1;
		image = fz_new_image_from_compressed_buffer(ctx, w, h, 8, cspace, xres, yres, 0, 0, NULL, NULL, bc, NULL);
	}
	fz_always(ctx)
		fz_drop_colorspace(ctx, cspace);
	fz_catch(ctx)
		fz_rethrow(ctx);

	return image;
}

/*
	Create a new image from the contents
	of a file, inferring its type from the format of the
	data.
*/
fz_image *
fz_new_image_from_file(fz_context *ctx, const char *path)
{
	fz_buffer *buffer;
	fz_image *image = NULL;

	buffer = fz_read_file(ctx, path);
	fz_try(ctx)
		image = fz_new_image_from_buffer(ctx, buffer);
	fz_always(ctx)
		fz_drop_buffer(ctx, buffer);
	fz_catch(ctx)
		fz_rethrow(ctx);

	return image;
}

/*
	Request the natural resolution
	of an image.

	xres, yres: Pointers to ints to be updated with the
	natural resolution of an image (or a sensible default
	if not encoded).
*/
void
fz_image_resolution(fz_image *image, int *xres, int *yres)
{
	*xres = image->xres;
	*yres = image->yres;
	if (*xres < 0 || *yres < 0 || (*xres == 0 && *yres == 0))
	{
		/* If neither xres or yres is sane, pick a sane value */
		*xres = SANE_DPI; *yres = SANE_DPI;
	}
	else if (*xres == 0)
	{
		*xres = *yres;
	}
	else if (*yres == 0)
	{
		*yres = *xres;
	}

	/* Scale xres and yres up until we get believable values */
	if (*xres < SANE_DPI || *yres < SANE_DPI || *xres > INSANE_DPI || *yres > INSANE_DPI)
	{
		if (*xres == *yres)
		{
			*xres = SANE_DPI;
			*yres = SANE_DPI;
		}
		else if (*xres < *yres)
		{
			*yres = *yres * SANE_DPI / *xres;
			*xres = SANE_DPI;
		}
		else
		{
			*xres = *xres * SANE_DPI / *yres;
			*yres = SANE_DPI;
		}
	}
}

typedef struct fz_display_list_image_s
{
	fz_image super;
	fz_matrix transform;
	fz_display_list *list;
} fz_display_list_image;

static fz_pixmap *
display_list_image_get_pixmap(fz_context *ctx, fz_image *image_, fz_irect *subarea, int w, int h, int *l2factor)
{
	fz_display_list_image *image = (fz_display_list_image *)image_;
	fz_matrix ctm;
	fz_device *dev;
	fz_pixmap *pix;

	fz_var(dev);

	if (subarea)
	{
		/* So, the whole image should be scaled to w * h, but we only want the
		 * given subarea of it. */
		int l = (subarea->x0 * w) / image->super.w;
		int t = (subarea->y0 * h) / image->super.h;
		int r = (subarea->x1 * w + image->super.w - 1) / image->super.w;
		int b = (subarea->y1 * h + image->super.h - 1) / image->super.h;

		pix = fz_new_pixmap(ctx, image->super.colorspace, r-l, b-t, NULL, 0);
		pix->x = l;
		pix->y = t;
	}
	else
	{
		pix = fz_new_pixmap(ctx, image->super.colorspace, w, h, NULL, 0);
	}

	/* If we render the display list into pix with the image matrix, we'll get a unit
	 * square result. Therefore scale by w, h. */
	ctm = fz_pre_scale(image->transform, w, h);

	fz_clear_pixmap(ctx, pix); /* clear to transparent */
	fz_try(ctx)
	{
		dev = fz_new_draw_device(ctx, ctm, pix);
		fz_run_display_list(ctx, image->list, dev, fz_identity, fz_infinite_rect, NULL);
		fz_close_device(ctx, dev);
	}
	fz_always(ctx)
		fz_drop_device(ctx, dev);
	fz_catch(ctx)
	{
		fz_drop_pixmap(ctx, pix);
		fz_rethrow(ctx);
	}

	/* Never do more subsampling, cos we've already given them the right size */
	if (l2factor)
		*l2factor = 0;

	return pix;
}

static void drop_display_list_image(fz_context *ctx, fz_image *image_)
{
	fz_display_list_image *image = (fz_display_list_image *)image_;

	if (image == NULL)
		return;
	fz_drop_display_list(ctx, image->list);
}

static size_t
display_list_image_get_size(fz_context *ctx, fz_image *image_)
{
	fz_display_list_image *image = (fz_display_list_image *)image_;

	if (image == NULL)
		return 0;

	return sizeof(fz_display_list_image) + 4096; /* FIXME */
}

/*
	Create a new image from a display list.

	w, h: The conceptual width/height of the image.

	transform: The matrix that needs to be applied to the given
	list to make it render to the unit square.

	list: The display list.
*/
fz_image *fz_new_image_from_display_list(fz_context *ctx, float w, float h, fz_display_list *list)
{
	fz_display_list_image *image;
	int iw, ih;

	iw = w * SCALABLE_IMAGE_DPI / 72;
	ih = h * SCALABLE_IMAGE_DPI / 72;

	image = fz_new_derived_image(ctx, iw, ih, 8, fz_device_rgb(ctx),
				SCALABLE_IMAGE_DPI, SCALABLE_IMAGE_DPI, 0, 0,
				NULL, NULL, NULL, fz_display_list_image,
				display_list_image_get_pixmap,
				display_list_image_get_size,
				drop_display_list_image);
	image->super.scalable = 1;
	image->transform = fz_scale(1 / w, 1 / h);
	image->list = fz_keep_display_list(ctx, list);

	return &image->super;
}
