/*
 * imgjp2.c --
 *
 *	This file contains a Tk photo image handler for the
 *	JPEG2000 (.jp2) format dealt with by the openjp2 library.
 *
 * Copyright (c) 2019 Christian Werner <chw at ch minus werner dot de>
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include <tcl.h>
#include <tk.h>
#include <string.h>
#include <stdlib.h>
#include <openjpeg.h>

/*
 * Internal struct to implement a memory backed stream
 * for the openjp2 library.
 */

typedef struct {
    void *base;		/* Memory buffer, ckalloc'ed */
    OPJ_SIZE_T size;	/* Current size */
    OPJ_SIZE_T pos;	/* Current file position */
    int iswr;		/* True when opened for writing */
} MemUdata;


/*
 *----------------------------------------------------------------------
 *
 * LogHandler --
 *
 *	This function is invoked by openjp2 library calls for
 *	output of log messages.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
LogHandler(const char *msg, void *udata)
{
#ifndef NDEBUG
    fprintf(stderr, "%s", msg);
    fflush(stderr);
#endif
}

/*
 *----------------------------------------------------------------------
 *
 * CommonMatchJP2 --
 *
 *	This function is invoked by the file and string match functions
 *	to see if the given stream contains image data in JP2 format.
 *
 * Results:
 *	The return value is >0 if the stream can be successfully
 *	decoded, and 0 otherwise.
 *
 * Side effects:
 *	The width and height variables, if any, are updated.
 *
 *----------------------------------------------------------------------
 */

static int
CommonMatchJP2(
    opj_stream_t *stream,
    int *widthPtr,
    int *heightPtr)
{
    int match = 0;
    opj_codec_t *codec;
    opj_dparameters_t par;
    opj_image_t *img = NULL;

    codec = opj_create_decompress(OPJ_CODEC_JP2);
    if (codec == NULL) {
	goto done;
    }
    opj_set_info_handler(codec, LogHandler, 0);
    opj_set_warning_handler(codec, LogHandler, 0);
    opj_set_error_handler(codec, LogHandler, 0);
    memset(&par, 0, sizeof(par));
    opj_set_default_decoder_parameters(&par);
    opj_setup_decoder(codec, &par);
    if (opj_read_header(stream, codec, &img)) {
	int w = 0, h = 0;

	if (img->numcomps > 0) {
	    w = img->comps[0].w;
	    h = img->comps[0].h;
	}
	if (widthPtr != NULL) {
	    *widthPtr = w;
	}
	if (heightPtr != NULL) {
	    *heightPtr = h;
	}
	match = 1;
    }
done:
    if (codec != NULL) {
	opj_destroy_codec(codec);
    }
    if (img != NULL) {
	opj_image_destroy(img);
    }
    return match;
}

/*
 *----------------------------------------------------------------------
 *
 * DownShift --
 *
 *	This helper function normalizes a high resolution image
 *	component (more than 8 bit) to 8 bit resolution for further
 *	processing.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Image component data is modified in place.
 *
 *----------------------------------------------------------------------
 */

static void
DownShift(
    opj_image_comp_t *comp)
{
    int n, shift = comp->prec - 8;

    for (n = 0; n < comp->w * comp->h; n++) {
	comp->data[n] >>= shift;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * CommonReadJP2 --
 *
 *	This function is called by the photo image type to read JP2
 *	format data from a stream and write it into a given photo image.
 *
 * Results:
 *	A standard Tcl completion code. If TCL_ERROR is returned then
 *	an error message is left in the interp's result.
 *
 * Side effects:
 *	New data is added to the image given by imgHandle.
 *
 *----------------------------------------------------------------------
 */

static int
CommonReadJP2(
    Tcl_Interp *interp,
    opj_stream_t *stream,
    Tk_PhotoHandle imgHandle,
    int destX,
    int destY,
    int width,
    int height,
    int srcX,
    int srcY)
{
    int result = TCL_ERROR;
    opj_codec_t *codec;
    opj_dparameters_t par;
    opj_image_t *img = NULL;

    memset(&par, 0, sizeof(par));
    opj_set_default_decoder_parameters(&par);
    codec = opj_create_decompress(OPJ_CODEC_JP2);
    if (codec == NULL) {
	Tcl_SetResult(interp, "error creating codec", TCL_STATIC);
	goto done;
    }
    opj_set_info_handler(codec, LogHandler, 0);
    opj_set_warning_handler(codec, LogHandler, 0);
    opj_set_error_handler(codec, LogHandler, 0);
    opj_setup_decoder(codec, &par);
    if (opj_read_header(stream, codec, &img) &&
	opj_set_decode_area(codec, img, srcX, srcY,
			    srcX + width, srcY + height)) {
	if (opj_decode(codec, stream, img) &&
	    opj_end_decompress(codec, stream)) {
	    result = TCL_OK;
	}
    } else {
	Tcl_SetResult(interp, "decoding failed", TCL_STATIC);
    }
    if (result == TCL_OK && img->comps[0].data != NULL) {
	Tk_PhotoImageBlock blk;

	memset(&blk, 0, sizeof(blk));
	if (img->numcomps == 3 && img->color_space == OPJ_CLRSPC_SRGB &&
	    img->comps[0].w == img->comps[1].w &&
	    img->comps[0].w == img->comps[2].w &&
	    img->comps[0].h == img->comps[1].h &&
	    img->comps[0].h == img->comps[2].h) {
	    if (img->comps[0].prec > 8) {
		DownShift(&img->comps[0]);
	    }
	    if (img->comps[1].prec > 8) {
		DownShift(&img->comps[1]);
	    }
	    if (img->comps[2].prec > 8) {
		DownShift(&img->comps[2]);
	    }
	    blk.pixelPtr = (unsigned char *) img->comps[0].data;
	    blk.pixelSize = sizeof(img->comps[0].data[0]);
	    blk.offset[0] = 0;
	    blk.offset[1] = (unsigned char *) img->comps[1].data - blk.pixelPtr;
	    blk.offset[2] = (unsigned char *) img->comps[2].data - blk.pixelPtr;
	    blk.width = img->comps[0].w;
	    blk.height = img->comps[0].h;
	    blk.pitch = img->comps[0].w * blk.pixelSize;
	} else if (img->numcomps > 0 && img->color_space == OPJ_CLRSPC_GRAY) {
	    if (img->comps[0].prec > 8) {
		DownShift(&img->comps[0]);
	    }
	    blk.pixelPtr = (unsigned char *) img->comps[0].data;
	    blk.pixelSize = sizeof(img->comps[0].data[0]);
	    blk.width = img->comps[0].w;
	    blk.height = img->comps[0].h;
	    blk.pitch = img->comps[0].w * blk.pixelSize;
	} else {
	    result = TCL_ERROR;
	    Tcl_SetResult(interp, "unsupported color space", TCL_STATIC);
	}
	if (result == TCL_OK) {
	    if (destX == 0 && destY == 0) {
		Tk_PhotoSetSize(interp, imgHandle, blk.width, blk.height);
	    }
	    Tk_PhotoExpand(interp, imgHandle,
			   destX + blk.width, destY + blk.height);
	    Tk_PhotoPutBlock_NoComposite(imgHandle, &blk, destX, destY,
					 blk.width, blk.height);
	}
    }
done:
    if (codec != NULL) {
	opj_destroy_codec(codec);
    }
    if (img != NULL) {
	opj_image_destroy(img);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * CommonWriteJP2 --
 *
 *	This function is called by the photo image type to write JP2
 *	format data from given photo image and write it into a given
 *	stream. Codec parameters can be passed in the fmtObj parameter,
 *	e.g. quality or compression settings.
 *
 * Results:
 *	A standard Tcl completion code. If TCL_ERROR is returned then
 *	an error message is left in the interp's result.
 *
 * Side effects:
 *	New data is written to the stream.
 *
 *----------------------------------------------------------------------
 */

static int
CommonWriteJP2(
    Tcl_Interp *interp,
    opj_stream_t *stream,
    Tcl_Obj *fmtObj,
    Tk_PhotoImageBlock *blkPtr)
{
    int x, y, result = TCL_ERROR;
    opj_cparameters_t par;
    opj_codec_t *codec;
    opj_image_t *img = NULL;
    opj_image_cmptparm_t comps[3];

    memset(&par, 0, sizeof(par));
    opj_set_default_encoder_parameters(&par);
    par.tcp_mct = 255;
    if (fmtObj != NULL) {
	char *fmtStr, *p, *q;
	int len, n;

	fmtStr = Tcl_GetString(fmtObj);
	p = strchr(fmtStr, '/');
	while (p != NULL) {
	    len = strlen(p);
	    if (len > 3 && strncmp(p, "/r:", 3) == 0) {
		n = 0;
		q = p + 3;
		while (n < sizeof(par.tcp_rates) / sizeof(par.tcp_rates[0])) {
		    char *end = q;
		    double d;

		    d = strtod(q, &end);
		    if (end == NULL || end == q) {
			break;
		    }
		    if (d < 1) {
			d = 1;
		    }
		    par.tcp_rates[n++] = d;
		    q = end + 1;
		}
		par.tcp_numlayers = n;
		par.cp_disto_alloc = 1;
		par.cp_fixed_quality = 0;
		memset(par.tcp_distoratio, 0, sizeof(par.tcp_distoratio));
	    } else if (len > 3 && strncmp(p, "/q:", 3) == 0) {
		n = 0;
		q = p + 3;
		while (n < sizeof(par.tcp_distoratio) /
		       sizeof(par.tcp_distoratio[0])) {
		    char *end = q;
		    double d;

		    d = strtod(q, &end);
		    if (end == NULL || end == q) {
			break;
		    }
		    par.tcp_distoratio[n++] = d;
		    q = end + 1;
		}
		par.tcp_numlayers = n;
		par.cp_fixed_quality = 1;
		par.cp_disto_alloc = 0;
		memset(par.tcp_rates, 0, sizeof(par.tcp_rates));
	    } else if (len > 1 && strncmp(p, "/I", 2) == 0) {
		par.irreversible = 1;
	    }
	    p = strchr(p + 1, '/');
	}
    }
    codec = opj_create_compress(OPJ_CODEC_JP2);
    if (codec == NULL) {
	Tcl_SetResult(interp, "error creating codec", TCL_STATIC);
	goto done;
    }
    opj_set_info_handler(codec, LogHandler, 0);
    opj_set_warning_handler(codec, LogHandler, 0);
    opj_set_error_handler(codec, LogHandler, 0);
    memset(comps, 0, sizeof(comps));
    comps[0].prec = comps[0].bpp = 8;
    comps[0].sgnd = 0;
    comps[0].dx = comps[0].dy = 1;
    comps[0].w = blkPtr->width;
    comps[0].h = blkPtr->height;
    comps[1] = comps[2] = comps[0];
    img = opj_image_create(3, comps, OPJ_CLRSPC_SRGB);
    if (img == NULL) {
	Tcl_SetResult(interp, "error creating image", TCL_STATIC);
	goto done;
    }
    img->x0 = img->y0 = 0;
    img->x1 = blkPtr->width;
    img->y1 = blkPtr->height;
    for (y = 0; y < blkPtr->height; y++) {
	int n;
	unsigned char *p[3];

	n = y * blkPtr->pitch;
	p[0] = blkPtr->pixelPtr + n + blkPtr->offset[0];
	p[1] = blkPtr->pixelPtr + n + blkPtr->offset[1];
	p[2] = blkPtr->pixelPtr + n + blkPtr->offset[2];
	n = y * blkPtr->width;
	for (x = 0; x < blkPtr->width; x++) {
	    img->comps[0].data[n + x] = *(p[0]);
	    img->comps[1].data[n + x] = *(p[1]);
	    img->comps[2].data[n + x] = *(p[2]);
	    p[0] += blkPtr->pixelSize;
	    p[1] += blkPtr->pixelSize;
	    p[2] += blkPtr->pixelSize;
	}
    }
    if (!opj_setup_encoder(codec, &par, img)) {
	Tcl_SetResult(interp, "setup encoder failed", TCL_STATIC);
    } else if (!opj_start_compress(codec, img, stream)) {
	Tcl_SetResult(interp, "start compress failed", TCL_STATIC);
    } else if (!opj_encode(codec, stream)) {
	Tcl_SetResult(interp, "encoding failed", TCL_STATIC);
    } else if (!opj_end_compress(codec, stream)) {
	Tcl_SetResult(interp, "end compress failed", TCL_STATIC);
    } else {
	result = TCL_OK;
    }
done:
    if (codec != NULL) {
	opj_destroy_codec(codec);
    }
    if (img != NULL) {
	opj_image_destroy(img);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ChanRead, ChanWrite, ChanSkip, ChanSeek -
 *
 *	These functions map from a Tcl_Channel to a stream as
 *	used by the openjp2 library.
 *
 * Results:
 *	Usually the number of bytes processed or OPJ_TRUE on success,
 *	-1 or OPJ_FALSE on errors.
 *
 * Side effects:
 *	Depending on the I/O function.
 *
 *----------------------------------------------------------------------
 */

static OPJ_SIZE_T
ChanRead(void *buffer, OPJ_SIZE_T nbytes, void *udata)
{
    Tcl_Channel chan = (Tcl_Channel) udata;
    OPJ_SIZE_T nread = 0;

    while (nbytes > 0) {
	int n, toRead = 1024 * 64;

	if (toRead > nbytes) {
	    toRead = nbytes;
	}
	n = Tcl_Read(chan, (char *) buffer + nread, toRead);
	if (n == -1) {
	    if (nread == 0) {
		nread = -1;
	    }
	    break;
	}
	if (n == 0) {
	    if (nread == 0) {
		nread = -1;
	    }
	    break;
	}
	nread += n;
	nbytes -= n;
    }
    return nread;
}

static OPJ_SIZE_T
ChanWrite(void *buffer, OPJ_SIZE_T nbytes, void *udata)
{
    Tcl_Channel chan = (Tcl_Channel) udata;
    OPJ_SIZE_T nwritten = 0;

    while (nbytes > 0) {
	int n, toWrite = 1024 * 64;

	if (toWrite > nbytes) {
	    toWrite = nbytes;
	}
	n = Tcl_Write(chan, (char *) buffer + nwritten, toWrite);
	if (n == -1) {
	    if (nwritten == 0) {
		nwritten = -1;
	    }
	    break;
	}
	if (n == 0) {
	    if (nwritten == 0) {
		nwritten = -1;
	    }
	    break;
	}
	nwritten += n;
	nbytes -= n;
    }
    return nwritten;
}

static OPJ_OFF_T
ChanSkip(OPJ_OFF_T nbytes, void *udata)
{
    Tcl_Channel chan = (Tcl_Channel) udata;

    if (Tcl_Seek(chan, nbytes, SEEK_CUR) == -1) {
	return -1;
    }
    return nbytes;
}

static OPJ_BOOL
ChanSeek(OPJ_OFF_T nbytes, void *udata)
{
    Tcl_Channel chan = (Tcl_Channel) udata;

    return (Tcl_Seek(chan, nbytes, SEEK_SET) != -1) ? OPJ_TRUE : OPJ_FALSE;
}

/*
 *----------------------------------------------------------------------
 *
 * MakeFileStream --
 *
 *	This function wraps a Tcl_Channel either for reading or for
 *	writing of a stream object as used by the openjp2 library.
 *
 * Results:
 *	Pointer to stream object or NULL on error.
 *
 * Side effects:
 *	Resources are allocated by the openjp2 library.
 *
 *----------------------------------------------------------------------
 */

static opj_stream_t *
MakeFileStream(
    Tcl_Channel chan,
    int iswr)
{
    opj_stream_t *stream;

    stream = opj_stream_default_create(iswr ? OPJ_FALSE : OPJ_TRUE);
    if (stream != NULL) {
	opj_stream_set_skip_function(stream, ChanSkip);
	opj_stream_set_seek_function(stream, ChanSeek);
	if (iswr) {
	    opj_stream_set_write_function(stream, ChanWrite);
	} else {
	    OPJ_OFF_T len = Tcl_Seek(chan, 0, SEEK_END);

	    Tcl_Seek(chan, 0, SEEK_SET);
	    opj_stream_set_read_function(stream, ChanRead);
	    opj_stream_set_user_data_length(stream, len);
	}
	opj_stream_set_user_data(stream, chan, NULL);
    }
    return stream;
}

/*
 *----------------------------------------------------------------------
 *
 * MemRead, MemWrite, MemSkip, MemSeek -
 *
 *	These functions map from a memory buffer to a stream as
 *	used by the openjp2 library. They are used to read/write
 *	JP2 data from/to a byte array.
 *
 * Results:
 *	Usually the number of bytes processed or OPJ_TRUE on success,
 *	-1 or OPJ_FALSE on errors.
 *
 * Side effects:
 *	Depending on the I/O function.
 *
 *----------------------------------------------------------------------
 */

static OPJ_SIZE_T
MemRead(void *buffer, OPJ_SIZE_T nbytes, void *udata)
{
    MemUdata *mud = (MemUdata *) udata;

    if (nbytes > mud->size - mud->pos) {
	nbytes = mud->size - mud->pos;
    }
    if (nbytes > 0) {
	memcpy(buffer, mud->base + mud->pos, nbytes);
	mud->pos += nbytes;
    }
    return nbytes;
}

static OPJ_SIZE_T
MemWrite(void *buffer, OPJ_SIZE_T nbytes, void *udata)
{
    MemUdata *mud = (MemUdata *) udata;

    if (!mud->iswr) {
	return -1;
    }
    if (mud->pos + nbytes > mud->size) {
	char *base = attemptckrealloc(mud->base, mud->pos + nbytes);

	if (base == NULL) {
	    return -1;
	}
	mud->base = base;
	mud->size = mud->pos + nbytes;
    }
    if (nbytes > 0) {
	memcpy(mud->base + mud->pos, buffer, nbytes);
	mud->pos += nbytes;
    }
    return nbytes;
}

static OPJ_OFF_T
MemSkip(OPJ_OFF_T nbytes, void *udata)
{
    MemUdata *mud = (MemUdata *) udata;

    if (mud->pos + nbytes > mud->size) {
	if (mud->iswr) {
	    char *base = attemptckrealloc(mud->base, mud->pos + nbytes);

	    if (base == NULL) {
		return -1;
	    }
	    mud->base = base;
	    memset(mud->base + mud->size, 0, mud->pos + nbytes - mud->size);
	    mud->size = mud->pos + nbytes;
	} else {
	    return -1;
	}
    } else if (mud->pos + nbytes < 0) {
	return -1;
    }
    mud->pos += nbytes;
    return nbytes;
}

static OPJ_BOOL
MemSeek(OPJ_OFF_T nbytes, void *udata)
{
    MemUdata *mud = (MemUdata *) udata;

    if (nbytes < 0 || nbytes > mud->size) {
	return OPJ_FALSE;
    }
    mud->pos = nbytes;
    return OPJ_TRUE;
}

/*
 *----------------------------------------------------------------------
 *
 * MakeMemStream --
 *
 *	This function creates a memory buffer either for reading or for
 *	writing of a stream object as used by the openjp2 library.
 *
 * Results:
 *	Pointer to stream object or NULL on error.
 *
 * Side effects:
 *	Resources are allocated by the openjp2 library.
 *
 *----------------------------------------------------------------------
 */

static opj_stream_t *
MakeMemStream(
    MemUdata *mud,
    unsigned char *data,
    int size)
{
    opj_stream_t *stream;

    stream =
	opj_stream_default_create((data == NULL) ? OPJ_FALSE : OPJ_TRUE);
    if (stream != NULL) {
	opj_stream_set_skip_function(stream, MemSkip);
	opj_stream_set_seek_function(stream, MemSeek);
	if (data == NULL) {
	    mud->base = NULL;
	    mud->size = mud->pos = 0;
	    mud->iswr = 1;
	    opj_stream_set_write_function(stream, MemWrite);
	} else {
	    mud->base = data;
	    mud->size = size;
	    mud->pos = 0;
	    mud->iswr = 0;
	    opj_stream_set_read_function(stream, MemRead);
	    opj_stream_set_user_data_length(stream, mud->size);
	}
	opj_stream_set_user_data(stream, mud, NULL);
    }
    return stream;
}

/*
 *----------------------------------------------------------------------
 *
 * FileMatchJP2 --
 *
 *	This function is invoked by the photo image type to see if a
 *	file contains image data in JP2 format.
 *
 * Results:
 *	The return value is >0 if the file can be successfully
 *	decoded, and 0 otherwise.
 *
 * Side effects:
 *	The width and height variables, if any, are updated.
 *
 *----------------------------------------------------------------------
 */

static int
FileMatchJP2(
    Tcl_Channel chan,
    const char *fileName,
    Tcl_Obj *fmtObj,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp)
{
    int match = 0, hlen;
    opj_stream_t *stream;
    unsigned char hdr[9];

    hlen = Tcl_Read(chan, (char *) hdr, sizeof(hdr));
    Tcl_Seek(chan, 0, SEEK_SET);
    if (hlen != sizeof(hdr)) {
	return match;
    }
    if (!((hdr[4] == 'j' && hdr[5] == 'P' &&
	   hdr[6] == ' ' && hdr[7] == ' ' && hdr[8] == '\r') ||
          (hdr[0] == 0xFF && hdr[1] == 0x4F))) {
	return match;
    }
    stream = MakeFileStream(chan, 0);
    if (stream != NULL) {
	match = CommonMatchJP2(stream, widthPtr, heightPtr);
	opj_stream_destroy(stream);
    }
    return match;
}

/*
 *----------------------------------------------------------------------
 *
 * FileReadJP2 --
 *
 *	This function is called by the photo image type to read JP2
 *	format data from a file and write it into a given photo image.
 *
 * Results:
 *	A standard Tcl completion code. If TCL_ERROR is returned then
 *	an error message is left in the interp's result.
 *
 * Side effects:
 *	The access position in the given file channel is changed, and
 *	new data is added to the image given by imgHandle.
 *
 *----------------------------------------------------------------------
 */

static int
FileReadJP2(
    Tcl_Interp *interp,
    Tcl_Channel chan,
    const char *fileName,
    Tcl_Obj *fmtObj,
    Tk_PhotoHandle imgHandle,
    int destX,
    int destY,
    int width,
    int height,
    int srcX,
    int srcY)
{
    int result = TCL_ERROR, hlen;
    opj_stream_t *stream;
    unsigned char hdr[9];

    hlen = Tcl_Read(chan, (char *) hdr, sizeof(hdr));
    Tcl_Seek(chan, 0, SEEK_SET);
    if (hlen != sizeof(hdr)) {
	Tcl_SetResult(interp, "invalid image header", TCL_STATIC);
	return result;
    }
    if (!((hdr[4] == 'j' && hdr[5] == 'P' &&
	   hdr[6] == ' ' && hdr[7] == ' ' && hdr[8] == '\r') ||
          (hdr[0] == 0xFF && hdr[1] == 0x4F))) {
	Tcl_SetResult(interp, "invalid image header", TCL_STATIC);
	return result;
    }
    stream = MakeFileStream(chan, 0);
    if (stream != NULL) {
	result = CommonReadJP2(interp, stream, imgHandle,
			       destX, destY, width, height, srcX, srcY);
	opj_stream_destroy(stream);
    } else {
	Tcl_SetResult(interp, "error creating stream", TCL_STATIC);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringMatchJP2 --
 *
 *	This function is invoked by the photo image type to see if a
 *	string contains image data in JP2 format.
 *
 * Results:
 *	The return value is >0 if the string can be successfully
 *	parsed, and 0 otherwise.
 *
 * Side effects:
 *	The width and height variables, if any, are updated.
 *
 *----------------------------------------------------------------------
 */

static int
StringMatchJP2(
    Tcl_Obj *dataObj,
    Tcl_Obj *fmtObj,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp)
{
    int match = 0, length;
    opj_stream_t *stream;
    MemUdata mud;
    unsigned char *data;

    data = Tcl_GetByteArrayFromObj(dataObj, &length);
    if (length < 9) {
	return match;
    }
    if (!((data[4] == 'j' && data[5] == 'P' &&
	   data[6] == ' ' && data[7] == ' ' && data[8] == '\r') ||
          (data[0] == 0xFF && data[1] == 0x4F))) {
	return match;
    }
    memset(&mud, 0, sizeof(mud));
    stream = MakeMemStream(&mud, data, length);
    if (stream != NULL) {
	match = CommonMatchJP2(stream, widthPtr, heightPtr);
	opj_stream_destroy(stream);
    }
    return match;
}

/*
 *----------------------------------------------------------------------
 *
 * StringReadJP2 --
 *
 *	This function is called by the photo image type to read JP2
 *	format data from a string and write it into a given photo image.
 *
 * Results:
 *	A standard Tcl completion code. If TCL_ERROR is returned then
 *	an error message is left in the interp's result.
 *
 * Side effects:
 *	New data is added to the image given by imgHandle.
 *
 *----------------------------------------------------------------------
 */

static int
StringReadJP2(
    Tcl_Interp *interp,
    Tcl_Obj *dataObj,
    Tcl_Obj *fmtObj,
    Tk_PhotoHandle imgHandle,
    int destX,
    int destY,
    int width,
    int height,
    int srcX,
    int srcY)
{
    int result = TCL_ERROR, length;
    opj_stream_t *stream;
    MemUdata mud;
    unsigned char *data;

    data = Tcl_GetByteArrayFromObj(dataObj, &length);
    if (length < 9) {
	Tcl_SetResult(interp, "invalid image header", TCL_STATIC);
	return result;
    }
    if (!((data[4] == 'j' && data[5] == 'P' &&
	   data[6] == ' ' && data[7] == ' ' && data[8] == '\r') ||
          (data[0] == 0xFF && data[1] == 0x4F))) {
	Tcl_SetResult(interp, "invalid image header", TCL_STATIC);
	return result;
    }
    memset(&mud, 0, sizeof(mud));
    stream = MakeMemStream(&mud, data, length);
    if (stream != NULL) {
	result = CommonReadJP2(interp, stream, imgHandle,
			       destX, destY, width, height, srcX, srcY);
	opj_stream_destroy(stream);
    } else {
	Tcl_SetResult(interp, "error creating stream", TCL_STATIC);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * FileWriteJP2 --
 *
 *	This function is called by the photo image type to write JP2
 *	format data from the given photo image to the given file name.
 *
 * Results:
 *	A standard Tcl completion code. If TCL_ERROR is returned then
 *	an error message is left in the interp's result.
 *
 * Side effects:
 *	The file is created and written with JP2 encoded image data.
 *
 *----------------------------------------------------------------------
 */

static int
FileWriteJP2(
    Tcl_Interp *interp,
    const char *filename,
    Tcl_Obj *fmtObj,
    Tk_PhotoImageBlock *blkPtr)
{
    int result = TCL_ERROR;
    Tcl_Channel chan;
    opj_stream_t *stream;

    chan = Tcl_OpenFileChannel(interp, filename, "wb", 0666);
    if (chan == NULL) {
	return TCL_ERROR;
    }
    stream = MakeFileStream(chan, 1);
    if (stream != NULL) {
	result = CommonWriteJP2(interp, stream, fmtObj, blkPtr);
	opj_stream_destroy(stream);
    } else {
	Tcl_SetResult(interp, "error creating stream", TCL_STATIC);
    }
    Tcl_Close(NULL, chan);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringWriteJP2 --
 *
 *	This function is called by the photo image type to write JP2
 *	format data from the given photo image into a byte array.
 *
 * Results:
 *	A standard Tcl completion code. If TCL_ERROR is returned then
 *	an error message is left in the interp's result. Otherwise
 *	inter's result is set to the byte array.
 *
 * Side effects:
 *	A byte array is created and written with JP2 encoded image data.
 *
 *----------------------------------------------------------------------
 */

static int
StringWriteJP2(
    Tcl_Interp *interp,
    Tcl_Obj *fmtObj,
    Tk_PhotoImageBlock *blkPtr)
{
    int result = TCL_ERROR;
    opj_stream_t *stream;
    MemUdata mud;

    memset(&mud, 0, sizeof(mud));
    stream = MakeMemStream(&mud, NULL, 0);
    if (stream != NULL) {
	result = CommonWriteJP2(interp, stream, fmtObj, blkPtr);
	opj_stream_destroy(stream);
    } else {
	Tcl_SetResult(interp, "error creating stream", TCL_STATIC);
    }
    if (result == TCL_OK) {
	if (mud.base == NULL || mud.size == 0) {
	    Tcl_SetResult(interp, "empty stream", TCL_STATIC);
	    result = TCL_ERROR;
	} else {
	    Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(mud.base, mud.size));
	}
    }
    if (mud.base != NULL) {
	ckfree(mud.base);
    }
    return result;
}

#ifdef USE_TCL_ALLOCATOR
/*
 *----------------------------------------------------------------------
 *
 * opj_malloc et.al --
 *
 *	Memory management functions for the openjp2 library. Most
 *	logic is borrowed from openjpeg/src/lib/openjp2/opj_malloc.c
 *	but uses Tcl's versions instead of malloc and friends.
 *
 *----------------------------------------------------------------------
 */

void *
opj_malloc(size_t size)
{
    unsigned int isize = size;

    if ((isize != size) || (isize == 0)) {
	return NULL;
    }
    return attemptckalloc(isize);
}

void *
opj_calloc(size_t num, size_t size)
{
    unsigned int isize;
    void *ptr;

    if ((num == 0) || (size == 0)) {
	return NULL;
    }
    isize = num * size;
    ptr = attemptckalloc(isize);
    if (ptr != NULL) {
	memset(ptr, 0, isize);
    }
    return ptr;
}

void *
opj_realloc(void *ptr, size_t size)
{
    unsigned int isize = size;

    if ((isize != size) || (isize == 0)) {
	return NULL;
    }
    return attemptckrealloc(ptr, isize);
}

void
opj_free(void *ptr)
{
    ckfree(ptr);
}

void *
opj_aligned_malloc(size_t size)
{
    size_t nsize;
    unsigned int isize;
    unsigned char *mem;
    void *ptr = NULL;

    if (size == 0) {
	return NULL;
    }
    nsize = size + 15 + sizeof(void *);
    isize = nsize;
    if ((nsize < size) || (isize != nsize)) {
	return NULL;
    }
    mem = attemptckalloc(isize);
    if (mem != NULL) {
	size_t off = ((15 ^ ((size_t)(mem + sizeof(void *)) & 15)) + 1) & 15;

	ptr = (void *)(mem + sizeof(void *) + off);
	((void **) ptr)[-1] = mem;
    }
    return ptr;
}

void *
opj_aligned_32_malloc(size_t size)
{
    size_t nsize;
    unsigned int isize;
    unsigned char *mem;
    void *ptr = NULL;

    if (size == 0) {
	return NULL;
    }
    nsize = size + 31 + sizeof(void *);
    isize = nsize;
    if ((nsize < size) || (isize != nsize)) {
	return NULL;
    }
    mem = attemptckalloc(isize);
    if (mem != NULL) {
	size_t off = ((31 ^ ((size_t)(mem + sizeof(void *)) & 31)) + 1) & 31;

	ptr = (void *)(mem + sizeof(void *) + off);
	((void **) ptr)[-1] = mem;
    }
    return ptr;
}

void *
opj_aligned_realloc(void *ptr, size_t size)
{
    size_t nsize;
    unsigned int isize;
    void *old, *nptr = NULL;
    unsigned char *mem;

    if (size == 0) {
	return NULL;
    }
    if (ptr == NULL) {
	return opj_aligned_malloc(size);
    }
    nsize = size + 15 + sizeof(void *);
    isize = nsize;
    if ((nsize < size) || (isize != nsize)) {
	return NULL;
    }
    old = ((void **) ptr)[-1];
    mem = attemptckrealloc(old, isize);
    if (mem != NULL) {
	size_t off = ((char *) ptr - (char *) old);
	size_t noff = ((15 ^ ((size_t)(mem + sizeof(void *)) & 15)) + 1) & 15;

	noff *= sizeof(void *);
	nptr = (void *)(mem + noff);
	if (noff != off) {
	    memmove(mem + noff, mem + off, size);
	}
	((void **) nptr)[-1] = mem;
    }
    return nptr;
}

void *
opj_aligned_32_realloc(void *ptr, size_t size)
{
    size_t nsize;
    unsigned int isize;
    void *old, *nptr = NULL;
    unsigned char *mem;

    if (size == 0) {
	return NULL;
    }
    if (ptr == NULL) {
	return opj_aligned_malloc(size);
    }
    nsize = size + 31 + sizeof(void *);
    isize = nsize;
    if ((nsize < size) || (isize != nsize)) {
	return NULL;
    }
    old = ((void **) ptr)[-1];
    mem = attemptckrealloc(old, isize);
    if (mem != NULL) {
	size_t off = ((char *) ptr - (char *) old);
	size_t noff = ((31 ^ ((size_t)(mem + sizeof(void *)) & 31)) + 1) & 31;

	noff *= sizeof(void *);
	nptr = (void *)(mem + noff);
	if (noff != off) {
	    memmove(mem + noff, mem + off, size);
	}
	((void **) nptr)[-1] = mem;
    }
    return nptr;
}

void
opj_aligned_free(void *ptr)
{
    if (ptr != NULL) {
	ckfree(((void **) ptr)[-1]);
    }
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * FmtJP2 --
 *
 *	This structure defines the "jp2" photo image format. It
 *	is registered in Tk when this module is loaded.
 *
 *----------------------------------------------------------------------
 */

static Tk_PhotoImageFormat FmtJP2 = {
    "jp2",
    FileMatchJP2,
    StringMatchJP2,
    FileReadJP2,
    StringReadJP2,
    FileWriteJP2,
    StringWriteJP2,
    NULL
};

/*
 *----------------------------------------------------------------------
 *
 * Imgjp2_Init --
 *
 *	This function is called when this module is loaded into
 *	the calling interp.
 *
 * Results:
 *	A standard Tcl completion code.
 *
 * Side effects:
 *	A photo image handler for the JP2 format (named "jp2")
 *	is added to the calling interp.
 *
 *----------------------------------------------------------------------
 */

int
DLLEXPORT
Imgjp2_Init(Tcl_Interp *interp)
{
#ifdef USE_TCL_STUBS
    if (Tcl_InitStubs(interp, "8.5", 0) == NULL) {
	return TCL_ERROR;
    }
#else
    if (Tcl_PkgRequire(interp, "Tcl", "8.5", 0) == NULL) {
	return TCL_ERROR;
    }
#endif
#ifdef USE_TK_STUBS
    if (Tk_InitStubs(interp, "8.5", 0) == NULL) {
	return TCL_ERROR;
    }
#else
    if (Tcl_PkgRequire(interp, "Tk", "8.5", 0) == NULL) {
	return TCL_ERROR;
    }
#endif
    if (Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION) != TCL_OK) {
	return TCL_ERROR;
    }
    Tk_CreatePhotoImageFormat(&FmtJP2);
    return TCL_OK;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * tab-width: 8
 * End:
 */

