/*
 * tkImgPNG.c --
 *
 *	A Tk photo image file handler for PNG files.
 *
 * Copyright (c) 2006-2008 Muonics, Inc.
 * Copyright (c) 2008 Donal K. Fellows
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "assert.h"
#include "tkInt.h"

#define	PNG_INT32(a,b,c,d)	\
	(((long)(a) << 24) | ((long)(b) << 16) | ((long)(c) << 8) | (long)(d))
#define	PNG_BLOCK_SZ	1024		/* Process up to 1k at a time. */
#define PNG_MIN(a, b) (((a) < (b)) ? (a) : (b))

/*
 * Every PNG image starts with the following 8-byte signature.
 */

#define PNG_SIG_SZ	8
static const unsigned char pngSignature[] = {
    137, 80, 78, 71, 13, 10, 26, 10
};

static const int startLine[8] = {
    0, 0, 0, 4, 0, 2, 0, 1
};

/*
 * Chunk type flags.
 */

#define PNG_CF_ANCILLARY 0x20000000L	/* Non-critical chunk (can ignore). */
#define PNG_CF_PRIVATE   0x00100000L	/* Application-specific chunk. */
#define PNG_CF_RESERVED  0x00001000L	/* Not used. */
#define PNG_CF_COPYSAFE  0x00000010L	/* Opaque data safe for copying. */

/*
 * Chunk types, not all of which have support implemented. Note that there are
 * others in the official extension set which we will never support (as they
 * are officially deprecated).
 */

#define CHUNK_IDAT	PNG_INT32('I','D','A','T')	/* Pixel data. */
#define CHUNK_IEND	PNG_INT32('I','E','N','D')	/* End of Image. */
#define CHUNK_IHDR	PNG_INT32('I','H','D','R')	/* Header. */
#define CHUNK_PLTE	PNG_INT32('P','L','T','E')	/* Palette. */

#define CHUNK_bKGD	PNG_INT32('b','K','G','D')	/* Background Color */
#define CHUNK_cHRM	PNG_INT32('c','H','R','M')	/* Chroma values. */
#define CHUNK_gAMA	PNG_INT32('g','A','M','A')	/* Gamma. */
#define CHUNK_hIST	PNG_INT32('h','I','S','T')	/* Histogram. */
#define CHUNK_iCCP	PNG_INT32('i','C','C','P')	/* Color profile. */
#define CHUNK_iTXt	PNG_INT32('i','T','X','t')	/* Internationalized
							 * text (comments,
							 * etc.) */
#define CHUNK_oFFs	PNG_INT32('o','F','F','s')	/* Image offset. */
#define CHUNK_pCAL	PNG_INT32('p','C','A','L')	/* Pixel calibration
							 * data. */
#define CHUNK_pHYs	PNG_INT32('p','H','Y','s')	/* Physical pixel
							 * dimensions. */
#define CHUNK_sBIT	PNG_INT32('s','B','I','T')	/* Significant bits */
#define CHUNK_sCAL	PNG_INT32('s','C','A','L')	/* Physical scale. */
#define CHUNK_sPLT	PNG_INT32('s','P','L','T')	/* Suggested
							 * palette. */
#define CHUNK_sRGB	PNG_INT32('s','R','G','B')	/* Standard RGB space
							 * declaration. */
#define CHUNK_tEXt	PNG_INT32('t','E','X','t')	/* Plain Latin-1
							 * text. */
#define CHUNK_tIME	PNG_INT32('t','I','M','E')	/* Time stamp. */
#define CHUNK_tRNS	PNG_INT32('t','R','N','S')	/* Transparency. */
#define CHUNK_zTXt	PNG_INT32('z','T','X','t')	/* Compressed Latin-1
							 * text. */

/*
 * Color flags.
 */

#define PNG_COLOR_INDEXED	1
#define PNG_COLOR_USED		2
#define PNG_COLOR_ALPHA		4

/*
 * Actual color types.
 */

#define PNG_COLOR_GRAY		0
#define PNG_COLOR_RGB		(PNG_COLOR_USED)
#define PNG_COLOR_PLTE		(PNG_COLOR_USED | PNG_COLOR_INDEXED)
#define PNG_COLOR_GRAYALPHA	(PNG_COLOR_GRAY | PNG_COLOR_ALPHA)
#define PNG_COLOR_RGBA		(PNG_COLOR_USED | PNG_COLOR_ALPHA)

/*
 * Compression Methods.
 */

#define PNG_COMPRESS_DEFLATE	0

/*
 * Filter Methods.
 */

#define PNG_FILTMETH_STANDARD	0

/*
 * Interlacing Methods.
 */

#define	PNG_INTERLACE_NONE	0
#define PNG_INTERLACE_ADAM7	1

/*
 * State information, used to store everything about the PNG image being
 * currently parsed or created.
 */

typedef struct {
    /*
     * PNG data source/destination channel/object/byte array.
     */

    Tcl_Channel channel;	/* Channel for from-file reads. */
    Tcl_Obj *objDataPtr;
    unsigned char *strDataBuf;	/* Raw source data for from-string reads. */
    int strDataLen;		/* Length of source data. */
    unsigned char *base64Data;	/* base64 encoded string data. */
    unsigned char base64Bits;	/* Remaining bits from last base64 read. */
    unsigned char base64State;	/* Current state of base64 decoder. */
    double alpha;		/* Alpha from -format option. */

    /*
     * Image header information.
     */

    unsigned char bitDepth;	/* Number of bits per pixel. */
    unsigned char colorType;	/* Grayscale, TrueColor, etc. */
    unsigned char compression;	/* Compression Mode (always zlib). */
    unsigned char filter;	/* Filter mode (0 - 3). */
    unsigned char interlace;	/* Type of interlacing (if any). */
    unsigned char numChannels;	/* Number of channels per pixel. */
    unsigned char bytesPerPixel;/* Bytes per pixel in scan line. */
    int bitScale;		/* Scale factor for RGB/Gray depths < 8. */
    int currentLine;		/* Current line being unfiltered. */
    unsigned char phase;	/* Interlacing phase (0..6). */
    Tk_PhotoImageBlock block;
    int blockLen;		/* Number of bytes in Tk image pixels. */

    /*
     * For containing data read from PLTE (palette) and tRNS (transparency)
     * chunks.
     */

    int paletteLen;		/* Number of PLTE entries (1..256). */
    int useTRNS;		/* Flag to indicate whether there was a
				 * palette given. */
    struct {
	unsigned char red;
	unsigned char green;
	unsigned char blue;
	unsigned char alpha;
    } palette[256];		/* Palette RGB/Transparency table. */
    unsigned char transVal[6];	/* Fully-transparent RGB/Gray Value. */

    /*
     * For compressing and decompressing IDAT chunks.
     */

    Tcl_ZlibStream stream;	/* Inflating or deflating stream; this one is
				 * not bound to a Tcl command. */
    Tcl_Obj *lastLineObj;	/* Last line of pixels, for unfiltering. */
    Tcl_Obj *thisLineObj;	/* Current line of pixels to process. */
    int lineSize;		/* Number of bytes in a PNG line. */
    int phaseSize;		/* Number of bytes/line in current phase. */
} PNGImage;

/*
 * Maximum size of various chunks.
 */

#define	PNG_PLTE_MAXSZ 768	/* 3 bytes/RGB entry, 256 entries max */
#define	PNG_TRNS_MAXSZ 256	/* 1-byte alpha, 256 entries max */

/*
 * Forward declarations of non-global functions defined in this file:
 */

static void		ApplyAlpha(PNGImage *pngPtr);
static int		CheckColor(Tcl_Interp *interp, PNGImage *pngPtr);
static inline int	CheckCRC(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned long calculated);
static void		CleanupPNGImage(PNGImage *pngPtr);
static int		DecodeLine(Tcl_Interp *interp, PNGImage *pngPtr);
static int		DecodePNG(Tcl_Interp *interp, PNGImage *pngPtr,
			    Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle,
			    int destX, int destY);
static int		EncodePNG(Tcl_Interp *interp,
			    Tk_PhotoImageBlock *blockPtr, PNGImage *pngPtr);
static int		FileMatchPNG(Tcl_Channel chan, const char *fileName,
			    Tcl_Obj *fmtObj, int *widthPtr, int *heightPtr,
			    Tcl_Interp *interp);
static int		FileReadPNG(Tcl_Interp *interp, Tcl_Channel chan,
			    const char *fileName, Tcl_Obj *fmtObj,
			    Tk_PhotoHandle imageHandle, int destX, int destY,
			    int width, int height, int srcX, int srcY);
static int		FileWritePNG(Tcl_Interp *interp, const char *filename,
			    Tcl_Obj *fmtObj, Tk_PhotoImageBlock *blockPtr);
static int		InitPNGImage(Tcl_Interp *interp, PNGImage *pngPtr,
			    Tcl_Channel chan, Tcl_Obj *objPtr, int dir);
static inline unsigned char Paeth(int a, int b, int c);
static int		ParseFormat(Tcl_Interp *interp, Tcl_Obj *fmtObj,
			    PNGImage *pngPtr);
static int		ReadBase64(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned char *destPtr, int destSz,
			    unsigned long *crcPtr);
static int		ReadByteArray(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned char *destPtr, int destSz,
			    unsigned long *crcPtr);
static int		ReadData(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned char *destPtr, int destSz,
			    unsigned long *crcPtr);
static int		ReadChunkHeader(Tcl_Interp *interp, PNGImage *pngPtr,
			    int *sizePtr, unsigned long *typePtr,
			    unsigned long *crcPtr);
static int		ReadIDAT(Tcl_Interp *interp, PNGImage *pngPtr,
			    int chunkSz, unsigned long crc);
static int		ReadIHDR(Tcl_Interp *interp, PNGImage *pngPtr);
static inline int	ReadInt32(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned long *resultPtr, unsigned long *crcPtr);
static int		ReadPLTE(Tcl_Interp *interp, PNGImage *pngPtr,
			    int chunkSz, unsigned long crc);
static int		ReadTRNS(Tcl_Interp *interp, PNGImage *pngPtr,
			    int chunkSz, unsigned long crc);
static int		SkipChunk(Tcl_Interp *interp, PNGImage *pngPtr,
			    int chunkSz, unsigned long crc);
static int		StringMatchPNG(Tcl_Obj *dataObj, Tcl_Obj *fmtObj,
			    int *widthPtr, int *heightPtr,
			    Tcl_Interp *interp);
static int		StringReadPNG(Tcl_Interp *interp, Tcl_Obj *dataObj,
			    Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle,
			    int destX, int destY, int width, int height,
			    int srcX, int srcY);
static int		StringWritePNG(Tcl_Interp *interp, Tcl_Obj *fmtObj,
			    Tk_PhotoImageBlock *blockPtr);
static int		UnfilterLine(Tcl_Interp *interp, PNGImage *pngPtr);
static inline int	WriteByte(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned char c, unsigned long *crcPtr);
static inline int	WriteChunk(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned long chunkType,
			    const unsigned char *dataPtr, int dataSize);
static int		WriteData(Tcl_Interp *interp, PNGImage *pngPtr,
			    const unsigned char *srcPtr, int srcSz,
			    unsigned long *crcPtr);
static int		WriteExtraChunks(Tcl_Interp *interp,
			    PNGImage *pngPtr);
static int		WriteIHDR(Tcl_Interp *interp, PNGImage *pngPtr,
			    Tk_PhotoImageBlock *blockPtr);
static int		WriteIDAT(Tcl_Interp *interp, PNGImage *pngPtr,
			    Tk_PhotoImageBlock *blockPtr);
static inline int	WriteInt32(Tcl_Interp *interp, PNGImage *pngPtr,
			    unsigned long l, unsigned long *crcPtr);

/*
 * The format record for the PNG file format:
 */

Tk_PhotoImageFormat tkImgFmtPNG = {
    "png",			/* name */
    FileMatchPNG,		/* fileMatchProc */
    StringMatchPNG,		/* stringMatchProc */
    FileReadPNG,		/* fileReadProc */
    StringReadPNG,		/* stringReadProc */
    FileWritePNG,		/* fileWriteProc */
    StringWritePNG,		/* stringWriteProc */
    NULL
};

/*
 *----------------------------------------------------------------------
 *
 * InitPNGImage --
 *
 *	This function is invoked by each of the Tk image handler procs
 *	(MatchStringProc, etc.) to initialize state information used during
 *	the course of encoding or decoding a PNG image.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if initialization failed.
 *
 * Side effects:
 *	The reference count of the -data Tcl_Obj*, if any, is incremented.
 *
 *----------------------------------------------------------------------
 */

static int
InitPNGImage(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    Tcl_Channel chan,
    Tcl_Obj *objPtr,
    int dir)
{
    memset(pngPtr, 0, sizeof(PNGImage));

    pngPtr->channel = chan;
    pngPtr->alpha = 1.0;

    /*
     * If decoding from a -data string object, increment its reference count
     * for the duration of the decode and get its length and byte array for
     * reading with ReadData().
     */

    if (objPtr) {
	Tcl_IncrRefCount(objPtr);
	pngPtr->objDataPtr = objPtr;
	pngPtr->strDataBuf =
		Tcl_GetByteArrayFromObj(objPtr, &pngPtr->strDataLen);
    }

    /*
     * Initialize the palette transparency table to fully opaque.
     */

    memset(pngPtr->palette, 255, sizeof(pngPtr->palette));

    /*
     * Initialize Zlib inflate/deflate stream.
     */

    if (Tcl_ZlibStreamInit(NULL, dir, TCL_ZLIB_FORMAT_ZLIB,
	    TCL_ZLIB_COMPRESS_DEFAULT, NULL, &pngPtr->stream) != TCL_OK) {
	if (interp) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "zlib initialization failed", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "ZLIB_INIT", NULL);
	}
	if (objPtr) {
	    Tcl_DecrRefCount(objPtr);
	}
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CleanupPNGImage --
 *
 *	This function is invoked by each of the Tk image handler procs
 *	(MatchStringProc, etc.) prior to returning to Tcl in order to clean up
 *	any allocated memory and call other cleanup handlers such as zlib's
 *	inflateEnd/deflateEnd.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The reference count of the -data Tcl_Obj*, if any, is decremented.
 *	Buffers are freed, streams are closed. The PNGImage should not be used
 *	for any purpose without being reinitialized post-cleanup.
 *
 *----------------------------------------------------------------------
 */

static void
CleanupPNGImage(
    PNGImage *pngPtr)
{
    /*
     * Don't need the object containing the -data value anymore.
     */

    if (pngPtr->objDataPtr) {
	Tcl_DecrRefCount(pngPtr->objDataPtr);
    }

    /*
     * Discard pixel buffer.
     */

    if (pngPtr->stream) {
	Tcl_ZlibStreamClose(pngPtr->stream);
    }

    if (pngPtr->block.pixelPtr) {
	ckfree(pngPtr->block.pixelPtr);
    }
    if (pngPtr->thisLineObj) {
	Tcl_DecrRefCount(pngPtr->thisLineObj);
    }
    if (pngPtr->lastLineObj) {
	Tcl_DecrRefCount(pngPtr->lastLineObj);
    }

    memset(pngPtr, 0, sizeof(PNGImage));
}

/*
 *----------------------------------------------------------------------
 *
 * ReadBase64 --
 *
 *	This function is invoked to read the specified number of bytes from
 *	base-64 encoded image data.
 *
 *	Note: It would be better if the Tk_PhotoImage stuff handled this by
 *	creating a channel from the -data value, which would take care of
 *	base64 decoding and made the data readable as if it were coming from a
 *	file.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs.
 *
 * Side effects:
 *	The file position will change. The running CRC is updated if a pointer
 *	to it is provided.
 *
 *----------------------------------------------------------------------
 */

static int
ReadBase64(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned char *destPtr,
    int destSz,
    unsigned long *crcPtr)
{
    static const unsigned char from64[] = {
	0x82, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x80, 0x80,
	0x83, 0x80, 0x80, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x80,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x3e,
	0x83, 0x83, 0x83, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
	0x3b, 0x3c, 0x3d, 0x83, 0x83, 0x83, 0x81, 0x83, 0x83, 0x83, 0x00,
	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
	0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
	0x17, 0x18, 0x19, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x1a, 0x1b,
	0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
	0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
	0x32, 0x33, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
	0x83, 0x83
    };

    /*
     * Definitions for the base-64 decoder.
     */

#define PNG64_SPECIAL	0x80	/* Flag bit */
#define PNG64_SPACE	0x80	/* Whitespace */
#define PNG64_PAD	0x81	/* Padding */
#define PNG64_DONE	0x82	/* End of data */
#define PNG64_BAD	0x83	/* Ooooh, naughty! */

    while (destSz && pngPtr->strDataLen) {
	unsigned char c = 0;
	unsigned char c64 = from64[*pngPtr->strDataBuf++];

	pngPtr->strDataLen--;

	if (PNG64_SPACE == c64) {
	    continue;
	}

	if (c64 & PNG64_SPECIAL) {
	    c = (unsigned char) pngPtr->base64Bits;
	} else {
	    switch (pngPtr->base64State++) {
	    case 0:
		pngPtr->base64Bits = c64 << 2;
		continue;
	    case 1:
		c = (unsigned char) (pngPtr->base64Bits | (c64 >> 4));
		pngPtr->base64Bits = (c64 & 0xF) << 4;
		break;
	    case 2:
		c = (unsigned char) (pngPtr->base64Bits | (c64 >> 2));
		pngPtr->base64Bits = (c64 & 0x3) << 6;
		break;
	    case 3:
		c = (unsigned char) (pngPtr->base64Bits | c64);
		pngPtr->base64State = 0;
		pngPtr->base64Bits = 0;
		break;
	    }
	}

	if (crcPtr) {
	    *crcPtr = Tcl_ZlibCRC32(*crcPtr, &c, 1);
	}

	if (destPtr) {
	    *destPtr++ = c;
	}

	destSz--;

	if (c64 & PNG64_SPECIAL) {
	    break;
	}
    }

    if (destSz) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"unexpected end of image data", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EARLY_END", NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadByteArray --
 *
 *	This function is invoked to read the specified number of bytes from a
 *	non-base64-encoded byte array provided via the -data option.
 *
 *	Note: It would be better if the Tk_PhotoImage stuff handled this by
 *	creating a channel from the -data value and made the data readable as
 *	if it were coming from a file.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs.
 *
 * Side effects:
 *	The file position will change. The running CRC is updated if a pointer
 *	to it is provided.
 *
 *----------------------------------------------------------------------
 */

static int
ReadByteArray(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned char *destPtr,
    int destSz,
    unsigned long *crcPtr)
{
    /*
     * Check to make sure the number of requested bytes are available.
     */

    if (pngPtr->strDataLen < destSz) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"unexpected end of image data", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EARLY_END", NULL);
	return TCL_ERROR;
    }

    while (destSz) {
	int blockSz = PNG_MIN(destSz, PNG_BLOCK_SZ);

	memcpy(destPtr, pngPtr->strDataBuf, blockSz);

	pngPtr->strDataBuf += blockSz;
	pngPtr->strDataLen -= blockSz;

	if (crcPtr) {
	    *crcPtr = Tcl_ZlibCRC32(*crcPtr, destPtr, blockSz);
	}

	destPtr += blockSz;
	destSz -= blockSz;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadData --
 *
 *	This function is invoked to read the specified number of bytes from
 *	the image file or data. It is a wrapper around the choice of byte
 *	array Tcl_Obj or Tcl_Channel which depends on whether the image data
 *	is coming from a file or -data.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs.
 *
 * Side effects:
 *	The file position will change. The running CRC is updated if a pointer
 *	to it is provided.
 *
 *----------------------------------------------------------------------
 */

static int
ReadData(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned char *destPtr,
    int destSz,
    unsigned long *crcPtr)
{
    if (pngPtr->base64Data) {
	return ReadBase64(interp, pngPtr, destPtr, destSz, crcPtr);
    } else if (pngPtr->strDataBuf) {
	return ReadByteArray(interp, pngPtr, destPtr, destSz, crcPtr);
    }

    while (destSz) {
	int blockSz = PNG_MIN(destSz, PNG_BLOCK_SZ);

	blockSz = Tcl_Read(pngPtr->channel, (char *)destPtr, blockSz);
	if (blockSz < 0) {
	    /* TODO: failure info... */
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "channel read failed: %s", Tcl_PosixError(interp)));
	    return TCL_ERROR;
	}

	/*
	 * Update CRC, pointer, and remaining count if anything was read.
	 */

	if (blockSz) {
	    if (crcPtr) {
		*crcPtr = Tcl_ZlibCRC32(*crcPtr, destPtr, blockSz);
	    }

	    destPtr += blockSz;
	    destSz -= blockSz;
	}

	/*
	 * Check for EOF before all desired data was read.
	 */

	if (destSz && Tcl_Eof(pngPtr->channel)) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "unexpected end of file", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EOF", NULL);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadInt32 --
 *
 *	This function is invoked to read a 32-bit integer in network byte
 *	order from the image data and return the value in host byte order.
 *	This is used, for example, to read the 32-bit CRC value for a chunk
 *	stored in the image file for comparison with the calculated CRC value.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs.
 *
 * Side effects:
 *	The file position will change. The running CRC is updated if a pointer
 *	to it is provided.
 *
 *----------------------------------------------------------------------
 */

static inline int
ReadInt32(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned long *resultPtr,
    unsigned long *crcPtr)
{
    unsigned char p[4];

    if (ReadData(interp, pngPtr, p, 4, crcPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    *resultPtr = PNG_INT32(p[0], p[1], p[2], p[3]);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CheckCRC --
 *
 *	This function is reads the final 4-byte integer CRC from a chunk and
 *	compares it to the running CRC calculated over the chunk type and data
 *	fields.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error or CRC mismatch occurs.
 *
 * Side effects:
 *	The file position will change.
 *
 *----------------------------------------------------------------------
 */

static inline int
CheckCRC(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned long calculated)
{
    unsigned long chunked;

    /*
     * Read the CRC field at the end of the chunk.
     */

    if (ReadInt32(interp, pngPtr, &chunked, NULL) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Compare the read CRC to what we calculate to make sure they match.
     */

    if (calculated != chunked) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj("CRC check failed", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "CRC", NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SkipChunk --
 *
 *	This function is used to skip a PNG chunk that is not used by this
 *	implementation. Given the input stream has had the chunk length and
 *	chunk type fields already read, this function will read the number of
 *	bytes indicated by the chunk length, plus four for the CRC, and will
 *	verify that CRC is correct for the skipped data.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error or CRC mismatch occurs.
 *
 * Side effects:
 *	The file position will change.
 *
 *----------------------------------------------------------------------
 */

static int
SkipChunk(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    int chunkSz,
    unsigned long crc)
{
    unsigned char buffer[PNG_BLOCK_SZ];

    /*
     * Skip data in blocks until none is left. Read up to PNG_BLOCK_SZ bytes
     * at a time, rather than trusting the claimed chunk size, which may not
     * be trustworthy.
     */

    while (chunkSz) {
	int blockSz = PNG_MIN(chunkSz, PNG_BLOCK_SZ);

	if (ReadData(interp, pngPtr, buffer, blockSz, &crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	chunkSz -= blockSz;
    }

    if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 * 4.3. Summary of standard chunks
 *
 * This table summarizes some properties of the standard chunk types.
 *
 *	Critical chunks (must appear in this order, except PLTE is optional):
 *
 *		Name   Multiple	 Ordering constraints OK?
 *
 *		IHDR	No	 Must be first
 *		PLTE	No	 Before IDAT
 *		IDAT	Yes	 Multiple IDATs must be consecutive
 *		IEND	No	 Must be last
 *
 *	Ancillary chunks (need not appear in this order):
 *
 *		Name   Multiple	 Ordering constraints OK?
 *
 *		cHRM	No	 Before PLTE and IDAT
 *		gAMA	No	 Before PLTE and IDAT
 *		iCCP	No	 Before PLTE and IDAT
 *		sBIT	No	 Before PLTE and IDAT
 *		sRGB	No	 Before PLTE and IDAT
 *		bKGD	No	 After PLTE; before IDAT
 *		hIST	No	 After PLTE; before IDAT
 *		tRNS	No	 After PLTE; before IDAT
 *		pHYs	No	 Before IDAT
 *		sPLT	Yes	 Before IDAT
 *		tIME	No	 None
 *		iTXt	Yes	 None
 *		tEXt	Yes	 None
 *		zTXt	Yes	 None
 *
 *	[From the PNG specification.]
 */

/*
 *----------------------------------------------------------------------
 *
 * ReadChunkHeader --
 *
 *	This function is used at the start of each chunk to extract the
 *	four-byte chunk length and four-byte chunk type fields. It will
 *	continue reading until it finds a chunk type that is handled by this
 *	implementation, checking the CRC of any chunks it skips.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs or an unknown critical
 *	chunk type is encountered.
 *
 * Side effects:
 *	The file position will change. The running CRC is updated.
 *
 *----------------------------------------------------------------------
 */

static int
ReadChunkHeader(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    int *sizePtr,
    unsigned long *typePtr,
    unsigned long *crcPtr)
{
    unsigned long chunkType = 0;
    int chunkSz = 0;
    unsigned long crc = 0;

    /*
     * Continue until finding a chunk type that is handled.
     */

    while (!chunkType) {
	unsigned long temp;
	unsigned char pc[4];
	int i;

	/*
	 * Read the 4-byte length field for the chunk. The length field is not
	 * included in the CRC calculation, so the running CRC must be reset
	 * afterward. Limit chunk lengths to INT_MAX, to align with the
	 * maximum size for Tcl_Read, Tcl_GetByteArrayFromObj, etc.
	 */

	if (ReadData(interp, pngPtr, pc, 4, NULL) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	temp = PNG_INT32(pc[0], pc[1], pc[2], pc[3]);

	if (temp > INT_MAX) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "chunk size is out of supported range on this architecture",
		    -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "OUTSIZE", NULL);
	    return TCL_ERROR;
	}

	chunkSz = (int) temp;
	crc = Tcl_ZlibCRC32(0, NULL, 0);

	/*
	 * Read the 4-byte chunk type.
	 */

	if (ReadData(interp, pngPtr, pc, 4, &crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	/*
	 * Convert it to a host-order integer for simple comparison.
	 */

	chunkType = PNG_INT32(pc[0], pc[1], pc[2], pc[3]);

	/*
	 * Check to see if this is a known/supported chunk type. Note that the
	 * PNG specs require non-critical (i.e., ancillary) chunk types that
	 * are not recognized to be ignored, rather than be treated as an
	 * error. It does, however, recommend that an unknown critical chunk
	 * type be treated as a failure.
	 *
	 * This switch/loop acts as a filter of sorts for undesired chunk
	 * types. The chunk type should still be checked elsewhere for
	 * determining it is in the correct order.
	 */

	switch (chunkType) {
	    /*
	     * These chunk types are required and/or supported.
	     */

	case CHUNK_IDAT:
	case CHUNK_IEND:
	case CHUNK_IHDR:
	case CHUNK_PLTE:
	case CHUNK_tRNS:
	    break;

	    /*
	     * These chunk types are part of the standard, but are not used by
	     * this implementation (at least not yet). Note that these are all
	     * ancillary chunks (lowercase first letter).
	     */

	case CHUNK_bKGD:
	case CHUNK_cHRM:
	case CHUNK_gAMA:
	case CHUNK_hIST:
	case CHUNK_iCCP:
	case CHUNK_iTXt:
	case CHUNK_oFFs:
	case CHUNK_pCAL:
	case CHUNK_pHYs:
	case CHUNK_sBIT:
	case CHUNK_sCAL:
	case CHUNK_sPLT:
	case CHUNK_sRGB:
	case CHUNK_tEXt:
	case CHUNK_tIME:
	case CHUNK_zTXt:
	    /*
	     * TODO: might want to check order here.
	     */

	    if (SkipChunk(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
		return TCL_ERROR;
	    }

	    chunkType = 0;
	    break;

	default:
	    /*
	     * Unknown chunk type. If it's critical, we can't continue.
	     */

	    if (!(chunkType & PNG_CF_ANCILLARY)) {
		if (chunkType & PNG_INT32(128,128,128,128)) {
		    /*
		     * No nice ASCII conversion; shouldn't happen either, but
		     * we'll be doubly careful.
		     */

		    Tcl_SetObjResult(interp, Tcl_NewStringObj(
			    "encountered an unsupported critical chunk type",
			    -1));
		} else {
		    char typeString[5];

		    typeString[0] = (char) ((chunkType >> 24) & 255);
		    typeString[1] = (char) ((chunkType >> 16) & 255);
		    typeString[2] = (char) ((chunkType >> 8) & 255);
		    typeString[3] = (char) (chunkType & 255);
		    typeString[4] = '\0';
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			    "encountered an unsupported critical chunk type"
			    " \"%s\"", typeString));
		}
		Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG",
			"UNSUPPORTED_CRITICAL", NULL);
		return TCL_ERROR;
	    }

	    /*
	     * Check to see if the chunk type has legal bytes.
	     */

	    for (i=0 ; i<4 ; i++) {
		if ((pc[i] < 65) || (pc[i] > 122) ||
			((pc[i] > 90) && (pc[i] < 97))) {
		    Tcl_SetObjResult(interp, Tcl_NewStringObj(
			    "invalid chunk type", -1));
		    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG",
			    "INVALID_CHUNK", NULL);
		    return TCL_ERROR;
		}
	    }

	    /*
	     * It seems to be an otherwise legally labelled ancillary chunk
	     * that we don't want, so skip it after at least checking its CRC.
	     */

	    if (SkipChunk(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
		return TCL_ERROR;
	    }

	    chunkType = 0;
	}
    }

    /*
     * Found a known chunk type that's handled, albiet possibly not in the
     * right order. Send back the chunk type (for further checking or
     * handling), the chunk size and the current CRC for the rest of the
     * calculation.
     */

    *typePtr = chunkType;
    *sizePtr = chunkSz;
    *crcPtr = crc;

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * CheckColor --
 *
 *	Do validation on color type, depth, and related information, and
 *	calculates storage requirements and offsets based on image dimensions
 *	and color.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if color information is invalid or some other
 *	failure occurs.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
CheckColor(
    Tcl_Interp *interp,
    PNGImage *pngPtr)
{
    int offset;

    /*
     * Verify the color type is valid and the bit depth is allowed.
     */

    switch (pngPtr->colorType) {
    case PNG_COLOR_GRAY:
	pngPtr->numChannels = 1;
	if ((1 != pngPtr->bitDepth) && (2 != pngPtr->bitDepth) &&
		(4 != pngPtr->bitDepth) && (8 != pngPtr->bitDepth) &&
		(16 != pngPtr->bitDepth)) {
	    goto unsupportedDepth;
	}
	break;

    case PNG_COLOR_RGB:
	pngPtr->numChannels = 3;
	if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) {
	    goto unsupportedDepth;
	}
	break;

    case PNG_COLOR_PLTE:
	pngPtr->numChannels = 1;
	if ((1 != pngPtr->bitDepth) && (2 != pngPtr->bitDepth) &&
		(4 != pngPtr->bitDepth) && (8 != pngPtr->bitDepth)) {
	    goto unsupportedDepth;
	}
	break;

    case PNG_COLOR_GRAYALPHA:
	pngPtr->numChannels = 2;
	if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) {
	    goto unsupportedDepth;
	}
	break;

    case PNG_COLOR_RGBA:
	pngPtr->numChannels = 4;
	if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) {
	unsupportedDepth:
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "bit depth is not allowed for given color type", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_DEPTH", NULL);
	    return TCL_ERROR;
	}
	break;

    default:
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"unknown color type field %d", pngPtr->colorType));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "UNKNOWN_COLOR", NULL);
	return TCL_ERROR;
    }

    /*
     * Set up the Tk photo block's pixel size and channel offsets. offset
     * array elements should already be 0 from the memset during InitPNGImage.
     */

    offset = (pngPtr->bitDepth > 8) ? 2 : 1;

    if (pngPtr->colorType & PNG_COLOR_USED) {
	pngPtr->block.pixelSize = offset * 4;
	pngPtr->block.offset[1] = offset;
	pngPtr->block.offset[2] = offset * 2;
	pngPtr->block.offset[3] = offset * 3;
    } else {
	pngPtr->block.pixelSize = offset * 2;
	pngPtr->block.offset[3] = offset;
    }

    /*
     * Calculate the block pitch, which is the number of bytes per line in the
     * image, given image width and depth of color. Make sure that it it isn't
     * larger than Tk can handle.
     */

    if (pngPtr->block.width > INT_MAX / pngPtr->block.pixelSize) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"image pitch is out of supported range on this architecture",
		-1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "PITCH", NULL);
	return TCL_ERROR;
    }

    pngPtr->block.pitch = pngPtr->block.pixelSize * pngPtr->block.width;

    /*
     * Calculate the total size of the image as represented to Tk given pitch
     * and image height. Make sure that it isn't larger than Tk can handle.
     */

    if (pngPtr->block.height > INT_MAX / pngPtr->block.pitch) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"image total size is out of supported range on this architecture",
		-1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "SIZE", NULL);
	return TCL_ERROR;
    }

    pngPtr->blockLen = pngPtr->block.height * pngPtr->block.pitch;

    /*
     * Determine number of bytes per pixel in the source for later use.
     */

    switch (pngPtr->colorType) {
    case PNG_COLOR_GRAY:
	pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 2 : 1;
	break;
    case PNG_COLOR_RGB:
	pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 6 : 3;
	break;
    case PNG_COLOR_PLTE:
	pngPtr->bytesPerPixel = 1;
	break;
    case PNG_COLOR_GRAYALPHA:
	pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 4 : 2;
	break;
    case PNG_COLOR_RGBA:
	pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 8 : 4;
	break;
    default:
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"unknown color type %d", pngPtr->colorType));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "UNKNOWN_COLOR", NULL);
	return TCL_ERROR;
    }

    /*
     * Calculate scale factor for bit depths less than 8, in order to adjust
     * them to a minimum of 8 bits per pixel in the Tk image.
     */

    if (pngPtr->bitDepth < 8) {
	pngPtr->bitScale = 255 / (int)(pow(2, pngPtr->bitDepth) - 1);
    } else {
	pngPtr->bitScale = 1;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadIHDR --
 *
 *	This function reads the PNG header from the beginning of a PNG file
 *	and returns the dimensions of the image.
 *
 * Results:
 *	The return value is 1 if file "f" appears to start with a valid PNG
 *	header, 0 otherwise. If the header is valid, then *widthPtr and
 *	*heightPtr are modified to hold the dimensions of the image.
 *
 * Side effects:
 *	The access position in f advances.
 *
 *----------------------------------------------------------------------
 */

static int
ReadIHDR(
    Tcl_Interp *interp,
    PNGImage *pngPtr)
{
    unsigned char sigBuf[PNG_SIG_SZ];
    unsigned long chunkType;
    int chunkSz;
    unsigned long crc;
    unsigned long width, height;
    int mismatch;

    /*
     * Read the appropriate number of bytes for the PNG signature.
     */

    if (ReadData(interp, pngPtr, sigBuf, PNG_SIG_SZ, NULL) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Compare the read bytes to the expected signature.
     */

    mismatch = memcmp(sigBuf, pngSignature, PNG_SIG_SZ);

    /*
     * If reading from string, reset position and try base64 decode.
     */

    if (mismatch && pngPtr->strDataBuf) {
	pngPtr->strDataBuf = Tcl_GetByteArrayFromObj(pngPtr->objDataPtr,
		&pngPtr->strDataLen);
	pngPtr->base64Data = pngPtr->strDataBuf;

	if (ReadData(interp, pngPtr, sigBuf, PNG_SIG_SZ, NULL) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	mismatch = memcmp(sigBuf, pngSignature, PNG_SIG_SZ);
    }

    if (mismatch) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"data stream does not have a PNG signature", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NO_SIG", NULL);
	return TCL_ERROR;
    }

    if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
	    &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Read in the IHDR (header) chunk for width, height, etc.
     *
     * The first chunk in the file must be the IHDR (headr) chunk.
     */

    if (chunkType != CHUNK_IHDR) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"expected IHDR chunk type", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NO_IHDR", NULL);
	return TCL_ERROR;
    }

    if (chunkSz != 13) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"invalid IHDR chunk size", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_IHDR", NULL);
	return TCL_ERROR;
    }

    /*
     * Read and verify the image width and height to be sure Tk can handle its
     * dimensions. The PNG specification does not permit zero-width or
     * zero-height images.
     */

    if (ReadInt32(interp, pngPtr, &width, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (ReadInt32(interp, pngPtr, &height, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (!width || !height || (width > INT_MAX) || (height > INT_MAX)) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"image dimensions are invalid or beyond architecture limits",
		-1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "DIMENSIONS", NULL);
	return TCL_ERROR;
    }

    /*
     * Set height and width for the Tk photo block.
     */

    pngPtr->block.width = (int) width;
    pngPtr->block.height = (int) height;

    /*
     * Read and the Bit Depth and Color Type.
     */

    if (ReadData(interp, pngPtr, &pngPtr->bitDepth, 1, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (ReadData(interp, pngPtr, &pngPtr->colorType, 1, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Verify that the color type is valid, the bit depth is allowed for the
     * color type, and calculate the number of channels and pixel depth (bits
     * per pixel * channels). Also set up offsets and sizes in the Tk photo
     * block for the pixel data.
     */

    if (CheckColor(interp, pngPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Only one compression method is currently defined by the standard.
     */

    if (ReadData(interp, pngPtr, &pngPtr->compression, 1, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (pngPtr->compression != PNG_COMPRESS_DEFLATE) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"unknown compression method %d", pngPtr->compression));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_COMPRESS", NULL);
	return TCL_ERROR;
    }

    /*
     * Only one filter method is currently defined by the standard; the method
     * has five actual filter types associated with it.
     */

    if (ReadData(interp, pngPtr, &pngPtr->filter, 1, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (pngPtr->filter != PNG_FILTMETH_STANDARD) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"unknown filter method %d", pngPtr->filter));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_FILTER", NULL);
	return TCL_ERROR;
    }

    if (ReadData(interp, pngPtr, &pngPtr->interlace, 1, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    switch (pngPtr->interlace) {
    case PNG_INTERLACE_NONE:
    case PNG_INTERLACE_ADAM7:
	break;

    default:
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"unknown interlace method %d", pngPtr->interlace));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_INTERLACE", NULL);
	return TCL_ERROR;
    }

    return CheckCRC(interp, pngPtr, crc);
}

/*
 *----------------------------------------------------------------------
 *
 * ReadPLTE --
 *
 *	This function reads the PLTE (indexed color palette) chunk data from
 *	the PNG file and populates the palette table in the PNGImage
 *	structure.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs or the PLTE chunk is
 *	invalid.
 *
 * Side effects:
 *	The access position in f advances.
 *
 *----------------------------------------------------------------------
 */

static int
ReadPLTE(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    int chunkSz,
    unsigned long crc)
{
    unsigned char buffer[PNG_PLTE_MAXSZ];
    int i, c;

    /*
     * This chunk is mandatory for color type 3 and forbidden for 2 and 6.
     */

    switch (pngPtr->colorType) {
    case PNG_COLOR_GRAY:
    case PNG_COLOR_GRAYALPHA:
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"PLTE chunk type forbidden for grayscale", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "PLTE_UNEXPECTED",
		NULL);
	return TCL_ERROR;

    default:
	break;
    }

    /*
     * The palette chunk contains from 1 to 256 palette entries. Each entry
     * consists of a 3-byte RGB value. It must therefore contain a non-zero
     * multiple of 3 bytes, up to 768.
     */

    if (!chunkSz || (chunkSz > PNG_PLTE_MAXSZ) || (chunkSz % 3)) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"invalid palette chunk size", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_PLTE", NULL);
	return TCL_ERROR;
    }

    /*
     * Read the palette contents and stash them for later, possibly.
     */

    if (ReadData(interp, pngPtr, buffer, chunkSz, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Stash away the palette entries and entry count for later mapping each
     * pixel's palette index to its color.
     */

    for (i=0, c=0 ; c<chunkSz ; i++) {
	pngPtr->palette[i].red = buffer[c++];
	pngPtr->palette[i].green = buffer[c++];
	pngPtr->palette[i].blue = buffer[c++];
    }

    pngPtr->paletteLen = i;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadTRNS --
 *
 *	This function reads the tRNS (transparency) chunk data from the PNG
 *	file and populates the alpha field of the palette table in the
 *	PNGImage structure or the single color transparency, as appropriate
 *	for the color type.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs or the tRNS chunk is
 *	invalid.
 *
 * Side effects:
 *	The access position in f advances.
 *
 *----------------------------------------------------------------------
 */

static int
ReadTRNS(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    int chunkSz,
    unsigned long crc)
{
    unsigned char buffer[PNG_TRNS_MAXSZ];
    int i;

    if (pngPtr->colorType & PNG_COLOR_ALPHA) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"tRNS chunk not allowed color types with a full alpha channel",
		-1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "INVALID_TRNS", NULL);
	return TCL_ERROR;
    }

    /*
     * For indexed color, there is up to one single-byte transparency value
     * per palette entry (thus a max of 256).
     */

    if (chunkSz > PNG_TRNS_MAXSZ) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"invalid tRNS chunk size", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_TRNS", NULL);
	return TCL_ERROR;
    }

    /*
     * Read in the raw transparency information.
     */

    if (ReadData(interp, pngPtr, buffer, chunkSz, &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    switch (pngPtr->colorType) {
    case PNG_COLOR_GRAYALPHA:
    case PNG_COLOR_RGBA:
	break;

    case PNG_COLOR_PLTE:
	/*
	 * The number of tRNS entries must be less than or equal to the number
	 * of PLTE entries, and consists of a single-byte alpha level for the
	 * corresponding PLTE entry.
	 */

	if (chunkSz > pngPtr->paletteLen) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "size of tRNS chunk is too large for the palette", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "TRNS_SIZE", NULL);
	    return TCL_ERROR;
	}

	for (i=0 ; i<chunkSz ; i++) {
	    pngPtr->palette[i].alpha = buffer[i];
	}
	break;

    case PNG_COLOR_GRAY:
	/*
	 * Grayscale uses a single 2-byte gray level, which we'll store in
	 * palette index 0, since we're not using the palette.
	 */

	if (chunkSz != 2) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "invalid tRNS chunk size - must 2 bytes for grayscale",
		    -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_TRNS", NULL);
	    return TCL_ERROR;
	}

	/*
	 * According to the PNG specs, if the bit depth is less than 16, then
	 * only the lower byte is used.
	 */

	if (16 == pngPtr->bitDepth) {
	    pngPtr->transVal[0] = buffer[0];
	    pngPtr->transVal[1] = buffer[1];
	} else {
	    pngPtr->transVal[0] = buffer[1];
	}
	pngPtr->useTRNS = 1;
	break;

    case PNG_COLOR_RGB:
	/*
	 * TrueColor uses a single RRGGBB triplet.
	 */

	if (chunkSz != 6) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "invalid tRNS chunk size - must 6 bytes for RGB", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_TRNS", NULL);
	    return TCL_ERROR;
	}

	/*
	 * According to the PNG specs, if the bit depth is less than 16, then
	 * only the lower byte is used. But the tRNS chunk still contains two
	 * bytes per channel.
	 */

	if (16 == pngPtr->bitDepth) {
	    memcpy(pngPtr->transVal, buffer, 6);
	} else {
	    pngPtr->transVal[0] = buffer[1];
	    pngPtr->transVal[1] = buffer[3];
	    pngPtr->transVal[2] = buffer[5];
	}
	pngPtr->useTRNS = 1;
	break;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Paeth --
 *
 *	Utility function for applying the Paeth filter to a pixel. The Paeth
 *	filter is a linear function of the pixel to be filtered and the pixels
 *	to the left, above, and above-left of the pixel to be unfiltered.
 *
 * Results:
 *	Result of the Paeth function for the left, above, and above-left
 *	pixels.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static inline unsigned char
Paeth(
    int a,
    int b,
    int c)
{
    int pa = abs(b - c);
    int pb = abs(a - c);
    int pc = abs(a + b - c - c);

    if ((pa <= pb) && (pa <= pc)) {
	return (unsigned char) a;
    }

    if (pb <= pc) {
	return (unsigned char) b;
    }

    return (unsigned char) c;
}

/*
 *----------------------------------------------------------------------
 *
 * UnfilterLine --
 *
 *	Applies the filter algorithm specified in first byte of a line to the
 *	line of pixels being read from a PNG image.
 *
 *	PNG specifies four filter algorithms (Sub, Up, Average, and Paeth)
 *	that combine a pixel's value with those of other pixels in the same
 *	and/or previous lines. Filtering is intended to make an image more
 *	compressible.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the filter type is not recognized.
 *
 * Side effects:
 *	Pixel data in thisLineObj are modified.
 *
 *----------------------------------------------------------------------
 */

static int
UnfilterLine(
    Tcl_Interp *interp,
    PNGImage *pngPtr)
{
    unsigned char *thisLine =
	    Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, NULL);
    unsigned char *lastLine =
	    Tcl_GetByteArrayFromObj(pngPtr->lastLineObj, NULL);

#define	PNG_FILTER_NONE		0
#define	PNG_FILTER_SUB		1
#define	PNG_FILTER_UP		2
#define	PNG_FILTER_AVG		3
#define	PNG_FILTER_PAETH	4

    switch (*thisLine) {
    case PNG_FILTER_NONE:	/* Nothing to do */
	break;
    case PNG_FILTER_SUB: {	/* Sub(x) = Raw(x) - Raw(x-bpp) */
	unsigned char *rawBpp = thisLine + 1;
	unsigned char *raw = rawBpp + pngPtr->bytesPerPixel;
	unsigned char *end = thisLine + pngPtr->phaseSize;

	while (raw < end) {
	    *raw++ += *rawBpp++;
	}
	break;
    }
    case PNG_FILTER_UP:		/* Up(x) = Raw(x) - Prior(x) */
	if (pngPtr->currentLine > startLine[pngPtr->phase]) {
	    unsigned char *prior = lastLine + 1;
	    unsigned char *raw = thisLine + 1;
	    unsigned char *end = thisLine + pngPtr->phaseSize;

	    while (raw < end) {
		*raw++ += *prior++;
	    }
	}
	break;
    case PNG_FILTER_AVG:
	/* Avg(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) */
	if (pngPtr->currentLine > startLine[pngPtr->phase]) {
	    unsigned char *prior = lastLine + 1;
	    unsigned char *rawBpp = thisLine + 1;
	    unsigned char *raw = rawBpp;
	    unsigned char *end = thisLine + pngPtr->phaseSize;
	    unsigned char *end2 = raw + pngPtr->bytesPerPixel;

	    while ((raw < end2) && (raw < end)) {
		*raw++ += *prior++ / 2;
	    }

	    while (raw < end) {
		*raw++ += (unsigned char)
			(((int) *rawBpp++ + (int) *prior++) / 2);
	    }
	} else {
	    unsigned char *rawBpp = thisLine + 1;
	    unsigned char *raw = rawBpp + pngPtr->bytesPerPixel;
	    unsigned char *end = thisLine + pngPtr->phaseSize;

	    while (raw < end) {
		*raw++ += *rawBpp++ / 2;
	    }
	}
	break;
    case PNG_FILTER_PAETH:
	/* Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) */
	if (pngPtr->currentLine > startLine[pngPtr->phase]) {
	    unsigned char *priorBpp = lastLine + 1;
	    unsigned char *prior = priorBpp;
	    unsigned char *rawBpp = thisLine + 1;
	    unsigned char *raw = rawBpp;
	    unsigned char *end = thisLine + pngPtr->phaseSize;
	    unsigned char *end2 = rawBpp + pngPtr->bytesPerPixel;

	    while ((raw < end) && (raw < end2)) {
		*raw++ += *prior++;
	    }

	    while (raw < end) {
		*raw++ += Paeth(*rawBpp++, *prior++, *priorBpp++);
	    }
	} else {
	    unsigned char *rawBpp = thisLine + 1;
	    unsigned char *raw = rawBpp + pngPtr->bytesPerPixel;
	    unsigned char *end = thisLine + pngPtr->phaseSize;

	    while (raw < end) {
		*raw++ += *rawBpp++;
	    }
	}
	break;
    default:
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"invalid filter type %d", *thisLine));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_FILTER", NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DecodeLine --
 *
 *	Unfilters a line of pixels from the PNG source data and decodes the
 *	data into the Tk_PhotoImageBlock for later copying into the Tk image.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the filter type is not recognized.
 *
 * Side effects:
 *	Pixel data in thisLine and block are modified and state information
 *	updated.
 *
 *----------------------------------------------------------------------
 */

static int
DecodeLine(
    Tcl_Interp *interp,
    PNGImage *pngPtr)
{
    unsigned char *pixelPtr = pngPtr->block.pixelPtr;
    int colNum = 0;		/* Current pixel column */
    unsigned char chan = 0;	/* Current channel (0..3) = (R, G, B, A) */
    unsigned char readByte = 0;	/* Current scan line byte */
    int haveBits = 0;		/* Number of bits remaining in current byte */
    unsigned char pixBits = 0;	/* Extracted bits for current channel */
    int shifts = 0;		/* Number of channels extracted from byte */
    int offset = 0;		/* Current offset into pixelPtr */
    int colStep = 1;		/* Column increment each pass */
    int pixStep = 0;		/* extra pixelPtr increment each pass */
    unsigned char lastPixel[6];
    unsigned char *p = Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, NULL);

    p++;
    if (UnfilterLine(interp, pngPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }
    if (pngPtr->currentLine >= pngPtr->block.height) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"PNG image data overflow"));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "DATA_OVERFLOW", NULL);
	return TCL_ERROR;
    }


    if (pngPtr->interlace) {
	switch (pngPtr->phase) {
	case 1:			/* Phase 1: */
	    colStep = 8;	/* 1 pixel per block of 8 per line */
	    break;		/* Start at column 0 */
	case 2:			/* Phase 2: */
	    colStep = 8;	/* 1 pixels per block of 8 per line */
	    colNum = 4;		/* Start at column 4 */
	    break;
	case 3:			/* Phase 3: */
	    colStep = 4;	/* 2 pixels per block of 8 per line */
	    break;		/* Start at column 0 */
	case 4:			/* Phase 4: */
	    colStep = 4;	/* 2 pixels per block of 8 per line */
	    colNum = 2;		/* Start at column 2 */
	    break;
	case 5:			/* Phase 5: */
	    colStep = 2;	/* 4 pixels per block of 8 per line */
	    break;		/* Start at column 0 */
	case 6:			/* Phase 6: */
	    colStep = 2;	/* 4 pixels per block of 8 per line */
	    colNum = 1;		/* Start at column 1 */
	    break;
				/* Phase 7: */
				/* 8 pixels per block of 8 per line */
				/* Start at column 0 */
	}
    }

    /*
     * Calculate offset into pixelPtr for the first pixel of the line.
     */

    offset = pngPtr->currentLine * pngPtr->block.pitch;

    /*
     * Adjust up for the starting pixel of the line.
     */

    offset += colNum * pngPtr->block.pixelSize;

    /*
     * Calculate the extra number of bytes to skip between columns.
     */

    pixStep = (colStep - 1) * pngPtr->block.pixelSize;

    for ( ; colNum < pngPtr->block.width ; colNum += colStep) {
	if (haveBits < (pngPtr->bitDepth * pngPtr->numChannels)) {
	    haveBits = 0;
	}

	for (chan = 0 ; chan < pngPtr->numChannels ; chan++) {
	    if (!haveBits) {
		shifts = 0;
		readByte = *p++;
		haveBits += 8;
	    }

	    if (16 == pngPtr->bitDepth) {
		pngPtr->block.pixelPtr[offset++] = readByte;

		if (pngPtr->useTRNS) {
		    lastPixel[chan * 2] = readByte;
		}

		readByte = *p++;

		if (pngPtr->useTRNS) {
		    lastPixel[(chan * 2) + 1] = readByte;
		}

		pngPtr->block.pixelPtr[offset++] = readByte;

		haveBits = 0;
		continue;
	    }

	    switch (pngPtr->bitDepth) {
	    case 1:
		pixBits = (unsigned char)((readByte >> (7-shifts)) & 0x01);
		break;
	    case 2:
		pixBits = (unsigned char)((readByte >> (6-shifts*2)) & 0x03);
		break;
	    case 4:
		pixBits = (unsigned char)((readByte >> (4-shifts*4)) & 0x0f);
		break;
	    case 8:
		pixBits = readByte;
		break;
	    }

	    if (PNG_COLOR_PLTE == pngPtr->colorType) {
		pixelPtr[offset++] = pngPtr->palette[pixBits].red;
		pixelPtr[offset++] = pngPtr->palette[pixBits].green;
		pixelPtr[offset++] = pngPtr->palette[pixBits].blue;
		pixelPtr[offset++] = pngPtr->palette[pixBits].alpha;
		chan += 2;
	    } else {
		pixelPtr[offset++] = (unsigned char)
			(pixBits * pngPtr->bitScale);

		if (pngPtr->useTRNS) {
		    lastPixel[chan] = pixBits;
		}
	    }

	    haveBits -= pngPtr->bitDepth;
	    shifts++;
	}

	/*
	 * Apply boolean transparency via tRNS data if necessary (where
	 * necessary means a tRNS chunk was provided and we're not using an
	 * alpha channel or indexed alpha).
	 */

	if ((PNG_COLOR_PLTE != pngPtr->colorType) &&
		!(pngPtr->colorType & PNG_COLOR_ALPHA)) {
	    unsigned char alpha;

	    if (pngPtr->useTRNS) {
		if (memcmp(lastPixel, pngPtr->transVal,
			pngPtr->bytesPerPixel) == 0) {
		    alpha = 0x00;
		} else {
		    alpha = 0xff;
		}
	    } else {
		alpha = 0xff;
	    }

	    pixelPtr[offset++] = alpha;

	    if (16 == pngPtr->bitDepth) {
		pixelPtr[offset++] = alpha;
	    }
	}

	offset += pixStep;
    }

    if (pngPtr->interlace) {
	/* Skip lines */

	switch (pngPtr->phase) {
	case 1: case 2: case 3:
	    pngPtr->currentLine += 8;
	    break;
	case 4: case 5:
	    pngPtr->currentLine += 4;
	    break;
	case 6: case 7:
	    pngPtr->currentLine += 2;
	    break;
	}

	/*
	 * Start the next phase if there are no more lines to do.
	 */

	if (pngPtr->currentLine >= pngPtr->block.height) {
	    unsigned long pixels = 0;

	    while ((!pixels || (pngPtr->currentLine >= pngPtr->block.height))
		    && (pngPtr->phase < 7)) {
		pngPtr->phase++;

		switch (pngPtr->phase) {
		case 2:
		    pixels = (pngPtr->block.width + 3) >> 3;
		    pngPtr->currentLine = 0;
		    break;
		case 3:
		    pixels = (pngPtr->block.width + 3) >> 2;
		    pngPtr->currentLine = 4;
		    break;
		case 4:
		    pixels = (pngPtr->block.width + 1) >> 2;
		    pngPtr->currentLine = 0;
		    break;
		case 5:
		    pixels = (pngPtr->block.width + 1) >> 1;
		    pngPtr->currentLine = 2;
		    break;
		case 6:
		    pixels = pngPtr->block.width >> 1;
		    pngPtr->currentLine = 0;
		    break;
		case 7:
		    pngPtr->currentLine = 1;
		    pixels = pngPtr->block.width;
		    break;
		}
	    }

	    if (16 == pngPtr->bitDepth) {
		pngPtr->phaseSize = 1 + (pngPtr->numChannels * pixels * 2);
	    } else {
		pngPtr->phaseSize = 1 + ((pngPtr->numChannels * pixels *
			pngPtr->bitDepth + 7) >> 3);
	    }
	}
    } else {
	pngPtr->currentLine++;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ReadIDAT --
 *
 *	This function reads the IDAT (pixel data) chunk from the PNG file to
 *	build the image. It will continue reading until all IDAT chunks have
 *	been processed or an error occurs.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs or an IDAT chunk is
 *	invalid.
 *
 * Side effects:
 *	The access position in f advances. Memory may be allocated by zlib
 *	through PNGZAlloc.
 *
 *----------------------------------------------------------------------
 */

static int
ReadIDAT(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    int chunkSz,
    unsigned long crc)
{
    /*
     * Process IDAT contents until there is no more in this chunk.
     */

    while (chunkSz && !Tcl_ZlibStreamEof(pngPtr->stream)) {
	int len1, len2;

	/*
	 * Read another block of input into the zlib stream if data remains.
	 */

	if (chunkSz) {
	    Tcl_Obj *inputObj = NULL;
	    int blockSz = PNG_MIN(chunkSz, PNG_BLOCK_SZ);
	    unsigned char *inputPtr = NULL;

	    /*
	     * Check for end of zlib stream.
	     */

	    if (Tcl_ZlibStreamEof(pngPtr->stream)) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(
			"extra data after end of zlib stream", -1));
		Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA",
			NULL);
		return TCL_ERROR;
	    }

	    inputObj = Tcl_NewObj();
	    Tcl_IncrRefCount(inputObj);
	    inputPtr = Tcl_SetByteArrayLength(inputObj, blockSz);

	    /*
	     * Read the next bit of IDAT chunk data, up to read buffer size.
	     */

	    if (ReadData(interp, pngPtr, inputPtr, blockSz,
		    &crc) == TCL_ERROR) {
		Tcl_DecrRefCount(inputObj);
		return TCL_ERROR;
	    }

	    chunkSz -= blockSz;

	    Tcl_ZlibStreamPut(pngPtr->stream, inputObj, TCL_ZLIB_NO_FLUSH);
	    Tcl_DecrRefCount(inputObj);
	}

	/*
	 * Inflate, processing each output buffer's worth as a line of pixels,
	 * until we cannot fill the buffer any more.
	 */

    getNextLine:
	Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, &len1);
	if (Tcl_ZlibStreamGet(pngPtr->stream, pngPtr->thisLineObj,
		pngPtr->phaseSize - len1) == TCL_ERROR) {
	    return TCL_ERROR;
	}
	Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, &len2);

	if (len2 == pngPtr->phaseSize) {
	    if (pngPtr->phase > 7) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(
			"extra data after final scan line of final phase",
			-1));
		Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA",
			NULL);
		return TCL_ERROR;
	    }

	    if (DecodeLine(interp, pngPtr) == TCL_ERROR) {
		return TCL_ERROR;
	    }

	    /*
	     * Swap the current/last lines so that we always have the last
	     * line processed available, which is necessary for filtering.
	     */

	    {
		Tcl_Obj *temp = pngPtr->lastLineObj;

		pngPtr->lastLineObj = pngPtr->thisLineObj;
		pngPtr->thisLineObj = temp;
	    }
	    Tcl_SetByteArrayLength(pngPtr->thisLineObj, 0);

	    /*
	     * Try to read another line of pixels out of the buffer
	     * immediately, but don't allow write past end of block.
	     */

	    if (pngPtr->currentLine < pngPtr->block.height) {
		goto getNextLine;
	    }

	}

	/*
	 * Got less than a whole buffer-load of pixels. Either we're going to
	 * be getting more data from the next IDAT, or we've done what we can
	 * here.
	 */
    }

    /*
     * Ensure that if we've got to the end of the compressed data, we've
     * also got to the end of the compressed stream. This sanity check is
     * enforced by most PNG readers.
     */

    if (chunkSz != 0) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"compressed data after stream finalize in PNG data", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA", NULL);
	return TCL_ERROR;
    }

    return CheckCRC(interp, pngPtr, crc);
}

/*
 *----------------------------------------------------------------------
 *
 * ApplyAlpha --
 *
 *	Applies an overall alpha value to a complete image that has been read.
 *	This alpha value is specified using the -format option to [image
 *	create photo].
 *
 * Results:
 *	N/A
 *
 * Side effects:
 *	The access position in f may change.
 *
 *----------------------------------------------------------------------
 */

static void
ApplyAlpha(
    PNGImage *pngPtr)
{
    if (pngPtr->alpha != 1.0) {
	register unsigned char *p = pngPtr->block.pixelPtr;
	unsigned char *endPtr = p + pngPtr->blockLen;
	int offset = pngPtr->block.offset[3];

	p += offset;

	if (16 == pngPtr->bitDepth) {
	    register unsigned int channel;

	    while (p < endPtr) {
		channel = (unsigned int)
			(((p[0] << 8) | p[1]) * pngPtr->alpha);

		*p++ = (unsigned char) (channel >> 8);
		*p++ = (unsigned char) (channel & 0xff);

		p += offset;
	    }
	} else {
	    while (p < endPtr) {
		p[0] = (unsigned char) (pngPtr->alpha * p[0]);
		p += 1 + offset;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ParseFormat --
 *
 *	This function parses the -format string that can be specified to the
 *	[image create photo] command to extract options for postprocessing of
 *	loaded images. Currently, this just allows specifying and applying an
 *	overall alpha value to the loaded image (for example, to make it
 *	entirely 50% as transparent as the actual image file).
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the format specification is invalid.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
ParseFormat(
    Tcl_Interp *interp,
    Tcl_Obj *fmtObj,
    PNGImage *pngPtr)
{
    Tcl_Obj **objv = NULL;
    int objc = 0;
    static const char *const fmtOptions[] = {
	"-alpha", NULL
    };
    enum fmtOptions {
	OPT_ALPHA
    };

    /*
     * Extract elements of format specification as a list.
     */

    if (fmtObj &&
	    Tcl_ListObjGetElements(interp, fmtObj, &objc, &objv) != TCL_OK) {
	return TCL_ERROR;
    }

    for (; objc>0 ; objc--, objv++) {
	int optIndex;

	/*
	 * Ignore the "png" part of the format specification.
	 */

	if (!strcasecmp(Tcl_GetString(objv[0]), "png")) {
	    continue;
	}

	if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions,
		sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	if (objc < 2) {
	    Tcl_WrongNumArgs(interp, 1, objv, "value");
	    return TCL_ERROR;
	}

	objc--;
	objv++;

	switch ((enum fmtOptions) optIndex) {
	case OPT_ALPHA:
	    if (Tcl_GetDoubleFromObj(interp, objv[0],
		    &pngPtr->alpha) == TCL_ERROR) {
		return TCL_ERROR;
	    }

	    if ((pngPtr->alpha < 0.0) || (pngPtr->alpha > 1.0)) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(
			"-alpha value must be between 0.0 and 1.0", -1));
		Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_ALPHA",
			NULL);
		return TCL_ERROR;
	    }
	    break;
	}
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DecodePNG --
 *
 *	This function handles the entirety of reading a PNG file (or data)
 *	from the first byte to the last.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O error occurs or any problems are
 *	detected in the PNG file.
 *
 * Side effects:
 *	The access position in f advances. Memory may be allocated and image
 *	dimensions and contents may change.
 *
 *----------------------------------------------------------------------
 */

static int
DecodePNG(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    Tcl_Obj *fmtObj,
    Tk_PhotoHandle imageHandle,
    int destX,
    int destY)
{
    unsigned long chunkType;
    int chunkSz;
    unsigned long crc;

    /*
     * Parse the PNG signature and IHDR (header) chunk.
     */

    if (ReadIHDR(interp, pngPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Extract alpha value from -format object, if specified.
     */

    if (ParseFormat(interp, fmtObj, pngPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * The next chunk may either be a PLTE (Palette) chunk or the first of at
     * least one IDAT (data) chunks. It could also be one of a number of
     * ancillary chunks, but those are skipped for us by the switch in
     * ReadChunkHeader().
     *
     * PLTE is mandatory for color type 3 and forbidden for 2 and 6
     */

    if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
	    &crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    if (CHUNK_PLTE == chunkType) {
	/*
	 * Finish parsing the PLTE chunk.
	 */

	if (ReadPLTE(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	/*
	 * Begin the next chunk.
	 */

	if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
		&crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}
    } else if (PNG_COLOR_PLTE == pngPtr->colorType) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"PLTE chunk required for indexed color", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NEED_PLTE", NULL);
	return TCL_ERROR;
    }

    /*
     * The next chunk may be a tRNS (palette transparency) chunk, depending on
     * the color type. It must come after the PLTE chunk and before the IDAT
     * chunk, but can be present if there is no PLTE chunk because it can be
     * used for Grayscale and TrueColor in lieu of an alpha channel.
     */

    if (CHUNK_tRNS == chunkType) {
	/*
	 * Finish parsing the tRNS chunk.
	 */

	if (ReadTRNS(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	/*
	 * Begin the next chunk.
	 */

	if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
		&crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}
    }

    /*
     * Other ancillary chunk types could appear here, but for now we're only
     * interested in IDAT. The others should have been skipped.
     */

    if (chunkType != CHUNK_IDAT) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"at least one IDAT chunk is required", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NEED_IDAT", NULL);
	return TCL_ERROR;
    }

    /*
     * Expand the photo size (if not set by the user) to provide enough space
     * for the image being parsed. It does not matter if width or height wrap
     * to negative here: Tk will not shrink the image.
     */

    if (Tk_PhotoExpand(interp, imageHandle, destX + pngPtr->block.width,
	    destY + pngPtr->block.height) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * A scan line consists of one byte for a filter type, plus the number of
     * bits per color sample times the number of color samples per pixel.
     */

    if (pngPtr->block.width > ((INT_MAX - 1) / (pngPtr->numChannels * 2))) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"line size is out of supported range on this architecture",
		-1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "LINE_SIZE", NULL);
	return TCL_ERROR;
    }

    if (16 == pngPtr->bitDepth) {
	pngPtr->lineSize = 1 + (pngPtr->numChannels * pngPtr->block.width*2);
    } else {
	pngPtr->lineSize = 1 + ((pngPtr->numChannels * pngPtr->block.width) /
		(8 / pngPtr->bitDepth));
	if (pngPtr->block.width % (8 / pngPtr->bitDepth)) {
	    pngPtr->lineSize++;
	}
    }

    /*
     * Allocate space for decoding the scan lines.
     */

    pngPtr->lastLineObj = Tcl_NewObj();
    Tcl_IncrRefCount(pngPtr->lastLineObj);
    pngPtr->thisLineObj = Tcl_NewObj();
    Tcl_IncrRefCount(pngPtr->thisLineObj);

    pngPtr->block.pixelPtr = attemptckalloc(pngPtr->blockLen);
    if (!pngPtr->block.pixelPtr) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"memory allocation failed", -1));
	Tcl_SetErrorCode(interp, "TK", "MALLOC", NULL);
	return TCL_ERROR;
    }

    /*
     * Determine size of the first phase if interlaced. Phase size should
     * always be <= line size, so probably not necessary to check for
     * arithmetic overflow here: should be covered by line size check.
     */

    if (pngPtr->interlace) {
	/*
	 * Only one pixel per block of 8 per line in the first phase.
	 */

	unsigned int pixels = (pngPtr->block.width + 7) >> 3;

	pngPtr->phase = 1;
	if (16 == pngPtr->bitDepth) {
	    pngPtr->phaseSize = 1 + pngPtr->numChannels*pixels*2;
	} else {
	    pngPtr->phaseSize = 1 +
		    ((pngPtr->numChannels*pixels*pngPtr->bitDepth + 7) >> 3);
	}
    } else {
	pngPtr->phaseSize = pngPtr->lineSize;
    }

    /*
     * All of the IDAT (data) chunks must be consecutive.
     */

    while (CHUNK_IDAT == chunkType) {
	if (ReadIDAT(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
		&crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}
    }

    /*
     * Ensure that we've got to the end of the compressed stream now that
     * there are no more IDAT segments. This sanity check is enforced by most
     * PNG readers.
     */

    if (!Tcl_ZlibStreamEof(pngPtr->stream)) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"unfinalized data stream in PNG data", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA", NULL);
	return TCL_ERROR;
    }

    /*
     * Now skip the remaining chunks which we're also not interested in.
     */

    while (CHUNK_IEND != chunkType) {
	if (SkipChunk(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}

	if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
		&crc) == TCL_ERROR) {
	    return TCL_ERROR;
	}
    }

    /*
     * Got the IEND (end of image) chunk. Do some final checks...
     */

    if (chunkSz) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"IEND chunk contents must be empty", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_IEND", NULL);
	return TCL_ERROR;
    }

    /*
     * Check the CRC on the IEND chunk.
     */

    if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * TODO: verify that nothing else comes after the IEND chunk, or do we
     * really care?
     */

#if 0
    if (ReadData(interp, pngPtr, &c, 1, NULL) != TCL_ERROR) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"extra data following IEND chunk", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_IEND", NULL);
	return TCL_ERROR;
    }
#endif

    /*
     * Apply overall image alpha if specified.
     */

    ApplyAlpha(pngPtr);

    /*
     * Copy the decoded image block into the Tk photo image.
     */

    if (Tk_PhotoPutBlock(interp, imageHandle, &pngPtr->block, destX, destY,
	    pngPtr->block.width, pngPtr->block.height,
	    TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FileMatchPNG --
 *
 *	This function is invoked by the photo image type to see if a file
 *	contains image data in PNG format.
 *
 * Results:
 *	The return value is 1 if the first characters in file f look like PNG
 *	data, and 0 otherwise.
 *
 * Side effects:
 *	The access position in f may change.
 *
 *----------------------------------------------------------------------
 */

static int
FileMatchPNG(
    Tcl_Channel chan,
    const char *fileName,
    Tcl_Obj *fmtObj,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp)
{
    PNGImage png;
    int match = 0;

    InitPNGImage(NULL, &png, chan, NULL, TCL_ZLIB_STREAM_INFLATE);

    if (ReadIHDR(interp, &png) == TCL_OK) {
	*widthPtr = png.block.width;
	*heightPtr = png.block.height;
	match = 1;
    }

    CleanupPNGImage(&png);

    return match;
}

/*
 *----------------------------------------------------------------------
 *
 * FileReadPNG --
 *
 *	This function is called by the photo image type to read PNG 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 file f is changed, and new data is added to the
 *	image given by imageHandle.
 *
 *----------------------------------------------------------------------
 */

static int
FileReadPNG(
    Tcl_Interp *interp,
    Tcl_Channel chan,
    const char *fileName,
    Tcl_Obj *fmtObj,
    Tk_PhotoHandle imageHandle,
    int destX,
    int destY,
    int width,
    int height,
    int srcX,
    int srcY)
{
    PNGImage png;
    int result = TCL_ERROR;

    result = InitPNGImage(interp, &png, chan, NULL, TCL_ZLIB_STREAM_INFLATE);

    if (TCL_OK == result) {
	result = DecodePNG(interp, &png, fmtObj, imageHandle, destX, destY);
    }

    CleanupPNGImage(&png);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringMatchPNG --
 *
 *	This function is invoked by the photo image type to see if an object
 *	contains image data in PNG format.
 *
 * Results:
 *	The return value is 1 if the first characters in the data are like PNG
 *	data, and 0 otherwise.
 *
 * Side effects:
 *	The size of the image is placed in widthPre and heightPtr.
 *
 *----------------------------------------------------------------------
 */

static int
StringMatchPNG(
    Tcl_Obj *pObjData,
    Tcl_Obj *fmtObj,
    int *widthPtr,
    int *heightPtr,
    Tcl_Interp *interp)
{
    PNGImage png;
    int match = 0;

    InitPNGImage(NULL, &png, NULL, pObjData, TCL_ZLIB_STREAM_INFLATE);

    png.strDataBuf = Tcl_GetByteArrayFromObj(pObjData, &png.strDataLen);

    if (ReadIHDR(interp, &png) == TCL_OK) {
	*widthPtr = png.block.width;
	*heightPtr = png.block.height;
	match = 1;
    }

    CleanupPNGImage(&png);
    return match;
}

/*
 *----------------------------------------------------------------------
 *
 * StringReadPNG --
 *
 *	This function is called by the photo image type to read PNG format
 *	data from an object and give it to the 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 imageHandle.
 *
 *----------------------------------------------------------------------
 */

static int
StringReadPNG(
    Tcl_Interp *interp,
    Tcl_Obj *pObjData,
    Tcl_Obj *fmtObj,
    Tk_PhotoHandle imageHandle,
    int destX,
    int destY,
    int width,
    int height,
    int srcX,
    int srcY)
{
    PNGImage png;
    int result = TCL_ERROR;

    result = InitPNGImage(interp, &png, NULL, pObjData,
	    TCL_ZLIB_STREAM_INFLATE);

    if (TCL_OK == result) {
	result = DecodePNG(interp, &png, fmtObj, imageHandle, destX, destY);
    }

    CleanupPNGImage(&png);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * WriteData --
 *
 *	This function writes a bytes from a buffer out to the PNG image.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the write fails.
 *
 * Side effects:
 *	File or buffer will be modified.
 *
 *----------------------------------------------------------------------
 */

static int
WriteData(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    const unsigned char *srcPtr,
    int srcSz,
    unsigned long *crcPtr)
{
    if (!srcPtr || !srcSz) {
	return TCL_OK;
    }

    if (crcPtr) {
	*crcPtr = Tcl_ZlibCRC32(*crcPtr, srcPtr, srcSz);
    }

    /*
     * TODO: is Tcl_AppendObjToObj faster here? i.e., does Tcl join the
     * objects immediately or store them in a multi-object rep?
     */

    if (pngPtr->objDataPtr) {
	int objSz;
	unsigned char *destPtr;

	Tcl_GetByteArrayFromObj(pngPtr->objDataPtr, &objSz);

	if (objSz > INT_MAX - srcSz) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "image too large to store completely in byte array", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "TOO_LARGE", NULL);
	    return TCL_ERROR;
	}

	destPtr = Tcl_SetByteArrayLength(pngPtr->objDataPtr, objSz + srcSz);

	if (!destPtr) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "memory allocation failed", -1));
	    Tcl_SetErrorCode(interp, "TK", "MALLOC", NULL);
	    return TCL_ERROR;
	}

	memcpy(destPtr+objSz, srcPtr, srcSz);
    } else if (Tcl_Write(pngPtr->channel, (const char *) srcPtr, srcSz) < 0) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"write to channel failed: %s", Tcl_PosixError(interp)));
	return TCL_ERROR;
    }

    return TCL_OK;
}

static inline int
WriteByte(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned char c,
    unsigned long *crcPtr)
{
    return WriteData(interp, pngPtr, &c, 1, crcPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * WriteInt32 --
 *
 *	This function writes a 32-bit integer value out to the PNG image as
 *	four bytes in network byte order.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the write fails.
 *
 * Side effects:
 *	File or buffer will be modified.
 *
 *----------------------------------------------------------------------
 */

static inline int
WriteInt32(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned long l,
    unsigned long *crcPtr)
{
    unsigned char pc[4];

    pc[0] = (unsigned char) ((l & 0xff000000) >> 24);
    pc[1] = (unsigned char) ((l & 0x00ff0000) >> 16);
    pc[2] = (unsigned char) ((l & 0x0000ff00) >> 8);
    pc[3] = (unsigned char) ((l & 0x000000ff) >> 0);

    return WriteData(interp, pngPtr, pc, 4, crcPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * WriteChunk --
 *
 *	Writes a complete chunk to the PNG image, including chunk type,
 *	length, contents, and CRC.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the write fails.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static inline int
WriteChunk(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    unsigned long chunkType,
    const unsigned char *dataPtr,
    int dataSize)
{
    unsigned long crc = Tcl_ZlibCRC32(0, NULL, 0);
    int result = TCL_OK;

    /*
     * Write the length field for the chunk.
     */

    result = WriteInt32(interp, pngPtr, dataSize, NULL);

    /*
     * Write the Chunk Type.
     */

    if (TCL_OK == result) {
	result = WriteInt32(interp, pngPtr, chunkType, &crc);
    }

    /*
     * Write the contents (if any).
     */

    if (TCL_OK == result) {
	result = WriteData(interp, pngPtr, dataPtr, dataSize, &crc);
    }

    /*
     * Write out the CRC at the end of the chunk.
     */

    if (TCL_OK == result) {
	result = WriteInt32(interp, pngPtr, crc, NULL);
    }

    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * WriteIHDR --
 *
 *	This function writes the PNG header at the beginning of a PNG file,
 *	which includes information such as dimensions and color type.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the write fails.
 *
 * Side effects:
 *	File or buffer will be modified.
 *
 *----------------------------------------------------------------------
 */

static int
WriteIHDR(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    Tk_PhotoImageBlock *blockPtr)
{
    unsigned long crc = Tcl_ZlibCRC32(0, NULL, 0);
    int result = TCL_OK;

    /*
     * The IHDR (header) chunk has a fixed size of 13 bytes.
     */

    result = WriteInt32(interp, pngPtr, 13, NULL);

    /*
     * Write the IHDR Chunk Type.
     */

    if (TCL_OK == result) {
	result = WriteInt32(interp, pngPtr, CHUNK_IHDR, &crc);
    }

    /*
     * Write the image width, height.
     */

    if (TCL_OK == result) {
	result = WriteInt32(interp, pngPtr, (unsigned long) blockPtr->width,
		&crc);
    }

    if (TCL_OK == result) {
	result = WriteInt32(interp, pngPtr, (unsigned long) blockPtr->height,
		&crc);
    }

    /*
     * Write bit depth. Although the PNG format supports 16 bits per channel,
     * Tk supports only 8 in the internal representation, which blockPtr
     * points to.
     */

    if (TCL_OK == result) {
	result = WriteByte(interp, pngPtr, 8, &crc);
    }

    /*
     * Write out the color type, previously determined.
     */

    if (TCL_OK == result) {
	result = WriteByte(interp, pngPtr, pngPtr->colorType, &crc);
    }

    /*
     * Write compression method (only one method is defined).
     */

    if (TCL_OK == result) {
	result = WriteByte(interp, pngPtr, PNG_COMPRESS_DEFLATE, &crc);
    }

    /*
     * Write filter method (only one method is defined).
     */

    if (TCL_OK == result) {
	result = WriteByte(interp, pngPtr, PNG_FILTMETH_STANDARD, &crc);
    }

    /*
     * Write interlace method as not interlaced.
     *
     * TODO: support interlace through -format?
     */

    if (TCL_OK == result) {
	result = WriteByte(interp, pngPtr, PNG_INTERLACE_NONE, &crc);
    }

    /*
     * Write out the CRC at the end of the chunk.
     */

    if (TCL_OK == result) {
	result = WriteInt32(interp, pngPtr, crc, NULL);
    }

    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * WriteIDAT --
 *
 *	Writes the IDAT (data) chunk to the PNG image, containing the pixel
 *	channel data. Currently, image lines are not filtered and writing
 *	interlaced pixels is not supported.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the write fails.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
WriteIDAT(
    Tcl_Interp *interp,
    PNGImage *pngPtr,
    Tk_PhotoImageBlock *blockPtr)
{
    int rowNum, flush = TCL_ZLIB_NO_FLUSH, outputSize, result;
    Tcl_Obj *outputObj;
    unsigned char *outputBytes;

    /*
     * Filter and compress each row one at a time.
     */

    for (rowNum=0 ; rowNum < blockPtr->height ; rowNum++) {
	int colNum;
	unsigned char *srcPtr, *destPtr;

	srcPtr = blockPtr->pixelPtr + (rowNum * blockPtr->pitch);
	destPtr = Tcl_SetByteArrayLength(pngPtr->thisLineObj,
		pngPtr->lineSize);

	/*
	 * TODO: use Paeth filtering.
	 */

	*destPtr++ = PNG_FILTER_NONE;

	/*
	 * Copy each pixel into the destination buffer after the filter type
	 * before filtering.
	 */

	for (colNum = 0 ; colNum < blockPtr->width ; colNum++) {
	    /*
	     * Copy red or gray channel.
	     */

	    *destPtr++ = srcPtr[blockPtr->offset[0]];

	    /*
	     * If not grayscale, copy the green and blue channels.
	     */

	    if (pngPtr->colorType & PNG_COLOR_USED) {
		*destPtr++ = srcPtr[blockPtr->offset[1]];
		*destPtr++ = srcPtr[blockPtr->offset[2]];
	    }

	    /*
	     * Copy the alpha channel, if used.
	     */

	    if (pngPtr->colorType & PNG_COLOR_ALPHA) {
		*destPtr++ = srcPtr[blockPtr->offset[3]];
	    }

	    /*
	     * Point to the start of the next pixel.
	     */

	    srcPtr += blockPtr->pixelSize;
	}

	/*
	 * Compress the line of pixels into the destination. If this is the
	 * last line, finalize the compressor at the same time. Note that this
	 * can't be just a flush; that leads to a file that some PNG readers
	 * choke on. [Bug 2984787]
	 */

	if (rowNum + 1 == blockPtr->height) {
	    flush = TCL_ZLIB_FINALIZE;
	}
	if (Tcl_ZlibStreamPut(pngPtr->stream, pngPtr->thisLineObj,
		flush) != TCL_OK) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "deflate() returned error", -1));
	    Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "DEFLATE", NULL);
	    return TCL_ERROR;
	}

	/*
	 * Swap line buffers to keep the last around for filtering next.
	 */

	{
	    Tcl_Obj *temp = pngPtr->lastLineObj;

	    pngPtr->lastLineObj = pngPtr->thisLineObj;
	    pngPtr->thisLineObj = temp;
	}
    }

    /*
     * Now get the compressed data and write it as one big IDAT chunk.
     */

    outputObj = Tcl_NewObj();
    (void) Tcl_ZlibStreamGet(pngPtr->stream, outputObj, -1);
    outputBytes = Tcl_GetByteArrayFromObj(outputObj, &outputSize);
    result = WriteChunk(interp, pngPtr, CHUNK_IDAT, outputBytes, outputSize);
    Tcl_DecrRefCount(outputObj);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * WriteExtraChunks --
 *
 *	Writes an sBIT and a tEXt chunks to the PNG image, describing a bunch
 *	of not very important metadata that many readers seem to need anyway.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if the write fails.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
WriteExtraChunks(
    Tcl_Interp *interp,
    PNGImage *pngPtr)
{
    static const unsigned char sBIT_contents[] = {
	8, 8, 8, 8
    };
    int sBIT_length = 4;
    Tcl_DString buf;

    /*
     * Each byte of each channel is always significant; we always write RGBA
     * images with 8 bits per channel as that is what the photo image's basic
     * data model is.
     */

    switch (pngPtr->colorType) {
    case PNG_COLOR_GRAY:
	sBIT_length = 1;
	break;
    case PNG_COLOR_GRAYALPHA:
	sBIT_length = 2;
	break;
    case PNG_COLOR_RGB:
    case PNG_COLOR_PLTE:
	sBIT_length = 3;
	break;
    case PNG_COLOR_RGBA:
	sBIT_length = 4;
	break;
    }
    if (WriteChunk(interp, pngPtr, CHUNK_sBIT, sBIT_contents, sBIT_length)
	    != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * Say that it is Tk that made the PNG. Note that we *need* the NUL at the
     * end of "Software" to be transferred; do *not* change the length
     * parameter to -1 there!
     */

    Tcl_DStringInit(&buf);
    Tcl_DStringAppend(&buf, "Software", 9);
    Tcl_DStringAppend(&buf, "Tk Toolkit v", -1);
    Tcl_DStringAppend(&buf, TK_PATCH_LEVEL, -1);
    if (WriteChunk(interp, pngPtr, CHUNK_tEXt,
	    (unsigned char *) Tcl_DStringValue(&buf),
	    Tcl_DStringLength(&buf)) != TCL_OK) {
	Tcl_DStringFree(&buf);
	return TCL_ERROR;
    }
    Tcl_DStringFree(&buf);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * EncodePNG --
 *
 *	This function handles the entirety of writing a PNG file (or data)
 *	from the first byte to the last. No effort is made to optimize the
 *	image data for best compression.
 *
 * Results:
 *	TCL_OK, or TCL_ERROR if an I/O or memory error occurs.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
EncodePNG(
    Tcl_Interp *interp,
    Tk_PhotoImageBlock *blockPtr,
    PNGImage *pngPtr)
{
    int greenOffset, blueOffset, alphaOffset;

    /*
     * Determine appropriate color type based on color usage (e.g., only red
     * and maybe alpha channel = grayscale).
     *
     * TODO: Check whether this is doing any good; Tk might just be pushing
     * full RGBA data all the time through here, even though the actual image
     * doesn't need it...
     */

    greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
    blueOffset = blockPtr->offset[2] - blockPtr->offset[0];
    alphaOffset = blockPtr->offset[3];
    if ((alphaOffset >= blockPtr->pixelSize) || (alphaOffset < 0)) {
	alphaOffset = 0;
    } else {
	alphaOffset -= blockPtr->offset[0];
    }

    if ((greenOffset != 0) || (blueOffset != 0)) {
	if (alphaOffset) {
	    pngPtr->colorType = PNG_COLOR_RGBA;
	    pngPtr->bytesPerPixel = 4;
	} else {
	    pngPtr->colorType = PNG_COLOR_RGB;
	    pngPtr->bytesPerPixel = 3;
	}
    } else {
	if (alphaOffset) {
	    pngPtr->colorType = PNG_COLOR_GRAYALPHA;
	    pngPtr->bytesPerPixel = 2;
	} else {
	    pngPtr->colorType = PNG_COLOR_GRAY;
	    pngPtr->bytesPerPixel = 1;
	}
    }

    /*
     * Allocate buffers for lines for filtering and compressed data.
     */

    pngPtr->lineSize = 1 + (pngPtr->bytesPerPixel * blockPtr->width);
    pngPtr->blockLen = pngPtr->lineSize * blockPtr->height;

    if ((blockPtr->width > (INT_MAX - 1) / (pngPtr->bytesPerPixel)) ||
	    (blockPtr->height > INT_MAX / pngPtr->lineSize)) {
	Tcl_SetObjResult(interp, Tcl_NewStringObj(
		"image is too large to encode pixel data", -1));
	Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "TOO_LARGE", NULL);
	return TCL_ERROR;
    }

    pngPtr->lastLineObj = Tcl_NewObj();
    Tcl_IncrRefCount(pngPtr->lastLineObj);
    pngPtr->thisLineObj = Tcl_NewObj();
    Tcl_IncrRefCount(pngPtr->thisLineObj);

    /*
     * Write out the PNG Signature that all PNGs begin with.
     */

    if (WriteData(interp, pngPtr, pngSignature, PNG_SIG_SZ,
	    NULL) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Write out the IHDR (header) chunk containing image dimensions, color
     * type, etc.
     */

    if (WriteIHDR(interp, pngPtr, blockPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Write out the extra chunks containing metadata that is of interest to
     * other programs more than us.
     */

    if (WriteExtraChunks(interp, pngPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Write out the image pixels in the IDAT (data) chunk.
     */

    if (WriteIDAT(interp, pngPtr, blockPtr) == TCL_ERROR) {
	return TCL_ERROR;
    }

    /*
     * Write out the IEND chunk that all PNGs end with.
     */

    return WriteChunk(interp, pngPtr, CHUNK_IEND, NULL, 0);
}

/*
 *----------------------------------------------------------------------
 *
 * FileWritePNG --
 *
 *	This function is called by the photo image type to write PNG format
 *	data to a file.
 *
 * 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 specified file is overwritten.
 *
 *----------------------------------------------------------------------
 */

static int
FileWritePNG(
    Tcl_Interp *interp,
    const char *filename,
    Tcl_Obj *fmtObj,
    Tk_PhotoImageBlock *blockPtr)
{
    Tcl_Channel chan;
    PNGImage png;
    int result = TCL_ERROR;

    /*
     * Open a Tcl file channel where the image data will be stored. Tk ought
     * to take care of this, and just provide a channel, but it doesn't.
     */

    chan = Tcl_OpenFileChannel(interp, filename, "w", 0644);

    if (!chan) {
	return TCL_ERROR;
    }

    /*
     * Initalize PNGImage instance for encoding.
     */

    if (InitPNGImage(interp, &png, chan, NULL,
	    TCL_ZLIB_STREAM_DEFLATE) == TCL_ERROR) {
	goto cleanup;
    }

    /*
     * Set the translation mode to binary so that CR and LF are not to the
     * platform's EOL sequence.
     */

    if (Tcl_SetChannelOption(interp, chan, "-translation",
	    "binary") != TCL_OK) {
	goto cleanup;
    }

    /*
     * Write the raw PNG data out to the file.
     */

    result = EncodePNG(interp, blockPtr, &png);

  cleanup:
    Tcl_Close(interp, chan);
    CleanupPNGImage(&png);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringWritePNG --
 *
 *	This function is called by the photo image type to write PNG format
 *	data to a Tcl object and return it in the result.
 *
 * Results:
 *	A standard TCL completion code. If TCL_ERROR is returned then an error
 *	message is left in the interp's result.
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
StringWritePNG(
    Tcl_Interp *interp,
    Tcl_Obj *fmtObj,
    Tk_PhotoImageBlock *blockPtr)
{
    Tcl_Obj *resultObj = Tcl_NewObj();
    PNGImage png;
    int result = TCL_ERROR;

    /*
     * Initalize PNGImage instance for encoding.
     */

    if (InitPNGImage(interp, &png, NULL, resultObj,
	    TCL_ZLIB_STREAM_DEFLATE) == TCL_ERROR) {
	goto cleanup;
    }

    /*
     * Write the raw PNG data into the prepared Tcl_Obj buffer. Set the result
     * back to the interpreter if successful.
     */

    result = EncodePNG(interp, blockPtr, &png);

    if (TCL_OK == result) {
	Tcl_SetObjResult(interp, png.objDataPtr);
    }

  cleanup:
    CleanupPNGImage(&png);
    return result;
}

/*
 * Local Variables:
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
