summaryrefslogtreecommitdiffstats
path: root/generic/tkImgPNG.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tkImgPNG.c')
-rw-r--r--generic/tkImgPNG.c3552
1 files changed, 3552 insertions, 0 deletions
diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c
new file mode 100644
index 0000000..8a740d2
--- /dev/null
+++ b/generic/tkImgPNG.c
@@ -0,0 +1,3552 @@
+/*
+ * 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 "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 0x10000000L /* 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 criticial 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 criticial 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->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.
+ */
+
+ 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 int channel;
+
+ while (p < endPtr) {
+ channel = (unsigned char)
+ (((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_RGBA;
+ 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:
+ */