diff options
Diffstat (limited to 'generic/tkImgPNG.c')
-rw-r--r-- | generic/tkImgPNG.c | 3555 |
1 files changed, 3555 insertions, 0 deletions
diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c new file mode 100644 index 0000000..f639694 --- /dev/null +++ b/generic/tkImgPNG.c @@ -0,0 +1,3555 @@ +/* + * 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[] = { + "png", "-alpha", NULL + }; + enum fmtOptions { + OPT_PNG, 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; + + if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions, + sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) { + return TCL_ERROR; + } + + /* + * Ignore the "png" part of the format specification. + */ + + if (OPT_PNG == optIndex) { + continue; + } + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "value"); + return TCL_ERROR; + } + + objc--; + objv++; + + switch ((enum fmtOptions) optIndex) { + case OPT_PNG: + break; + + 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: + */ |