#include "mupdf/fitz.h"
#include "fitz-imp.h"

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

#if FZ_ENABLE_JPX

typedef struct fz_jpxd_s fz_jpxd;
typedef struct stream_block_s stream_block;

static void
jpx_ycc_to_rgb(fz_context *ctx, fz_pixmap *pix, int cbsign, int crsign)
{
	int w = pix->w;
	int h = pix->h;
	int stride = pix->stride;
	int x, y;

	for (y = 0; y < h; y++)
	{
		unsigned char * row = &pix->samples[stride * y];
		for (x = 0; x < w; x++)
		{
			int ycc[3];
			ycc[0] = row[x * 3 + 0];
			ycc[1] = row[x * 3 + 1];
			ycc[2] = row[x * 3 + 2];

			/* consciously skip Y */
			if (cbsign)
				ycc[1] -= 128;
			if (crsign)
				ycc[2] -= 128;

			row[x * 3 + 0] = fz_clampi(ycc[0] + 1.402f * ycc[2], 0, 255);
			row[x * 3 + 1] = fz_clampi(ycc[0] - 0.34413f * ycc[1] - 0.71414f * ycc[2], 0, 255);
			row[x * 3 + 2] = fz_clampi(ycc[0] + 1.772f * ycc[1], 0, 255);
		}
	}
}

#ifdef HAVE_LURATECH

#include <lwf_jp2.h>

#define MAX_COLORS 4
#define MAX_ALPHAS 1
#define MAX_COMPONENTS (MAX_COLORS + MAX_ALPHAS)

#define HAS_PALETTE(cs) ( \
	(cs) == cJP2_Colorspace_Palette_Gray || \
	(cs) == cJP2_Colorspace_Palette_RGBa || \
	(cs) == cJP2_Colorspace_Palette_RGB_YCCa || \
	(cs) == cJP2_Colorspace_Palette_CIE_LABa || \
	(cs) == cJP2_Colorspace_Palette_ICCa || \
	(cs) == cJP2_Colorspace_Palette_CMYKa)

struct fz_jpxd_s
{
	fz_pixmap *pix;
	JP2_Palette_Params *palette;
	JP2_Property_Value width;
	JP2_Property_Value height;
	fz_colorspace *cs;
	int expand_indexed;
	unsigned long xres;
	unsigned long yres;
	JP2_Property_Value hstep[MAX_COMPONENTS];
	JP2_Property_Value vstep[MAX_COMPONENTS];
	JP2_Property_Value bpss[MAX_COMPONENTS];
	JP2_Property_Value signs[MAX_COMPONENTS];
};

struct stream_block_s
{
	const unsigned char *data;
	size_t size;
};

static void * JP2_Callback_Conv
jpx_alloc(long size, JP2_Callback_Param param)
{
	fz_context *ctx = (fz_context *) param;
	return fz_malloc(ctx, size);
}

static JP2_Error JP2_Callback_Conv
jpx_free(void *ptr, JP2_Callback_Param param)
{
	fz_context *ctx = (fz_context *) param;
	fz_free(ctx, ptr);
	return cJP2_Error_OK;
}

static unsigned long JP2_Callback_Conv
jpx_read(unsigned char *pucData,
		unsigned long ulPos, unsigned long ulSize,
		JP2_Callback_Param param)
{
	stream_block *sb = (stream_block *) param;

	if (ulPos >= sb->size)
		return 0;

	ulSize = (unsigned long)fz_minz(ulSize, sb->size - ulPos);
	memcpy(pucData, &sb->data[ulPos], ulSize);
	return ulSize;
}

static JP2_Error JP2_Callback_Conv
jpx_write(unsigned char * pucData, short sComponent, unsigned long ulRow,
		unsigned long ulStart, unsigned long ulNum, JP2_Callback_Param param)
{
	fz_jpxd *state = (fz_jpxd *) param;
	JP2_Property_Value hstep, vstep;
	unsigned char *row;
	int w, h, n, entries, expand;
	JP2_Property_Value x, y, i, bps, sign;
	JP2_Property_Value k;
	unsigned long **palette;

	w = state->pix->w;
	h = state->pix->h;
	n = state->pix->n;

	if (ulRow >= (unsigned long)h || ulStart >= (unsigned long)w || sComponent >= n)
		return cJP2_Error_OK;

	ulNum = fz_mini(ulNum, w - ulStart);
	hstep = state->hstep[sComponent];
	vstep = state->vstep[sComponent];
	bps = state->bpss[sComponent];
	sign = state->signs[sComponent];

	palette = state->palette ? state->palette->ppulPalette : NULL;
	entries = state->palette ? state->palette->ulEntries : 1;
	expand = state->expand_indexed;

	row = state->pix->samples +
		state->pix->stride * ulRow * vstep +
		n * ulStart * hstep +
		sComponent;

	for (y = 0; ulRow * vstep + y < (JP2_Property_Value)h && y < vstep; y++)
	{
		unsigned char *p = row;

		for (i = 0; i < ulNum; i++)
		{
			for (x = 0; (ulStart + i) * hstep + x < (JP2_Property_Value)w && x < hstep; x++)
			{
				if (palette)
				{
						unsigned char v = fz_clampi(pucData[i], 0, entries - 1);

						if (expand)
						{
							for (k = 0; k < n; k++)
								p[k] = palette[k][v];
						}
						else
							*p = v;
				}
				else
				{
					if (bps > 8)
					{
						unsigned int v = (pucData[2 * i + 1] << 8) | pucData[2 * i + 0];
						v &= (1 << bps) - 1;
						v -= sign;
						*p = v >> (bps - 8);
					}
					else if (bps == 8)
					{
						unsigned int v = pucData[i];
						v &= (1 << bps) - 1;
						v -= sign;
						*p = v;
					}
					else
					{
						unsigned int v = pucData[i];
						v &= (1 << bps) - 1;
						v -= sign;
						*p = v << (8 - bps);
					}
				}

				p += n;
			}
		}

		row += state->pix->stride;
	}

	return cJP2_Error_OK;
}

static fz_pixmap *
jpx_read_image(fz_context *ctx, fz_jpxd *state, const unsigned char *data, size_t size, fz_colorspace *defcs, int onlymeta)
{
	JP2_Decomp_Handle doc;
	JP2_Channel_Def_Params *chans = NULL;
	JP2_Error err;
	int colors, alphas, prealphas;
	JP2_Property_Value k;
	JP2_Colorspace colorspace;
	JP2_Property_Value nchans;
	JP2_Property_Value widths[MAX_COMPONENTS];
	JP2_Property_Value heights[MAX_COMPONENTS];
	stream_block sb;

	memset(state, 0x00, sizeof (fz_jpxd));

	sb.data = data;
	sb.size = size;

	fz_try(ctx)
	{
		err = JP2_Decompress_Start(&doc,
				jpx_alloc, (JP2_Callback_Param) ctx,
				jpx_free, (JP2_Callback_Param) ctx,
				jpx_read, (JP2_Callback_Param) &sb);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot open image: %d", (int) err);

#if defined(JP2_LICENSE_NUM_1) && defined(JP2_LICENSE_NUM_2)
		err = JP2_Document_SetLicense(doc, JP2_LICENSE_NUM_1, JP2_LICENSE_NUM_2);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot set license: %d", (int) err);
#endif

		err = JP2_Decompress_GetProp(doc, cJP2_Prop_Extern_Colorspace, (unsigned long *) &colorspace, -1, -1);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get colorspace: %d", (int) err);

		err = JP2_Decompress_GetChannelDefs(doc, &chans, &nchans);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get channel definitions: %d", (int) err);

		colors = 0;
		alphas = 0;
		prealphas = 0;
		for (k = 0; k < nchans; k++)
		{
			switch (chans[k].ulType)
			{
			case cJP2_Channel_Type_Color: colors++; break;
			case cJP2_Channel_Type_Opacity: alphas++; break;
			case cJP2_Channel_Type_Opacity_Pre: prealphas++; break;
			}
		}

		if (prealphas> 0)
			alphas = prealphas;
		colors = fz_clampi(colors, 0, MAX_COLORS);
		alphas = fz_clampi(alphas, 0, MAX_ALPHAS);

		nchans = colors + alphas;

		if (HAS_PALETTE(colorspace))
		{
			err = JP2_Decompress_GetProp(doc, cJP2_Prop_Width, &state->width, -1, 0);
			if (err != cJP2_Error_OK)
				fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get width for palette indicies: %d", (int) err);
			err = JP2_Decompress_GetProp(doc, cJP2_Prop_Height, &state->height, -1, 0);
			if (err != cJP2_Error_OK)
				fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get height for palette indicies: %d", (int) err);

			for (k = 0; k < nchans; k++)
			{
				widths[k] = state->width;
				heights[k] = state->height;
			}
		}
		else
		{
			for (k = 0; k < nchans; k++)
			{
				err = JP2_Decompress_GetProp(doc, cJP2_Prop_Width, &widths[k], -1, k);
				if (err != cJP2_Error_OK)
					fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get width for component %d: %d", (int) k, (int) err);
				err = JP2_Decompress_GetProp(doc, cJP2_Prop_Height, &heights[k], -1, k);
				if (err != cJP2_Error_OK)
					fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get height for component %d: %d", (int) k, (int) err);

				state->width = fz_maxi(state->width, widths[k]);
				state->height = fz_maxi(state->height, heights[k]);
			}
		}

		err = JP2_Decompress_GetResolution(doc, &state->yres, &state->xres, NULL,
				cJP2_Resolution_Dots_Per_Inch, cJP2_Resolution_Capture);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get resolution: %d", (int) err);

		if (state->xres == 0 || state->yres == 0)
			state->xres = state->yres = 72;

		state->cs = NULL;

		if (defcs)
		{
			if (defcs->n == nchans)
				state->cs = fz_keep_colorspace(ctx, defcs);
			else
				fz_warn(ctx, "jpx file (%lu) and dict colorspace (%d, %s) do not match", nchans, defcs->n, defcs->name);
		}

#if FZ_ENABLE_ICC
		if (!state->cs && colorspace == cJP2_Colorspace_Palette_ICCa)
		{
			unsigned char *iccprofile = NULL;
			unsigned long size = 0;
			fz_buffer *cbuf = NULL;
			fz_var(cbuf);

			err = JP2_Decompress_GetICC(doc, &iccprofile, &size);
			if (err != cJP2_Error_OK)
				fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get ICC color profile: %d", (int) err);

			fz_try(ctx)
			{
				cbuf = fz_new_buffer_from_copied_data(ctx, iccprofile, size);
				state->cs = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_NONE, 0, NULL, cbuf);
			}
			fz_always(ctx)
				fz_drop_buffer(ctx, cbuf);
			fz_catch(ctx)
				fz_warn(ctx, "ignoring embedded ICC profile in JPX");

			if (state->cs && state->cs->n != nchans)
			{
				fz_warn(ctx, "invalid number of components in ICC profile, ignoring ICC profile in JPX");
				fz_drop_colorspace(ctx, state->cs);
				state->cs = NULL;
			}
		}
#endif

		if (!state->cs)
		{
			switch (colors)
			{
			case 4:
				state->cs = fz_keep_colorspace(ctx, fz_device_cmyk(ctx));
				break;
			case 3:
				if (colorspace == cJP2_Colorspace_CIE_LABa)
					state->cs = fz_keep_colorspace(ctx, fz_device_lab(ctx));
				else
					state->cs = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
				break;
			case 1:
				state->cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
				break;
			case 0:
				if (alphas == 1)
				{
					/* alpha only images are rendered as grayscale */
					state->cs = fz_keep_colorspace(ctx, fz_device_gray(ctx));
					colors = 1;
					alphas = 0;
					break;
				}
				/* fallthrough */
			default:
				fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported number of components: %lu", nchans);
			}
		}
	}
	fz_catch(ctx)
	{
		fz_drop_colorspace(ctx, state->cs);
		JP2_Decompress_End(doc);
		fz_rethrow(ctx);
	}

	if (onlymeta)
	{
		JP2_Decompress_End(doc);
		return NULL;
	}

	fz_try(ctx)
	{
		state->pix = fz_new_pixmap(ctx, state->cs, state->width, state->height, NULL, alphas);
		fz_clear_pixmap_with_value(ctx, state->pix, 0);

		if (HAS_PALETTE(colorspace))
		{
			if (!fz_colorspace_is_indexed(ctx, state->cs))
				state->expand_indexed = 1;

			err = JP2_Decompress_GetPalette(doc, &state->palette);
			if (err != cJP2_Error_OK)
				fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get indexed palette: %d", (int) err);

			/* no available sample file */
			for (k = 0; k < state->palette->ulChannels; k++)
				if (state->palette->pucSignedSample[k])
					fz_throw(ctx, FZ_ERROR_GENERIC, "signed palette components not yet supported");
		}

		for (k = 0; k < nchans; k++)
		{
			state->hstep[k] = (state->width + (widths[k] - 1)) / widths[k];
			state->vstep[k] = (state->height + (heights[k] - 1)) / heights[k];

			if (HAS_PALETTE(colorspace))
			{
				state->bpss[k] = state->palette->pucBitsPerSample[k];
				state->signs[k] = state->palette->pucSignedSample[k];
			}
			else
			{
				err = JP2_Decompress_GetProp(doc, cJP2_Prop_Bits_Per_Sample, &state->bpss[k], -1, k);
				if (err != cJP2_Error_OK)
					fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get bits per sample for component %d: %d", (int) k, (int) err);
				err = JP2_Decompress_GetProp(doc, cJP2_Prop_Signed_Samples, &state->signs[k], -1, k);
				if (err != cJP2_Error_OK)
					fz_throw(ctx, FZ_ERROR_GENERIC, "cannot get signed for component %d: %d", (int) k, (int) err);
			}
			if (state->signs[k])
				state->signs[k] = 1 << (state->bpss[k] - 1);
		}

		err = JP2_Decompress_SetProp(doc, cJP2_Prop_Output_Parameter, (JP2_Property_Value) state);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot set write callback userdata: %d", (int) err);
		err = JP2_Decompress_SetProp(doc, cJP2_Prop_Output_Function, (JP2_Property_Value) jpx_write);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot set write callback: %d", (int) err);

		err = JP2_Decompress_Image(doc);
		if (err != cJP2_Error_OK)
			fz_throw(ctx, FZ_ERROR_GENERIC, "cannot decode image: %d", (int) err);

		if (colorspace == cJP2_Colorspace_RGB_YCCa)
			jpx_ycc_to_rgb(ctx, state->pix, !state->signs[1], !state->signs[2]);

		if (state->pix->alpha && ! (HAS_PALETTE(colorspace) && !state->expand_indexed))
		{
			if (alphas > 0 && prealphas == 0)
				fz_premultiply_pixmap(ctx, state->pix);
		}
	}
	fz_always(ctx)
	{
		fz_drop_colorspace(ctx, state->cs);
		JP2_Decompress_End(doc);
	}
	fz_catch(ctx)
	{
		fz_drop_pixmap(ctx, state->pix);
		fz_rethrow(ctx);
	}

	return state->pix;
}

fz_pixmap *
fz_load_jpx(fz_context *ctx, const unsigned char *data, size_t size, fz_colorspace *defcs)
{
	fz_jpxd state = { 0 };

	return jpx_read_image(ctx, &state, data, size, defcs, 0);
}

void
fz_load_jpx_info(fz_context *ctx, const unsigned char *data, size_t size, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
{
	fz_jpxd state = { 0 };

	jpx_read_image(ctx, &state, data, size, NULL, 1);

	*cspacep = state.cs;
	*wp = state.width;
	*hp = state.height;
	*xresp = state.xres;
	*yresp = state.yres;
}

#else /* HAVE_LURATECH */

#include <openjpeg.h>

struct fz_jpxd_s
{
	int width;
	int height;
	fz_colorspace *cs;
	int xres;
	int yres;
};

struct stream_block_s
{
	const unsigned char *data;
	OPJ_SIZE_T size;
	OPJ_SIZE_T pos;
};

/* OpenJPEG does not provide a safe mechanism to intercept
 * allocations. In the latest version all allocations go
 * though opj_malloc etc, but no context is passed around.
 *
 * In order to ensure that allocations throughout mupdf
 * are done consistently, we implement opj_malloc etc as
 * functions that call down to fz_malloc etc. These
 * require context variables, so we lock and unlock around
 * calls to openjpeg. Any attempt to call through
 * without setting these will be detected.
 *
 * It is therefore vital that any fz_lock/fz_unlock
 * handlers are shared between all the fz_contexts in
 * use at a time.
 */

/* Potentially we can write different versions
 * of get_context and set_context for different
 * threading systems.
 */

static fz_context *opj_secret = NULL;

static void set_opj_context(fz_context *ctx)
{
	opj_secret = ctx;
}

static fz_context *get_opj_context(void)
{
	return opj_secret;
}

void opj_lock(fz_context *ctx)
{
	fz_lock(ctx, FZ_LOCK_FREETYPE);

	set_opj_context(ctx);
}

void opj_unlock(fz_context *ctx)
{
	set_opj_context(NULL);

	fz_unlock(ctx, FZ_LOCK_FREETYPE);
}

void *opj_malloc(size_t size)
{
	fz_context *ctx = get_opj_context();

	assert(ctx != NULL);

	return fz_malloc_no_throw(ctx, size);
}

void *opj_calloc(size_t n, size_t size)
{
	fz_context *ctx = get_opj_context();

	assert(ctx != NULL);

	return fz_calloc_no_throw(ctx, n, size);
}

void *opj_realloc(void *ptr, size_t size)
{
	fz_context *ctx = get_opj_context();

	assert(ctx != NULL);

	return fz_realloc_no_throw(ctx, ptr, size);
}

void opj_free(void *ptr)
{
	fz_context *ctx = get_opj_context();

	assert(ctx != NULL);

	fz_free(ctx, ptr);
}

static void * opj_aligned_malloc_n(size_t alignment, size_t size)
{
	uint8_t *ptr;
	int off;

	if (size == 0)
		return NULL;

	size += alignment + sizeof(uint8_t);
	ptr = opj_malloc(size);
	if (ptr == NULL)
		return NULL;
	off = alignment-(((int)(intptr_t)ptr) & (alignment - 1));
	ptr[off-1] = off;
	return ptr + off;
}

void * opj_aligned_malloc(size_t size)
{
	return opj_aligned_malloc_n(16, size);
}

void * opj_aligned_32_malloc(size_t size)
{
	return opj_aligned_malloc_n(32, size);
}

void opj_aligned_free(void* ptr_)
{
	uint8_t *ptr = (uint8_t *)ptr_;
	uint8_t off;
	if (ptr == NULL)
		return;

	off = ptr[-1];
	opj_free((void *)(((unsigned char *)ptr) - off));
}

#if 0
/* UNUSED currently, and moderately tricky, so deferred until required */
void * opj_aligned_realloc(void *ptr, size_t size)
{
	return opj_realloc(ptr, size);
}
#endif

static void fz_opj_error_callback(const char *msg, void *client_data)
{
	fz_context *ctx = (fz_context *)client_data;
	char buf[200];
	int n;
	fz_strlcpy(buf, msg, sizeof buf);
	n = strlen(buf);
	if (buf[n-1] == '\n')
		buf[n-1] = 0;
	fz_warn(ctx, "openjpeg error: %s", buf);
}

static void fz_opj_warning_callback(const char *msg, void *client_data)
{
	fz_context *ctx = (fz_context *)client_data;
	char buf[200];
	int n;
	fz_strlcpy(buf, msg, sizeof buf);
	n = strlen(buf);
	if (buf[n-1] == '\n')
		buf[n-1] = 0;
	fz_warn(ctx, "openjpeg warning: %s", buf);
}

static void fz_opj_info_callback(const char *msg, void *client_data)
{
	/* fz_warn("openjpeg info: %s", msg); */
}

static OPJ_SIZE_T fz_opj_stream_read(void * p_buffer, OPJ_SIZE_T p_nb_bytes, void * p_user_data)
{
	stream_block *sb = (stream_block *)p_user_data;
	OPJ_SIZE_T len;

	len = sb->size - sb->pos;
	if (len == 0)
		return (OPJ_SIZE_T)-1; /* End of file! */
	if (len > p_nb_bytes)
		len = p_nb_bytes;
	memcpy(p_buffer, sb->data + sb->pos, len);
	sb->pos += len;
	return len;
}

static OPJ_OFF_T fz_opj_stream_skip(OPJ_OFF_T skip, void * p_user_data)
{
	stream_block *sb = (stream_block *)p_user_data;

	if (skip > (OPJ_OFF_T)(sb->size - sb->pos))
		skip = (OPJ_OFF_T)(sb->size - sb->pos);
	sb->pos += skip;
	return sb->pos;
}

static OPJ_BOOL fz_opj_stream_seek(OPJ_OFF_T seek_pos, void * p_user_data)
{
	stream_block *sb = (stream_block *)p_user_data;

	if (seek_pos > (OPJ_OFF_T)sb->size)
		return OPJ_FALSE;
	sb->pos = seek_pos;
	return OPJ_TRUE;
}

static fz_pixmap *
jpx_read_image(fz_context *ctx, fz_jpxd *state, const unsigned char *data, size_t size, fz_colorspace *defcs, int onlymeta)
{
	fz_pixmap *img = NULL;
	opj_dparameters_t params;
	opj_codec_t *codec;
	opj_image_t *jpx;
	opj_stream_t *stream;
	OPJ_CODEC_FORMAT format;
	int a, n, w, h;
	int x, y, k;
	stream_block sb;
	OPJ_UINT32 i;

	fz_var(img);

	if (size < 2)
		fz_throw(ctx, FZ_ERROR_GENERIC, "not enough data to determine image format");

	/* Check for SOC marker -- if found we have a bare J2K stream */
	if (data[0] == 0xFF && data[1] == 0x4F)
		format = OPJ_CODEC_J2K;
	else
		format = OPJ_CODEC_JP2;

	opj_set_default_decoder_parameters(&params);
	if (fz_colorspace_is_indexed(ctx, defcs))
		params.flags |= OPJ_DPARAMETERS_IGNORE_PCLR_CMAP_CDEF_FLAG;

	codec = opj_create_decompress(format);
	opj_set_info_handler(codec, fz_opj_info_callback, ctx);
	opj_set_warning_handler(codec, fz_opj_warning_callback, ctx);
	opj_set_error_handler(codec, fz_opj_error_callback, ctx);
	if (!opj_setup_decoder(codec, &params))
	{
		opj_destroy_codec(codec);
		fz_throw(ctx, FZ_ERROR_GENERIC, "j2k decode failed");
	}

	stream = opj_stream_default_create(OPJ_TRUE);
	sb.data = data;
	sb.pos = 0;
	sb.size = size;

	opj_stream_set_read_function(stream, fz_opj_stream_read);
	opj_stream_set_skip_function(stream, fz_opj_stream_skip);
	opj_stream_set_seek_function(stream, fz_opj_stream_seek);
	opj_stream_set_user_data(stream, &sb, NULL);
	/* Set the length to avoid an assert */
	opj_stream_set_user_data_length(stream, size);

	if (!opj_read_header(stream, codec, &jpx))
	{
		opj_stream_destroy(stream);
		opj_destroy_codec(codec);
		fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to read JPX header");
	}

	if (!opj_decode(codec, stream, jpx))
	{
		opj_stream_destroy(stream);
		opj_destroy_codec(codec);
		opj_image_destroy(jpx);
		fz_throw(ctx, FZ_ERROR_GENERIC, "Failed to decode JPX image");
	}

	opj_stream_destroy(stream);
	opj_destroy_codec(codec);

	/* jpx should never be NULL here, but check anyway */
	if (!jpx)
		fz_throw(ctx, FZ_ERROR_GENERIC, "opj_decode failed");

	/* Count number of alpha and color channels */
	n = a = 0;
	for (i = 0; i < jpx->numcomps; ++i)
	{
		if (jpx->comps[i].alpha)
			++a;
		else
			++n;
	}

	for (k = 1; k < n + a; k++)
	{
		if (!jpx->comps[k].data)
		{
			opj_image_destroy(jpx);
			fz_throw(ctx, FZ_ERROR_GENERIC, "image components are missing data");
		}
	}

	state->width = w = jpx->x1 - jpx->x0;
	state->height = h = jpx->y1 - jpx->y0;
	state->xres = 72; /* openjpeg does not read the JPEG 2000 resc box */
	state->yres = 72; /* openjpeg does not read the JPEG 2000 resc box */

	state->cs = NULL;

	if (defcs)
	{
		if (defcs->n == n)
			state->cs = fz_keep_colorspace(ctx, defcs);
		else
			fz_warn(ctx, "jpx file and dict colorspace do not match");
	}

#if FZ_ENABLE_ICC
	if (!state->cs && jpx->icc_profile_buf)
	{
		fz_buffer *cbuf = NULL;
		fz_var(cbuf);

		fz_try(ctx)
		{
			cbuf = fz_new_buffer_from_copied_data(ctx, jpx->icc_profile_buf, jpx->icc_profile_len);
			state->cs = fz_new_icc_colorspace(ctx, FZ_COLORSPACE_NONE, 0, NULL, cbuf);
		}
		fz_always(ctx)
			fz_drop_buffer(ctx, cbuf);
		fz_catch(ctx)
			fz_warn(ctx, "ignoring embedded ICC profile in JPX");

		if (state->cs && state->cs->n != n)
		{
			fz_warn(ctx, "invalid number of components in ICC profile, ignoring ICC profile in JPX");
			fz_drop_colorspace(ctx, state->cs);
			state->cs = NULL;
		}
	}
#endif

	if (!state->cs)
	{
		switch (n)
		{
		case 1: state->cs = fz_keep_colorspace(ctx, fz_device_gray(ctx)); break;
		case 3: state->cs = fz_keep_colorspace(ctx, fz_device_rgb(ctx)); break;
		case 4: state->cs = fz_keep_colorspace(ctx, fz_device_cmyk(ctx)); break;
		default:
			{
				opj_image_destroy(jpx);
				fz_throw(ctx, FZ_ERROR_GENERIC, "unsupported number of components: %d", n);
			}
		}
	}

	if (onlymeta)
	{
		opj_image_destroy(jpx);
		return NULL;
	}

	fz_try(ctx)
	{
		unsigned char *samples;
		int stride, comps;

		a = !!a; /* ignore any superfluous alpha channels */
		img = fz_new_pixmap(ctx, state->cs, w, h, NULL, a);
		stride = fz_pixmap_stride(ctx, img);
		comps = fz_pixmap_components(ctx, img);
		samples = fz_pixmap_samples(ctx, img);

		fz_clear_pixmap_with_value(ctx, img, 0);

		for (k = 0; k < comps; k++)
		{
			opj_image_comp_t *comp = &(jpx->comps[k]);
			int oy = comp->y0 * comp->dy - jpx->y0;
			int ox = comp->x0 * comp->dx - jpx->x0;

			if (comp->data == NULL)
				fz_throw(ctx, FZ_ERROR_GENERIC, "No data for JP2 image component %d", k);

			for (y = 0; y < comp->h; y++)
			{
				for (x = 0; x < comp->w; x++)
				{
					OPJ_INT32 v;
					int dx;
					int dy;

					v = comp->data[y * comp->w + x];

					if (comp->sgnd)
						v = v + (1 << (comp->prec - 1));
					if (comp->prec > 8)
						v = v >> (comp->prec - 8);
					else if (comp->prec < 8)
						v = v << (8 - comp->prec);

					for (dy = 0; dy < comp->dy; dy++)
					{
						for (dx = 0; dx < comp->dx; dx++)
						{
							int xx = ox + x * comp->dx + dx;
							int yy = oy + y * comp->dy + dy;

							if (xx < w && yy < h)
								samples[yy * stride + xx * comps + k] = v;
						}
					}
				}
			}
		}

		if (jpx->color_space == OPJ_CLRSPC_SYCC && n == 3 && a == 0)
			jpx_ycc_to_rgb(ctx, img, 1, 1);
		if (a)
			fz_premultiply_pixmap(ctx, img);
	}
	fz_always(ctx)
	{
		fz_drop_colorspace(ctx, state->cs);
		opj_image_destroy(jpx);
	}
	fz_catch(ctx)
	{
		fz_drop_pixmap(ctx, img);
		fz_rethrow(ctx);
	}

	return img;
}

fz_pixmap *
fz_load_jpx(fz_context *ctx, const unsigned char *data, size_t size, fz_colorspace *defcs)
{
	fz_jpxd state = { 0 };
	fz_pixmap *pix = NULL;

	fz_try(ctx)
	{
		opj_lock(ctx);
		pix = jpx_read_image(ctx, &state, data, size, defcs, 0);
	}
	fz_always(ctx)
		opj_unlock(ctx);
	fz_catch(ctx)
		fz_rethrow(ctx);

	return pix;
}

void
fz_load_jpx_info(fz_context *ctx, const unsigned char *data, size_t size, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
{
	fz_jpxd state = { 0 };

	fz_try(ctx)
	{
		opj_lock(ctx);
		jpx_read_image(ctx, &state, data, size, NULL, 1);
	}
	fz_always(ctx)
		opj_unlock(ctx);
	fz_catch(ctx)
		fz_rethrow(ctx);

	*cspacep = state.cs;
	*wp = state.width;
	*hp = state.height;
	*xresp = state.xres;
	*yresp = state.yres;
}

#endif /* HAVE_LURATECH */

#else /* FZ_ENABLE_JPX */

fz_pixmap *
fz_load_jpx(fz_context *ctx, const unsigned char *data, size_t size, fz_colorspace *defcs)
{
	fz_throw(ctx, FZ_ERROR_GENERIC, "JPX support disabled");
}

void
fz_load_jpx_info(fz_context *ctx, const unsigned char *data, size_t size, int *wp, int *hp, int *xresp, int *yresp, fz_colorspace **cspacep)
{
	fz_throw(ctx, FZ_ERROR_GENERIC, "JPX support disabled");
}

#endif
