/* code adapted from mtpaint - GPLv3 */

#ifdef HAVE_OPENJPEG

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <openjpeg.h>

#include "image.h"
#include "rwTable.h"



/* *** PREFACE ***
 * OpenJPEG 1.x is wasteful in the extreme, with memory overhead of about
 * 7 times the unpacked image size. So it can fail to handle even such
 * resolutions that fit into available memory with lots of room to spare.
 * Still, JasPer is an even worse memory hog, if a somewhat faster one.
 * Another thing - Linux builds of OpenJPEG cannot properly encode an opacity
 * channel (fixed in SVN on 06.11.09, revision 541)
 * And JP2 images with 4 channels, produced by OpenJPEG, cause JasPer
 * to die horribly - WJ */

extern void * xmalloc(size_t n);

static void stupid_callback(const char *msg, void *client_data)
{
}

/* Build bitdepth translation table */
void set_xlate(unsigned char *xlat, int bpp)
{
	int i, j, m, n = (1 << bpp) - 1;

	for (i = 0 , j = n , m = n + n; i <= n; i++ , j += 255 * 2)
		xlat[i] = j / m;
}

int
TestJP2K(char *file)
{
    unsigned char buf[8];
    FILE *fd = fopen(file, "r");
    int ret = 0;

    if (fd == NULL)
	return 0;

    if (6 == fread(buf, sizeof(char), 6, fd)) {
        if (buf[0] == 0xFF && buf[1] == 0x4F)
	    ret = 1;
	if (buf[0] == 0x00 && buf[1] == 0x00 &&
            buf[2] == 0x00 && buf[3] == 0x0c &&
            buf[4] == 0x6A && buf[5] == 0x50)
	    ret = 1;
    }
    fclose(fd);

    return ret;
}

Image *
ReadJP2K(char *file_name)
{
	opj_dparameters_t par;
	opj_dinfo_t *dinfo;
	opj_cio_t *cio = NULL;
	opj_image_t *image = NULL;
	opj_image_comp_t *comp;
	opj_event_mgr_t useless_events; // !!! Silently made mandatory in v1.2
	unsigned char xtb[256], *dest, *buf = NULL;
	FILE *fp;
        Image *outImage;
	int i, j, k, l, w, h, w0, nc, step, delta, shift, bpp;
	int *src, codec = CODEC_JP2;

	if ((fp = fopen(file_name, "rb")) == NULL) return (-1);

	/* Read in the entire file */
	fseek(fp, 0, SEEK_END);
	l = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	buf = malloc(l);
	if (!buf) goto ffail;
	i = fread(buf, 1, l, fp);
	if (i < l) goto ffail;
	fclose(fp);
	if ((buf[0] == 0xFF) && (buf[1] == 0x4F)) codec = CODEC_J2K;

	/* Decompress it */
	dinfo = opj_create_decompress(codec);
	if (!dinfo) goto lfail;
	memset(&useless_events, 0, sizeof(useless_events));
	useless_events.error_handler = useless_events.warning_handler =
		useless_events.info_handler = stupid_callback;
	opj_set_event_mgr((opj_common_ptr)dinfo, &useless_events, stderr);
	opj_set_default_decoder_parameters(&par);
	opj_setup_decoder(dinfo, &par);
	cio = opj_cio_open((opj_common_ptr)dinfo, buf, l);
	if (!cio) goto lfail;
	image = opj_decode(dinfo, cio);
	opj_cio_close(cio);
	opj_destroy_decompress(dinfo);
	free(buf);
	if (!image) goto ifail;
	
	/* Analyze what we got */
        nc = image->numcomps;
	comp = image->comps;
        if (nc < 3)
	    bpp = 1;
        else
	    bpp = 3;
        /* printf("bpp = %d comps=%d\n", image->numcomps, comp); */
	w = (comp->w + (1 << comp->factor) - 1) >> comp->factor;
	h = (comp->h + (1 << comp->factor) - 1) >> comp->factor;

	for (i = 1; i < nc; i++) /* Check if all components are the same size */
	{
		comp++;
		if ((w != (comp->w + (1 << comp->factor) - 1) >> comp->factor) ||
		    (h != (comp->h + (1 << comp->factor) - 1) >> comp->factor))
			goto ifail;
	}

        if (bpp == 1)
            outImage = ImageNewGrey(w, h);
        else
            outImage = ImageNew(w, h);
        if (!outImage) goto ifail;

	/* Unpack data */
	for (i = 0, comp = image->comps; i < nc; i++ , comp++)
	{
		if (i < bpp) /* Image */
		{
			dest = outImage->data + i;
			step = bpp;
		}
		else /* Alpha */
		{
		        if (!outImage->alpha) 
			    outImage->alpha = xmalloc(w*h);
		        dest = outImage->alpha;
			if (!dest) break; /* No alpha allocated */
			step = 1;
		}
		w0 = comp->w;
		delta = comp->sgnd ? 1 << (comp->prec - 1) : 0;
		shift = comp->prec > 8 ? comp->prec - 8 : 0;
		set_xlate(xtb, comp->prec - shift);
		for (j = 0; j < h; j++)
		{
			src = comp->data + j * w0;
			for (k = 0; k < w; k++)
			{
				*dest = xtb[(src[k] + delta) >> shift];
				dest += step;
			}
		}
	}
        opj_image_destroy(image);
        return outImage;

ifail:	opj_image_destroy(image);
	return NULL;
lfail:	opj_destroy_decompress(dinfo);
	free(buf);
	return NULL;
ffail:	free(buf);
	fclose(fp);
	return NULL;
}

int 
WriteJP2K(char *file_name, Image * outImage)
{
	opj_cparameters_t par;
	opj_cinfo_t *cinfo;
	opj_image_cmptparm_t channels[4];
	opj_cio_t *cio = NULL;
	opj_image_t *image;
	opj_event_mgr_t useless_events; // !!! Silently made mandatory in v1.2
	unsigned char *src;
	FILE *fp;
	int i, j, k, nc, step;
	int *dest, w, h, res = 1;
        int jp2k_mode, jp2k_rate;

	if ((fp = fopen(file_name, "wb")) == NULL) return 1;

	/* Create intermediate structure */
	nc = outImage->alpha ? 4 : 3;
        w = outImage->width;
        h = outImage->height;
        jp2k_mode = 1;
        jp2k_rate = 8;

	memset(channels, 0, sizeof(channels));
	for (i = 0; i < nc; i++)
	{
		channels[i].prec = channels[i].bpp = 8;
		channels[i].dx = channels[i].dy = 1;
		channels[i].w = w;
		channels[i].h = h;
	}
	image = opj_image_create(nc, channels, CLRSPC_SRGB);
	if (!image) goto ffail;
	image->x0 = image->y0 = 0;
	image->x1 = w; image->y1 = h;
        

	/* Fill it */
	k = w * h;
	for (i = 0; i < nc; i++)
	{
		if (i < 3)
		{
			src = outImage->data + i;
			step = 3;
		}
		else
		{
			src = outImage->alpha;
			step = 1;
		}
		dest = image->comps[i].data;
		for (j = 0; j < k; j++ , src += step) dest[j] = *src;
	}

	/* Compress it */
	cinfo = opj_create_compress((jp2k_mode)? CODEC_JP2 : CODEC_J2K);
	if (!cinfo) goto fail;
	memset(&useless_events, 0, sizeof(useless_events));
	useless_events.error_handler = useless_events.warning_handler =
		useless_events.info_handler = stupid_callback;
	opj_set_event_mgr((opj_common_ptr)cinfo, &useless_events, stderr);
	opj_set_default_encoder_parameters(&par);
	par.tcp_numlayers = 1;
	par.tcp_rates[0] = jp2k_rate;
	par.cp_disto_alloc = 1;
	opj_setup_encoder(cinfo, &par, image);
	cio = opj_cio_open((opj_common_ptr)cinfo, NULL, 0);
	if (!cio) goto fail;
	if (!opj_encode(cinfo, cio, image, NULL)) goto fail;

	/* Write it */
	k = cio_tell(cio);
	if (fwrite(cio->buffer, 1, k, fp) == k) res = 0;

fail:	if (cio) opj_cio_close(cio);
	opj_destroy_compress(cinfo);
	opj_image_destroy(image);
ffail:	fclose(fp);
	return (res);
}
#endif

#ifdef HAVE_JASPER

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <jasper.h>

#include "image.h"
#include "rwTable.h"

/* *** PREFACE ***
 * JasPer is QUITE a memory waster, with peak memory usage nearly TEN times the
 * unpacked image size. But what is worse, its API is 99% undocumented.
 * And to add insult to injury, it reacts to some invalid JP2 files (4-channel
 * ones written by OpenJPEG) by abort()ing, instead of returning error - WJ */

static int jasper_init;

/* Build bitdepth translation table */
void set_xlate(unsigned char *xlat, int bpp)
{
	int i, j, m, n = (1 << bpp) - 1;

	for (i = 0 , j = n , m = n + n; i <= n; i++ , j += 255 * 2)
		xlat[i] = j / m;
}

int
TestJP2K(char *file)
{
    unsigned char buf[8];
    FILE *fd = fopen(file, "r");
    int ret = 0;

    if (fd == NULL)
	return 0;

    if (6 == fread(buf, sizeof(char), 6, fd)) {
        if (buf[0] == 0xFF && buf[1] == 0x4F)
	    ret = 1;
	if (buf[0] == 0x00 && buf[1] == 0x00 &&
            buf[2] == 0x00 && buf[3] == 0x0c &&
            buf[4] == 0x6A && buf[5] == 0x50)
	    ret = 1;
    }
    fclose(fd);

    return ret;
}

Image *
ReadJP2K(char *file_name)
{
	jas_image_t *img;
	jas_stream_t *inp;
	jas_matrix_t *mx;
	jas_seqent_t *src;
        Image * outImage;
	char *fmt;
	unsigned char xtb[256], *dest;
	int nc, cspace, mode, slots[4];
	int bits, shift, delta, chan, step;
	int i, j, k, n, w, h, bpp;
        int jp2k_mode, jp2k_rate;

	/* Init the dumb library */
	if (!jasper_init) jas_init();
	jasper_init = 1;
        jp2k_mode = 1;
        jp2k_rate = 8;

	/* Open the file */
	inp = jas_stream_fopen(file_name, "rb");
	if (!inp) return NULL;
	/* Validate format */
	fmt = jas_image_fmttostr(jas_image_getfmt(inp));
	if (!fmt || strcmp(fmt, (jp2k_mode)? "jp2" : "jpc"))
		goto ffail;

	/* Decode the file into a halfbaked pile of bytes */
	img = jas_image_decode(inp, -1, NULL);
	jas_stream_close(inp);
	if (!img) goto dfail;
	/* Analyze the pile's contents */
	nc = jas_image_numcmpts(img);
	mode = jas_clrspc_fam(cspace = jas_image_clrspc(img));
	if (mode == JAS_CLRSPC_FAM_GRAY) bpp = 1;
	else if (mode == JAS_CLRSPC_FAM_RGB) bpp = 3;
	else goto ifail;
	if (bpp == 3)
	{
		slots[0] = jas_image_getcmptbytype(img,
			JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R));
		slots[1] = jas_image_getcmptbytype(img,
			JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G));
		slots[2] = jas_image_getcmptbytype(img,
			JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B));
		if ((slots[1] < 0) | (slots[2] < 0)) goto ifail;
	}
	else
	{
		slots[0] = jas_image_getcmptbytype(img,
			JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_GRAY_Y));
	}
	if (slots[0] < 0) goto ifail;
	if (nc > bpp)
	{
		slots[bpp] = jas_image_getcmptbytype(img, JAS_IMAGE_CT_OPACITY);
/* !!! JasPer has a bug - it doesn't write out component definitions if color
 * channels are in natural order, thus losing the types of any extra components.
 * (See where variable "needcdef" in src/libjasper/jp2/jp2_enc.c gets unset.)
 * Then on reading, type will be replaced by component's ordinal number - WJ */
		if (slots[bpp] < 0) slots[bpp] = jas_image_getcmptbytype(img, bpp);
		/* Use an unlabeled extra component for alpha if no labeled one */
		if (slots[bpp] < 0)
			slots[bpp] = jas_image_getcmptbytype(img, JAS_IMAGE_CT_UNKNOWN);
		nc = bpp + (slots[bpp] >= 0); // Ignore extra channels if no alpha
	}
	w = jas_image_cmptwidth(img, slots[0]);
	h = jas_image_cmptheight(img, slots[0]);
	for (i = 1; i < nc; i++) /* Check if all components are the same size */
	{
		if ((jas_image_cmptwidth(img, slots[i]) != w) ||
			(jas_image_cmptheight(img, slots[i]) != h)) goto ifail;
	}

	/* Allocate "matrix" */
	mx = jas_matrix_create(1, w);
	if (!mx) goto ifail;
	/* Allocate image */
        if (bpp == 1)
            outImage = ImageNewGrey(w, h);
        else
            outImage = ImageNew(w, h);        
        if (!outImage) goto mfail;

	if (nc > bpp) outImage->alpha = xmalloc(w*h);
#if U_LCMS
	/* JasPer implements CMS internally, but without lcms, it makes no sense
	 * to provide all the interface stuff for this one rare format - WJ */
	while ((bpp == 3) && (cspace != JAS_CLRSPC_SRGB))
	{
		jas_cmprof_t *prof;
		jas_image_t *timg;

		prof = jas_cmprof_createfromclrspc(JAS_CLRSPC_SRGB);
		if (!prof) break;
		timg = jas_image_chclrspc(img, prof, JAS_CMXFORM_INTENT_PER);
		jas_cmprof_destroy(prof);
		if (!timg) break;
		jas_image_destroy(img);
		img = timg;
		break;
	}
#endif

	/* Unravel the ugly thing into proper format */
	for (i = n = 0; i < nc; i++)
	{
		if (i < bpp) /* Image */
		{
			dest = outImage->data + i;
			step = bpp;
		}
		else /* Alpha */
		{
			dest = outImage->alpha;
			step = 1;
		}
		chan = slots[i];
		bits = jas_image_cmptprec(img, chan);
		delta = jas_image_cmptsgnd(img, chan) ? 1 << (bits - 1) : 0;
		shift = bits > 8 ? bits - 8 : 0;
		set_xlate(xtb, bits - shift);
		for (j = 0; j < h; j++ , n++)
		{
			jas_image_readcmpt(img, chan, 0, j, w, 1, mx);
			src = jas_matrix_getref(mx, 0, 0);
			for (k = 0; k < w; k++)
			{
				*dest = xtb[(unsigned)(src[k] + delta) >> shift];
				dest += step;
			}
		}
	}
        jas_image_destroy(img);
        return outImage;

mfail:	jas_matrix_destroy(mx);
ifail:	jas_image_destroy(img);
dfail:	return NULL;
ffail:	jas_stream_close(inp);
	return NULL;
}

int 
WriteJP2K(char *file_name, Image * outImage)
{
	static const jas_image_cmpttype_t chans[4] = {
		JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_R),
		JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_G),
		JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_RGB_B),
		JAS_IMAGE_CT_OPACITY };
	jas_image_cmptparm_t cp[4];
	jas_image_t *img;
	jas_stream_t *outp;
	jas_matrix_t *mx;
	jas_seqent_t *dest;
	char buf[256], *opts = NULL;
	unsigned char *src;
	int w , h, res = 1;
	int i, j, k, n, nc, step, bpp = 3;
        int jp2k_mode, jp2k_rate;

	/* Init the dumb library */
	if (!jasper_init) jas_init();
	jasper_init = 1;
        w = outImage->width;
        h = outImage->height;
        jp2k_mode = 1;
        jp2k_rate = 8;

	/* Open the file */
	outp = jas_stream_fopen(file_name, "wb");
	if (!outp) return 1;
	/* Setup component parameters */
	memset(cp, 0, sizeof(cp)); // Zero out all that needs zeroing
	cp[0].hstep = cp[0].vstep = 1;
	cp[0].width = w; cp[0].height = h;
	cp[0].prec = 8;
	cp[3] = cp[2] = cp[1] = cp[0];
	/* Create image structure */
	nc = (outImage->alpha)? 4 : 3;
	img = jas_image_create(nc, cp, JAS_CLRSPC_SRGB);
	if (!img) goto fail;
	/* Allocate "matrix" */
	mx = jas_matrix_create(1, w);
	if (!mx) goto fail2;

	/* Fill image structure */
	for (i = n = 0; i < nc; i++)
	{
	/* !!! The only workaround for JasPer losing extra components' types on
	 * write is to reorder the RGB components - but then, dumb readers, such
	 * as ones in Mozilla and GTK+, would read them in wrong order - WJ */
		jas_image_setcmpttype(img, i, chans[i]);
		if (i < 3) /* Image */
		{
			src = outImage->data + i;
			step = bpp;
		}
		else /* Alpha */
		{
			src = outImage->alpha;
			step = 1;
		}
		for (j = 0; j < h; j++ , n++)
		{
			dest = jas_matrix_getref(mx, 0, 0);
			for (k = 0; k < w; k++)
			{
				dest[k] = *src;
				src += step;
			}
			jas_image_writecmpt(img, i, 0, j, w, 1, mx);
		}
	}

	/* Compress it */
	if (jp2k_rate) // Lossless if NO "rate" option passed
		sprintf(opts = buf, "rate=%g", 1.0 / jp2k_rate);
	if (!jas_image_encode(img, outp, jas_image_strtofmt(
		jp2k_mode ? "jp2" : "jpc"), opts)) res = 0;
	jas_stream_flush(outp);

fail3:	jas_matrix_destroy(mx);
fail2:	jas_image_destroy(img);
fail:	jas_stream_close(outp);
	return (res);
}
#endif

