/*
* 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 0x20000000L /* Non-critical chunk (can ignore). */
#define PNG_CF_PRIVATE 0x00100000L /* Application-specific chunk. */
#define PNG_CF_RESERVED 0x00001000L /* Not used. */
#define PNG_CF_COPYSAFE 0x00000010L /* Opaque data safe for copying. */
/*
* Chunk types, not all of which have support implemented. Note that there are
* others in the official extension set which we will never support (as they
* are officially deprecated).
*/
#define CHUNK_IDAT PNG_INT32('I','D','A','T') /* Pixel data. */
#define CHUNK_IEND PNG_INT32('I','E','N','D') /* End of Image. */
#define CHUNK_IHDR PNG_INT32('I','H','D','R') /* Header. */
#define CHUNK_PLTE PNG_INT32('P','L','T','E') /* Palette. */
#define CHUNK_bKGD PNG_INT32('b','K','G','D') /* Background Color */
#define CHUNK_cHRM PNG_INT32('c','H','R','M') /* Chroma values. */
#define CHUNK_gAMA PNG_INT32('g','A','M','A') /* Gamma. */
#define CHUNK_hIST PNG_INT32('h','I','S','T') /* Histogram. */
#define CHUNK_iCCP PNG_INT32('i','C','C','P') /* Color profile. */
#define CHUNK_iTXt PNG_INT32('i','T','X','t') /* Internationalized
* text (comments,
* etc.) */
#define CHUNK_oFFs PNG_INT32('o','F','F','s') /* Image offset. */
#define CHUNK_pCAL PNG_INT32('p','C','A','L') /* Pixel calibration
* data. */
#define CHUNK_pHYs PNG_INT32('p','H','Y','s') /* Physical pixel
* dimensions. */
#define CHUNK_sBIT PNG_INT32('s','B','I','T') /* Significant bits */
#define CHUNK_sCAL PNG_INT32('s','C','A','L') /* Physical scale. */
#define CHUNK_sPLT PNG_INT32('s','P','L','T') /* Suggested
* palette. */
#define CHUNK_sRGB PNG_INT32('s','R','G','B') /* Standard RGB space
* declaration. */
#define CHUNK_tEXt PNG_INT32('t','E','X','t') /* Plain Latin-1
* text. */
#define CHUNK_tIME PNG_INT32('t','I','M','E') /* Time stamp. */
#define CHUNK_tRNS PNG_INT32('t','R','N','S') /* Transparency. */
#define CHUNK_zTXt PNG_INT32('z','T','X','t') /* Compressed Latin-1
* text. */
/*
* Color flags.
*/
#define PNG_COLOR_INDEXED 1
#define PNG_COLOR_USED 2
#define PNG_COLOR_ALPHA 4
/*
* Actual color types.
*/
#define PNG_COLOR_GRAY 0
#define PNG_COLOR_RGB (PNG_COLOR_USED)
#define PNG_COLOR_PLTE (PNG_COLOR_USED | PNG_COLOR_INDEXED)
#define PNG_COLOR_GRAYALPHA (PNG_COLOR_GRAY | PNG_COLOR_ALPHA)
#define PNG_COLOR_RGBA (PNG_COLOR_USED | PNG_COLOR_ALPHA)
/*
* Compression Methods.
*/
#define PNG_COMPRESS_DEFLATE 0
/*
* Filter Methods.
*/
#define PNG_FILTMETH_STANDARD 0
/*
* Interlacing Methods.
*/
#define PNG_INTERLACE_NONE 0
#define PNG_INTERLACE_ADAM7 1
/*
* State information, used to store everything about the PNG image being
* currently parsed or created.
*/
typedef struct {
/*
* PNG data source/destination channel/object/byte array.
*/
Tcl_Channel channel; /* Channel for from-file reads. */
Tcl_Obj *objDataPtr;
unsigned char *strDataBuf; /* Raw source data for from-string reads. */
int strDataLen; /* Length of source data. */
unsigned char *base64Data; /* base64 encoded string data. */
unsigned char base64Bits; /* Remaining bits from last base64 read. */
unsigned char base64State; /* Current state of base64 decoder. */
double alpha; /* Alpha from -format option. */
/*
* Image header information.
*/
unsigned char bitDepth; /* Number of bits per pixel. */
unsigned char colorType; /* Grayscale, TrueColor, etc. */
unsigned char compression; /* Compression Mode (always zlib). */
unsigned char filter; /* Filter mode (0 - 3). */
unsigned char interlace; /* Type of interlacing (if any). */
unsigned char numChannels; /* Number of channels per pixel. */
unsigned char bytesPerPixel;/* Bytes per pixel in scan line. */
int bitScale; /* Scale factor for RGB/Gray depths < 8. */
int currentLine; /* Current line being unfiltered. */
unsigned char phase; /* Interlacing phase (0..6). */
Tk_PhotoImageBlock block;
int blockLen; /* Number of bytes in Tk image pixels. */
/*
* For containing data read from PLTE (palette) and tRNS (transparency)
* chunks.
*/
int paletteLen; /* Number of PLTE entries (1..256). */
int useTRNS; /* Flag to indicate whether there was a
* palette given. */
struct {
unsigned char red;
unsigned char green;
unsigned char blue;
unsigned char alpha;
} palette[256]; /* Palette RGB/Transparency table. */
unsigned char transVal[6]; /* Fully-transparent RGB/Gray Value. */
/*
* For compressing and decompressing IDAT chunks.
*/
Tcl_ZlibStream stream; /* Inflating or deflating stream; this one is
* not bound to a Tcl command. */
Tcl_Obj *lastLineObj; /* Last line of pixels, for unfiltering. */
Tcl_Obj *thisLineObj; /* Current line of pixels to process. */
int lineSize; /* Number of bytes in a PNG line. */
int phaseSize; /* Number of bytes/line in current phase. */
} PNGImage;
/*
* Maximum size of various chunks.
*/
#define PNG_PLTE_MAXSZ 768 /* 3 bytes/RGB entry, 256 entries max */
#define PNG_TRNS_MAXSZ 256 /* 1-byte alpha, 256 entries max */
/*
* Forward declarations of non-global functions defined in this file:
*/
static void ApplyAlpha(PNGImage *pngPtr);
static int CheckColor(Tcl_Interp *interp, PNGImage *pngPtr);
static inline int CheckCRC(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned long calculated);
static void CleanupPNGImage(PNGImage *pngPtr);
static int DecodeLine(Tcl_Interp *interp, PNGImage *pngPtr);
static int DecodePNG(Tcl_Interp *interp, PNGImage *pngPtr,
Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle,
int destX, int destY);
static int EncodePNG(Tcl_Interp *interp,
Tk_PhotoImageBlock *blockPtr, PNGImage *pngPtr);
static int FileMatchPNG(Tcl_Channel chan, const char *fileName,
Tcl_Obj *fmtObj, int *widthPtr, int *heightPtr,
Tcl_Interp *interp);
static int FileReadPNG(Tcl_Interp *interp, Tcl_Channel chan,
const char *fileName, Tcl_Obj *fmtObj,
Tk_PhotoHandle imageHandle, int destX, int destY,
int width, int height, int srcX, int srcY);
static int FileWritePNG(Tcl_Interp *interp, const char *filename,
Tcl_Obj *fmtObj, Tk_PhotoImageBlock *blockPtr);
static int InitPNGImage(Tcl_Interp *interp, PNGImage *pngPtr,
Tcl_Channel chan, Tcl_Obj *objPtr, int dir);
static inline unsigned char Paeth(int a, int b, int c);
static int ParseFormat(Tcl_Interp *interp, Tcl_Obj *fmtObj,
PNGImage *pngPtr);
static int ReadBase64(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned char *destPtr, int destSz,
unsigned long *crcPtr);
static int ReadByteArray(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned char *destPtr, int destSz,
unsigned long *crcPtr);
static int ReadData(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned char *destPtr, int destSz,
unsigned long *crcPtr);
static int ReadChunkHeader(Tcl_Interp *interp, PNGImage *pngPtr,
int *sizePtr, unsigned long *typePtr,
unsigned long *crcPtr);
static int ReadIDAT(Tcl_Interp *interp, PNGImage *pngPtr,
int chunkSz, unsigned long crc);
static int ReadIHDR(Tcl_Interp *interp, PNGImage *pngPtr);
static inline int ReadInt32(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned long *resultPtr, unsigned long *crcPtr);
static int ReadPLTE(Tcl_Interp *interp, PNGImage *pngPtr,
int chunkSz, unsigned long crc);
static int ReadTRNS(Tcl_Interp *interp, PNGImage *pngPtr,
int chunkSz, unsigned long crc);
static int SkipChunk(Tcl_Interp *interp, PNGImage *pngPtr,
int chunkSz, unsigned long crc);
static int StringMatchPNG(Tcl_Obj *dataObj, Tcl_Obj *fmtObj,
int *widthPtr, int *heightPtr,
Tcl_Interp *interp);
static int StringReadPNG(Tcl_Interp *interp, Tcl_Obj *dataObj,
Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle,
int destX, int destY, int width, int height,
int srcX, int srcY);
static int StringWritePNG(Tcl_Interp *interp, Tcl_Obj *fmtObj,
Tk_PhotoImageBlock *blockPtr);
static int UnfilterLine(Tcl_Interp *interp, PNGImage *pngPtr);
static inline int WriteByte(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned char c, unsigned long *crcPtr);
static inline int WriteChunk(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned long chunkType,
const unsigned char *dataPtr, int dataSize);
static int WriteData(Tcl_Interp *interp, PNGImage *pngPtr,
const unsigned char *srcPtr, int srcSz,
unsigned long *crcPtr);
static int WriteExtraChunks(Tcl_Interp *interp,
PNGImage *pngPtr);
static int WriteIHDR(Tcl_Interp *interp, PNGImage *pngPtr,
Tk_PhotoImageBlock *blockPtr);
static int WriteIDAT(Tcl_Interp *interp, PNGImage *pngPtr,
Tk_PhotoImageBlock *blockPtr);
static inline int WriteInt32(Tcl_Interp *interp, PNGImage *pngPtr,
unsigned long l, unsigned long *crcPtr);
/*
* The format record for the PNG file format:
*/
Tk_PhotoImageFormat tkImgFmtPNG = {
"png", /* name */
FileMatchPNG, /* fileMatchProc */
StringMatchPNG, /* stringMatchProc */
FileReadPNG, /* fileReadProc */
StringReadPNG, /* stringReadProc */
FileWritePNG, /* fileWriteProc */
StringWritePNG, /* stringWriteProc */
NULL
};
/*
*----------------------------------------------------------------------
*
* InitPNGImage --
*
* This function is invoked by each of the Tk image handler procs
* (MatchStringProc, etc.) to initialize state information used during
* the course of encoding or decoding a PNG image.
*
* Results:
* TCL_OK, or TCL_ERROR if initialization failed.
*
* Side effects:
* The reference count of the -data Tcl_Obj*, if any, is incremented.
*
*----------------------------------------------------------------------
*/
static int
InitPNGImage(
Tcl_Interp *interp,
PNGImage *pngPtr,
Tcl_Channel chan,
Tcl_Obj *objPtr,
int dir)
{
memset(pngPtr, 0, sizeof(PNGImage));
pngPtr->channel = chan;
pngPtr->alpha = 1.0;
/*
* If decoding from a -data string object, increment its reference count
* for the duration of the decode and get its length and byte array for
* reading with ReadData().
*/
if (objPtr) {
Tcl_IncrRefCount(objPtr);
pngPtr->objDataPtr = objPtr;
pngPtr->strDataBuf =
Tcl_GetByteArrayFromObj(objPtr, &pngPtr->strDataLen);
}
/*
* Initialize the palette transparency table to fully opaque.
*/
memset(pngPtr->palette, 255, sizeof(pngPtr->palette));
/*
* Initialize Zlib inflate/deflate stream.
*/
if (Tcl_ZlibStreamInit(NULL, dir, TCL_ZLIB_FORMAT_ZLIB,
TCL_ZLIB_COMPRESS_DEFAULT, NULL, &pngPtr->stream) != TCL_OK) {
if (interp) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"zlib initialization failed", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "ZLIB_INIT", NULL);
}
if (objPtr) {
Tcl_DecrRefCount(objPtr);
}
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* CleanupPNGImage --
*
* This function is invoked by each of the Tk image handler procs
* (MatchStringProc, etc.) prior to returning to Tcl in order to clean up
* any allocated memory and call other cleanup handlers such as zlib's
* inflateEnd/deflateEnd.
*
* Results:
* None.
*
* Side effects:
* The reference count of the -data Tcl_Obj*, if any, is decremented.
* Buffers are freed, streams are closed. The PNGImage should not be used
* for any purpose without being reinitialized post-cleanup.
*
*----------------------------------------------------------------------
*/
static void
CleanupPNGImage(
PNGImage *pngPtr)
{
/*
* Don't need the object containing the -data value anymore.
*/
if (pngPtr->objDataPtr) {
Tcl_DecrRefCount(pngPtr->objDataPtr);
}
/*
* Discard pixel buffer.
*/
if (pngPtr->stream) {
Tcl_ZlibStreamClose(pngPtr->stream);
}
if (pngPtr->block.pixelPtr) {
ckfree(pngPtr->block.pixelPtr);
}
if (pngPtr->thisLineObj) {
Tcl_DecrRefCount(pngPtr->thisLineObj);
}
if (pngPtr->lastLineObj) {
Tcl_DecrRefCount(pngPtr->lastLineObj);
}
memset(pngPtr, 0, sizeof(PNGImage));
}
/*
*----------------------------------------------------------------------
*
* ReadBase64 --
*
* This function is invoked to read the specified number of bytes from
* base-64 encoded image data.
*
* Note: It would be better if the Tk_PhotoImage stuff handled this by
* creating a channel from the -data value, which would take care of
* base64 decoding and made the data readable as if it were coming from a
* file.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs.
*
* Side effects:
* The file position will change. The running CRC is updated if a pointer
* to it is provided.
*
*----------------------------------------------------------------------
*/
static int
ReadBase64(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned char *destPtr,
int destSz,
unsigned long *crcPtr)
{
static const unsigned char from64[] = {
0x82, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x80, 0x80,
0x83, 0x80, 0x80, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x80,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x3e,
0x83, 0x83, 0x83, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
0x3b, 0x3c, 0x3d, 0x83, 0x83, 0x83, 0x81, 0x83, 0x83, 0x83, 0x00,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x18, 0x19, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x1a, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26,
0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
0x32, 0x33, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
0x83, 0x83
};
/*
* Definitions for the base-64 decoder.
*/
#define PNG64_SPECIAL 0x80 /* Flag bit */
#define PNG64_SPACE 0x80 /* Whitespace */
#define PNG64_PAD 0x81 /* Padding */
#define PNG64_DONE 0x82 /* End of data */
#define PNG64_BAD 0x83 /* Ooooh, naughty! */
while (destSz && pngPtr->strDataLen) {
unsigned char c = 0;
unsigned char c64 = from64[*pngPtr->strDataBuf++];
pngPtr->strDataLen--;
if (PNG64_SPACE == c64) {
continue;
}
if (c64 & PNG64_SPECIAL) {
c = (unsigned char) pngPtr->base64Bits;
} else {
switch (pngPtr->base64State++) {
case 0:
pngPtr->base64Bits = c64 << 2;
continue;
case 1:
c = (unsigned char) (pngPtr->base64Bits | (c64 >> 4));
pngPtr->base64Bits = (c64 & 0xF) << 4;
break;
case 2:
c = (unsigned char) (pngPtr->base64Bits | (c64 >> 2));
pngPtr->base64Bits = (c64 & 0x3) << 6;
break;
case 3:
c = (unsigned char) (pngPtr->base64Bits | c64);
pngPtr->base64State = 0;
pngPtr->base64Bits = 0;
break;
}
}
if (crcPtr) {
*crcPtr = Tcl_ZlibCRC32(*crcPtr, &c, 1);
}
if (destPtr) {
*destPtr++ = c;
}
destSz--;
if (c64 & PNG64_SPECIAL) {
break;
}
}
if (destSz) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"unexpected end of image data", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EARLY_END", NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadByteArray --
*
* This function is invoked to read the specified number of bytes from a
* non-base64-encoded byte array provided via the -data option.
*
* Note: It would be better if the Tk_PhotoImage stuff handled this by
* creating a channel from the -data value and made the data readable as
* if it were coming from a file.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs.
*
* Side effects:
* The file position will change. The running CRC is updated if a pointer
* to it is provided.
*
*----------------------------------------------------------------------
*/
static int
ReadByteArray(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned char *destPtr,
int destSz,
unsigned long *crcPtr)
{
/*
* Check to make sure the number of requested bytes are available.
*/
if (pngPtr->strDataLen < destSz) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"unexpected end of image data", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EARLY_END", NULL);
return TCL_ERROR;
}
while (destSz) {
int blockSz = PNG_MIN(destSz, PNG_BLOCK_SZ);
memcpy(destPtr, pngPtr->strDataBuf, blockSz);
pngPtr->strDataBuf += blockSz;
pngPtr->strDataLen -= blockSz;
if (crcPtr) {
*crcPtr = Tcl_ZlibCRC32(*crcPtr, destPtr, blockSz);
}
destPtr += blockSz;
destSz -= blockSz;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadData --
*
* This function is invoked to read the specified number of bytes from
* the image file or data. It is a wrapper around the choice of byte
* array Tcl_Obj or Tcl_Channel which depends on whether the image data
* is coming from a file or -data.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs.
*
* Side effects:
* The file position will change. The running CRC is updated if a pointer
* to it is provided.
*
*----------------------------------------------------------------------
*/
static int
ReadData(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned char *destPtr,
int destSz,
unsigned long *crcPtr)
{
if (pngPtr->base64Data) {
return ReadBase64(interp, pngPtr, destPtr, destSz, crcPtr);
} else if (pngPtr->strDataBuf) {
return ReadByteArray(interp, pngPtr, destPtr, destSz, crcPtr);
}
while (destSz) {
int blockSz = PNG_MIN(destSz, PNG_BLOCK_SZ);
blockSz = Tcl_Read(pngPtr->channel, (char *)destPtr, blockSz);
if (blockSz < 0) {
/* TODO: failure info... */
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"channel read failed: %s", Tcl_PosixError(interp)));
return TCL_ERROR;
}
/*
* Update CRC, pointer, and remaining count if anything was read.
*/
if (blockSz) {
if (crcPtr) {
*crcPtr = Tcl_ZlibCRC32(*crcPtr, destPtr, blockSz);
}
destPtr += blockSz;
destSz -= blockSz;
}
/*
* Check for EOF before all desired data was read.
*/
if (destSz && Tcl_Eof(pngPtr->channel)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"unexpected end of file", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EOF", NULL);
return TCL_ERROR;
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadInt32 --
*
* This function is invoked to read a 32-bit integer in network byte
* order from the image data and return the value in host byte order.
* This is used, for example, to read the 32-bit CRC value for a chunk
* stored in the image file for comparison with the calculated CRC value.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs.
*
* Side effects:
* The file position will change. The running CRC is updated if a pointer
* to it is provided.
*
*----------------------------------------------------------------------
*/
static inline int
ReadInt32(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned long *resultPtr,
unsigned long *crcPtr)
{
unsigned char p[4];
if (ReadData(interp, pngPtr, p, 4, crcPtr) == TCL_ERROR) {
return TCL_ERROR;
}
*resultPtr = PNG_INT32(p[0], p[1], p[2], p[3]);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* CheckCRC --
*
* This function is reads the final 4-byte integer CRC from a chunk and
* compares it to the running CRC calculated over the chunk type and data
* fields.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error or CRC mismatch occurs.
*
* Side effects:
* The file position will change.
*
*----------------------------------------------------------------------
*/
static inline int
CheckCRC(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned long calculated)
{
unsigned long chunked;
/*
* Read the CRC field at the end of the chunk.
*/
if (ReadInt32(interp, pngPtr, &chunked, NULL) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Compare the read CRC to what we calculate to make sure they match.
*/
if (calculated != chunked) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("CRC check failed", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "CRC", NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* SkipChunk --
*
* This function is used to skip a PNG chunk that is not used by this
* implementation. Given the input stream has had the chunk length and
* chunk type fields already read, this function will read the number of
* bytes indicated by the chunk length, plus four for the CRC, and will
* verify that CRC is correct for the skipped data.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error or CRC mismatch occurs.
*
* Side effects:
* The file position will change.
*
*----------------------------------------------------------------------
*/
static int
SkipChunk(
Tcl_Interp *interp,
PNGImage *pngPtr,
int chunkSz,
unsigned long crc)
{
unsigned char buffer[PNG_BLOCK_SZ];
/*
* Skip data in blocks until none is left. Read up to PNG_BLOCK_SZ bytes
* at a time, rather than trusting the claimed chunk size, which may not
* be trustworthy.
*/
while (chunkSz) {
int blockSz = PNG_MIN(chunkSz, PNG_BLOCK_SZ);
if (ReadData(interp, pngPtr, buffer, blockSz, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
chunkSz -= blockSz;
}
if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
return TCL_ERROR;
}
return TCL_OK;
}
/*
* 4.3. Summary of standard chunks
*
* This table summarizes some properties of the standard chunk types.
*
* Critical chunks (must appear in this order, except PLTE is optional):
*
* Name Multiple Ordering constraints OK?
*
* IHDR No Must be first
* PLTE No Before IDAT
* IDAT Yes Multiple IDATs must be consecutive
* IEND No Must be last
*
* Ancillary chunks (need not appear in this order):
*
* Name Multiple Ordering constraints OK?
*
* cHRM No Before PLTE and IDAT
* gAMA No Before PLTE and IDAT
* iCCP No Before PLTE and IDAT
* sBIT No Before PLTE and IDAT
* sRGB No Before PLTE and IDAT
* bKGD No After PLTE; before IDAT
* hIST No After PLTE; before IDAT
* tRNS No After PLTE; before IDAT
* pHYs No Before IDAT
* sPLT Yes Before IDAT
* tIME No None
* iTXt Yes None
* tEXt Yes None
* zTXt Yes None
*
* [From the PNG specification.]
*/
/*
*----------------------------------------------------------------------
*
* ReadChunkHeader --
*
* This function is used at the start of each chunk to extract the
* four-byte chunk length and four-byte chunk type fields. It will
* continue reading until it finds a chunk type that is handled by this
* implementation, checking the CRC of any chunks it skips.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs or an unknown critical
* chunk type is encountered.
*
* Side effects:
* The file position will change. The running CRC is updated.
*
*----------------------------------------------------------------------
*/
static int
ReadChunkHeader(
Tcl_Interp *interp,
PNGImage *pngPtr,
int *sizePtr,
unsigned long *typePtr,
unsigned long *crcPtr)
{
unsigned long chunkType = 0;
int chunkSz = 0;
unsigned long crc = 0;
/*
* Continue until finding a chunk type that is handled.
*/
while (!chunkType) {
unsigned long temp;
unsigned char pc[4];
int i;
/*
* Read the 4-byte length field for the chunk. The length field is not
* included in the CRC calculation, so the running CRC must be reset
* afterward. Limit chunk lengths to INT_MAX, to align with the
* maximum size for Tcl_Read, Tcl_GetByteArrayFromObj, etc.
*/
if (ReadData(interp, pngPtr, pc, 4, NULL) == TCL_ERROR) {
return TCL_ERROR;
}
temp = PNG_INT32(pc[0], pc[1], pc[2], pc[3]);
if (temp > INT_MAX) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"chunk size is out of supported range on this architecture",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "OUTSIZE", NULL);
return TCL_ERROR;
}
chunkSz = (int) temp;
crc = Tcl_ZlibCRC32(0, NULL, 0);
/*
* Read the 4-byte chunk type.
*/
if (ReadData(interp, pngPtr, pc, 4, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Convert it to a host-order integer for simple comparison.
*/
chunkType = PNG_INT32(pc[0], pc[1], pc[2], pc[3]);
/*
* Check to see if this is a known/supported chunk type. Note that the
* PNG specs require non-critical (i.e., ancillary) chunk types that
* are not recognized to be ignored, rather than be treated as an
* error. It does, however, recommend that an unknown critical chunk
* type be treated as a failure.
*
* This switch/loop acts as a filter of sorts for undesired chunk
* types. The chunk type should still be checked elsewhere for
* determining it is in the correct order.
*/
switch (chunkType) {
/*
* These chunk types are required and/or supported.
*/
case CHUNK_IDAT:
case CHUNK_IEND:
case CHUNK_IHDR:
case CHUNK_PLTE:
case CHUNK_tRNS:
break;
/*
* These chunk types are part of the standard, but are not used by
* this implementation (at least not yet). Note that these are all
* ancillary chunks (lowercase first letter).
*/
case CHUNK_bKGD:
case CHUNK_cHRM:
case CHUNK_gAMA:
case CHUNK_hIST:
case CHUNK_iCCP:
case CHUNK_iTXt:
case CHUNK_oFFs:
case CHUNK_pCAL:
case CHUNK_pHYs:
case CHUNK_sBIT:
case CHUNK_sCAL:
case CHUNK_sPLT:
case CHUNK_sRGB:
case CHUNK_tEXt:
case CHUNK_tIME:
case CHUNK_zTXt:
/*
* TODO: might want to check order here.
*/
if (SkipChunk(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
return TCL_ERROR;
}
chunkType = 0;
break;
default:
/*
* Unknown chunk type. If it's critical, we can't continue.
*/
if (!(chunkType & PNG_CF_ANCILLARY)) {
if (chunkType & PNG_INT32(128,128,128,128)) {
/*
* No nice ASCII conversion; shouldn't happen either, but
* we'll be doubly careful.
*/
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"encountered an unsupported critical chunk type",
-1));
} else {
char typeString[5];
typeString[0] = (char) ((chunkType >> 24) & 255);
typeString[1] = (char) ((chunkType >> 16) & 255);
typeString[2] = (char) ((chunkType >> 8) & 255);
typeString[3] = (char) (chunkType & 255);
typeString[4] = '\0';
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"encountered an unsupported critical chunk type"
" \"%s\"", typeString));
}
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG",
"UNSUPPORTED_CRITICAL", NULL);
return TCL_ERROR;
}
/*
* Check to see if the chunk type has legal bytes.
*/
for (i=0 ; i<4 ; i++) {
if ((pc[i] < 65) || (pc[i] > 122) ||
((pc[i] > 90) && (pc[i] < 97))) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invalid chunk type", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG",
"INVALID_CHUNK", NULL);
return TCL_ERROR;
}
}
/*
* It seems to be an otherwise legally labelled ancillary chunk
* that we don't want, so skip it after at least checking its CRC.
*/
if (SkipChunk(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
return TCL_ERROR;
}
chunkType = 0;
}
}
/*
* Found a known chunk type that's handled, albiet possibly not in the
* right order. Send back the chunk type (for further checking or
* handling), the chunk size and the current CRC for the rest of the
* calculation.
*/
*typePtr = chunkType;
*sizePtr = chunkSz;
*crcPtr = crc;
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* CheckColor --
*
* Do validation on color type, depth, and related information, and
* calculates storage requirements and offsets based on image dimensions
* and color.
*
* Results:
* TCL_OK, or TCL_ERROR if color information is invalid or some other
* failure occurs.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static int
CheckColor(
Tcl_Interp *interp,
PNGImage *pngPtr)
{
int offset;
/*
* Verify the color type is valid and the bit depth is allowed.
*/
switch (pngPtr->colorType) {
case PNG_COLOR_GRAY:
pngPtr->numChannels = 1;
if ((1 != pngPtr->bitDepth) && (2 != pngPtr->bitDepth) &&
(4 != pngPtr->bitDepth) && (8 != pngPtr->bitDepth) &&
(16 != pngPtr->bitDepth)) {
goto unsupportedDepth;
}
break;
case PNG_COLOR_RGB:
pngPtr->numChannels = 3;
if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) {
goto unsupportedDepth;
}
break;
case PNG_COLOR_PLTE:
pngPtr->numChannels = 1;
if ((1 != pngPtr->bitDepth) && (2 != pngPtr->bitDepth) &&
(4 != pngPtr->bitDepth) && (8 != pngPtr->bitDepth)) {
goto unsupportedDepth;
}
break;
case PNG_COLOR_GRAYALPHA:
pngPtr->numChannels = 2;
if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) {
goto unsupportedDepth;
}
break;
case PNG_COLOR_RGBA:
pngPtr->numChannels = 4;
if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) {
unsupportedDepth:
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"bit depth is not allowed for given color type", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_DEPTH", NULL);
return TCL_ERROR;
}
break;
default:
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown color type field %d", pngPtr->colorType));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "UNKNOWN_COLOR", NULL);
return TCL_ERROR;
}
/*
* Set up the Tk photo block's pixel size and channel offsets. offset
* array elements should already be 0 from the memset during InitPNGImage.
*/
offset = (pngPtr->bitDepth > 8) ? 2 : 1;
if (pngPtr->colorType & PNG_COLOR_USED) {
pngPtr->block.pixelSize = offset * 4;
pngPtr->block.offset[1] = offset;
pngPtr->block.offset[2] = offset * 2;
pngPtr->block.offset[3] = offset * 3;
} else {
pngPtr->block.pixelSize = offset * 2;
pngPtr->block.offset[3] = offset;
}
/*
* Calculate the block pitch, which is the number of bytes per line in the
* image, given image width and depth of color. Make sure that it it isn't
* larger than Tk can handle.
*/
if (pngPtr->block.width > INT_MAX / pngPtr->block.pixelSize) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"image pitch is out of supported range on this architecture",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "PITCH", NULL);
return TCL_ERROR;
}
pngPtr->block.pitch = pngPtr->block.pixelSize * pngPtr->block.width;
/*
* Calculate the total size of the image as represented to Tk given pitch
* and image height. Make sure that it isn't larger than Tk can handle.
*/
if (pngPtr->block.height > INT_MAX / pngPtr->block.pitch) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"image total size is out of supported range on this architecture",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "SIZE", NULL);
return TCL_ERROR;
}
pngPtr->blockLen = pngPtr->block.height * pngPtr->block.pitch;
/*
* Determine number of bytes per pixel in the source for later use.
*/
switch (pngPtr->colorType) {
case PNG_COLOR_GRAY:
pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 2 : 1;
break;
case PNG_COLOR_RGB:
pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 6 : 3;
break;
case PNG_COLOR_PLTE:
pngPtr->bytesPerPixel = 1;
break;
case PNG_COLOR_GRAYALPHA:
pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 4 : 2;
break;
case PNG_COLOR_RGBA:
pngPtr->bytesPerPixel = (pngPtr->bitDepth > 8) ? 8 : 4;
break;
default:
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown color type %d", pngPtr->colorType));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "UNKNOWN_COLOR", NULL);
return TCL_ERROR;
}
/*
* Calculate scale factor for bit depths less than 8, in order to adjust
* them to a minimum of 8 bits per pixel in the Tk image.
*/
if (pngPtr->bitDepth < 8) {
pngPtr->bitScale = 255 / (int)(pow(2, pngPtr->bitDepth) - 1);
} else {
pngPtr->bitScale = 1;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadIHDR --
*
* This function reads the PNG header from the beginning of a PNG file
* and returns the dimensions of the image.
*
* Results:
* The return value is 1 if file "f" appears to start with a valid PNG
* header, 0 otherwise. If the header is valid, then *widthPtr and
* *heightPtr are modified to hold the dimensions of the image.
*
* Side effects:
* The access position in f advances.
*
*----------------------------------------------------------------------
*/
static int
ReadIHDR(
Tcl_Interp *interp,
PNGImage *pngPtr)
{
unsigned char sigBuf[PNG_SIG_SZ];
unsigned long chunkType;
int chunkSz;
unsigned long crc;
unsigned long width, height;
int mismatch;
/*
* Read the appropriate number of bytes for the PNG signature.
*/
if (ReadData(interp, pngPtr, sigBuf, PNG_SIG_SZ, NULL) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Compare the read bytes to the expected signature.
*/
mismatch = memcmp(sigBuf, pngSignature, PNG_SIG_SZ);
/*
* If reading from string, reset position and try base64 decode.
*/
if (mismatch && pngPtr->strDataBuf) {
pngPtr->strDataBuf = Tcl_GetByteArrayFromObj(pngPtr->objDataPtr,
&pngPtr->strDataLen);
pngPtr->base64Data = pngPtr->strDataBuf;
if (ReadData(interp, pngPtr, sigBuf, PNG_SIG_SZ, NULL) == TCL_ERROR) {
return TCL_ERROR;
}
mismatch = memcmp(sigBuf, pngSignature, PNG_SIG_SZ);
}
if (mismatch) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"data stream does not have a PNG signature", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NO_SIG", NULL);
return TCL_ERROR;
}
if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
&crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Read in the IHDR (header) chunk for width, height, etc.
*
* The first chunk in the file must be the IHDR (headr) chunk.
*/
if (chunkType != CHUNK_IHDR) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"expected IHDR chunk type", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NO_IHDR", NULL);
return TCL_ERROR;
}
if (chunkSz != 13) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invalid IHDR chunk size", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_IHDR", NULL);
return TCL_ERROR;
}
/*
* Read and verify the image width and height to be sure Tk can handle its
* dimensions. The PNG specification does not permit zero-width or
* zero-height images.
*/
if (ReadInt32(interp, pngPtr, &width, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (ReadInt32(interp, pngPtr, &height, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (!width || !height || (width > INT_MAX) || (height > INT_MAX)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"image dimensions are invalid or beyond architecture limits",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "DIMENSIONS", NULL);
return TCL_ERROR;
}
/*
* Set height and width for the Tk photo block.
*/
pngPtr->block.width = (int) width;
pngPtr->block.height = (int) height;
/*
* Read and the Bit Depth and Color Type.
*/
if (ReadData(interp, pngPtr, &pngPtr->bitDepth, 1, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (ReadData(interp, pngPtr, &pngPtr->colorType, 1, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Verify that the color type is valid, the bit depth is allowed for the
* color type, and calculate the number of channels and pixel depth (bits
* per pixel * channels). Also set up offsets and sizes in the Tk photo
* block for the pixel data.
*/
if (CheckColor(interp, pngPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Only one compression method is currently defined by the standard.
*/
if (ReadData(interp, pngPtr, &pngPtr->compression, 1, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (pngPtr->compression != PNG_COMPRESS_DEFLATE) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown compression method %d", pngPtr->compression));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_COMPRESS", NULL);
return TCL_ERROR;
}
/*
* Only one filter method is currently defined by the standard; the method
* has five actual filter types associated with it.
*/
if (ReadData(interp, pngPtr, &pngPtr->filter, 1, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (pngPtr->filter != PNG_FILTMETH_STANDARD) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown filter method %d", pngPtr->filter));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_FILTER", NULL);
return TCL_ERROR;
}
if (ReadData(interp, pngPtr, &pngPtr->interlace, 1, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
switch (pngPtr->interlace) {
case PNG_INTERLACE_NONE:
case PNG_INTERLACE_ADAM7:
break;
default:
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"unknown interlace method %d", pngPtr->interlace));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_INTERLACE", NULL);
return TCL_ERROR;
}
return CheckCRC(interp, pngPtr, crc);
}
/*
*----------------------------------------------------------------------
*
* ReadPLTE --
*
* This function reads the PLTE (indexed color palette) chunk data from
* the PNG file and populates the palette table in the PNGImage
* structure.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs or the PLTE chunk is
* invalid.
*
* Side effects:
* The access position in f advances.
*
*----------------------------------------------------------------------
*/
static int
ReadPLTE(
Tcl_Interp *interp,
PNGImage *pngPtr,
int chunkSz,
unsigned long crc)
{
unsigned char buffer[PNG_PLTE_MAXSZ];
int i, c;
/*
* This chunk is mandatory for color type 3 and forbidden for 2 and 6.
*/
switch (pngPtr->colorType) {
case PNG_COLOR_GRAY:
case PNG_COLOR_GRAYALPHA:
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"PLTE chunk type forbidden for grayscale", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "PLTE_UNEXPECTED",
NULL);
return TCL_ERROR;
default:
break;
}
/*
* The palette chunk contains from 1 to 256 palette entries. Each entry
* consists of a 3-byte RGB value. It must therefore contain a non-zero
* multiple of 3 bytes, up to 768.
*/
if (!chunkSz || (chunkSz > PNG_PLTE_MAXSZ) || (chunkSz % 3)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invalid palette chunk size", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_PLTE", NULL);
return TCL_ERROR;
}
/*
* Read the palette contents and stash them for later, possibly.
*/
if (ReadData(interp, pngPtr, buffer, chunkSz, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Stash away the palette entries and entry count for later mapping each
* pixel's palette index to its color.
*/
for (i=0, c=0 ; c<chunkSz ; i++) {
pngPtr->palette[i].red = buffer[c++];
pngPtr->palette[i].green = buffer[c++];
pngPtr->palette[i].blue = buffer[c++];
}
pngPtr->paletteLen = i;
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadTRNS --
*
* This function reads the tRNS (transparency) chunk data from the PNG
* file and populates the alpha field of the palette table in the
* PNGImage structure or the single color transparency, as appropriate
* for the color type.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs or the tRNS chunk is
* invalid.
*
* Side effects:
* The access position in f advances.
*
*----------------------------------------------------------------------
*/
static int
ReadTRNS(
Tcl_Interp *interp,
PNGImage *pngPtr,
int chunkSz,
unsigned long crc)
{
unsigned char buffer[PNG_TRNS_MAXSZ];
int i;
if (pngPtr->colorType & PNG_COLOR_ALPHA) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"tRNS chunk not allowed color types with a full alpha channel",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "INVALID_TRNS", NULL);
return TCL_ERROR;
}
/*
* For indexed color, there is up to one single-byte transparency value
* per palette entry (thus a max of 256).
*/
if (chunkSz > PNG_TRNS_MAXSZ) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invalid tRNS chunk size", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_TRNS", NULL);
return TCL_ERROR;
}
/*
* Read in the raw transparency information.
*/
if (ReadData(interp, pngPtr, buffer, chunkSz, &crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
return TCL_ERROR;
}
switch (pngPtr->colorType) {
case PNG_COLOR_GRAYALPHA:
case PNG_COLOR_RGBA:
break;
case PNG_COLOR_PLTE:
/*
* The number of tRNS entries must be less than or equal to the number
* of PLTE entries, and consists of a single-byte alpha level for the
* corresponding PLTE entry.
*/
if (chunkSz > pngPtr->paletteLen) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"size of tRNS chunk is too large for the palette", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "TRNS_SIZE", NULL);
return TCL_ERROR;
}
for (i=0 ; i<chunkSz ; i++) {
pngPtr->palette[i].alpha = buffer[i];
}
break;
case PNG_COLOR_GRAY:
/*
* Grayscale uses a single 2-byte gray level, which we'll store in
* palette index 0, since we're not using the palette.
*/
if (chunkSz != 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invalid tRNS chunk size - must 2 bytes for grayscale",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_TRNS", NULL);
return TCL_ERROR;
}
/*
* According to the PNG specs, if the bit depth is less than 16, then
* only the lower byte is used.
*/
if (16 == pngPtr->bitDepth) {
pngPtr->transVal[0] = buffer[0];
pngPtr->transVal[1] = buffer[1];
} else {
pngPtr->transVal[0] = buffer[1];
}
pngPtr->useTRNS = 1;
break;
case PNG_COLOR_RGB:
/*
* TrueColor uses a single RRGGBB triplet.
*/
if (chunkSz != 6) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"invalid tRNS chunk size - must 6 bytes for RGB", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_TRNS", NULL);
return TCL_ERROR;
}
/*
* According to the PNG specs, if the bit depth is less than 16, then
* only the lower byte is used. But the tRNS chunk still contains two
* bytes per channel.
*/
if (16 == pngPtr->bitDepth) {
memcpy(pngPtr->transVal, buffer, 6);
} else {
pngPtr->transVal[0] = buffer[1];
pngPtr->transVal[1] = buffer[3];
pngPtr->transVal[2] = buffer[5];
}
pngPtr->useTRNS = 1;
break;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Paeth --
*
* Utility function for applying the Paeth filter to a pixel. The Paeth
* filter is a linear function of the pixel to be filtered and the pixels
* to the left, above, and above-left of the pixel to be unfiltered.
*
* Results:
* Result of the Paeth function for the left, above, and above-left
* pixels.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static inline unsigned char
Paeth(
int a,
int b,
int c)
{
int pa = abs(b - c);
int pb = abs(a - c);
int pc = abs(a + b - c - c);
if ((pa <= pb) && (pa <= pc)) {
return (unsigned char) a;
}
if (pb <= pc) {
return (unsigned char) b;
}
return (unsigned char) c;
}
/*
*----------------------------------------------------------------------
*
* UnfilterLine --
*
* Applies the filter algorithm specified in first byte of a line to the
* line of pixels being read from a PNG image.
*
* PNG specifies four filter algorithms (Sub, Up, Average, and Paeth)
* that combine a pixel's value with those of other pixels in the same
* and/or previous lines. Filtering is intended to make an image more
* compressible.
*
* Results:
* TCL_OK, or TCL_ERROR if the filter type is not recognized.
*
* Side effects:
* Pixel data in thisLineObj are modified.
*
*----------------------------------------------------------------------
*/
static int
UnfilterLine(
Tcl_Interp *interp,
PNGImage *pngPtr)
{
unsigned char *thisLine =
Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, NULL);
unsigned char *lastLine =
Tcl_GetByteArrayFromObj(pngPtr->lastLineObj, NULL);
#define PNG_FILTER_NONE 0
#define PNG_FILTER_SUB 1
#define PNG_FILTER_UP 2
#define PNG_FILTER_AVG 3
#define PNG_FILTER_PAETH 4
switch (*thisLine) {
case PNG_FILTER_NONE: /* Nothing to do */
break;
case PNG_FILTER_SUB: { /* Sub(x) = Raw(x) - Raw(x-bpp) */
unsigned char *rawBpp = thisLine + 1;
unsigned char *raw = rawBpp + pngPtr->bytesPerPixel;
unsigned char *end = thisLine + pngPtr->phaseSize;
while (raw < end) {
*raw++ += *rawBpp++;
}
break;
}
case PNG_FILTER_UP: /* Up(x) = Raw(x) - Prior(x) */
if (pngPtr->currentLine > startLine[pngPtr->phase]) {
unsigned char *prior = lastLine + 1;
unsigned char *raw = thisLine + 1;
unsigned char *end = thisLine + pngPtr->phaseSize;
while (raw < end) {
*raw++ += *prior++;
}
}
break;
case PNG_FILTER_AVG:
/* Avg(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) */
if (pngPtr->currentLine > startLine[pngPtr->phase]) {
unsigned char *prior = lastLine + 1;
unsigned char *rawBpp = thisLine + 1;
unsigned char *raw = rawBpp;
unsigned char *end = thisLine + pngPtr->phaseSize;
unsigned char *end2 = raw + pngPtr->bytesPerPixel;
while ((raw < end2) && (raw < end)) {
*raw++ += *prior++ / 2;
}
while (raw < end) {
*raw++ += (unsigned char)
(((int) *rawBpp++ + (int) *prior++) / 2);
}
} else {
unsigned char *rawBpp = thisLine + 1;
unsigned char *raw = rawBpp + pngPtr->bytesPerPixel;
unsigned char *end = thisLine + pngPtr->phaseSize;
while (raw < end) {
*raw++ += *rawBpp++ / 2;
}
}
break;
case PNG_FILTER_PAETH:
/* Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) */
if (pngPtr->currentLine > startLine[pngPtr->phase]) {
unsigned char *priorBpp = lastLine + 1;
unsigned char *prior = priorBpp;
unsigned char *rawBpp = thisLine + 1;
unsigned char *raw = rawBpp;
unsigned char *end = thisLine + pngPtr->phaseSize;
unsigned char *end2 = rawBpp + pngPtr->bytesPerPixel;
while ((raw < end) && (raw < end2)) {
*raw++ += *prior++;
}
while (raw < end) {
*raw++ += Paeth(*rawBpp++, *prior++, *priorBpp++);
}
} else {
unsigned char *rawBpp = thisLine + 1;
unsigned char *raw = rawBpp + pngPtr->bytesPerPixel;
unsigned char *end = thisLine + pngPtr->phaseSize;
while (raw < end) {
*raw++ += *rawBpp++;
}
}
break;
default:
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"invalid filter type %d", *thisLine));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_FILTER", NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* DecodeLine --
*
* Unfilters a line of pixels from the PNG source data and decodes the
* data into the Tk_PhotoImageBlock for later copying into the Tk image.
*
* Results:
* TCL_OK, or TCL_ERROR if the filter type is not recognized.
*
* Side effects:
* Pixel data in thisLine and block are modified and state information
* updated.
*
*----------------------------------------------------------------------
*/
static int
DecodeLine(
Tcl_Interp *interp,
PNGImage *pngPtr)
{
unsigned char *pixelPtr = pngPtr->block.pixelPtr;
int colNum = 0; /* Current pixel column */
unsigned char chan = 0; /* Current channel (0..3) = (R, G, B, A) */
unsigned char readByte = 0; /* Current scan line byte */
int haveBits = 0; /* Number of bits remaining in current byte */
unsigned char pixBits = 0; /* Extracted bits for current channel */
int shifts = 0; /* Number of channels extracted from byte */
int offset = 0; /* Current offset into pixelPtr */
int colStep = 1; /* Column increment each pass */
int pixStep = 0; /* extra pixelPtr increment each pass */
unsigned char lastPixel[6];
unsigned char *p = Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, NULL);
p++;
if (UnfilterLine(interp, pngPtr) == TCL_ERROR) {
return TCL_ERROR;
}
if (pngPtr->currentLine >= pngPtr->block.height) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"PNG image data overflow"));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "DATA_OVERFLOW", NULL);
return TCL_ERROR;
}
if (pngPtr->interlace) {
switch (pngPtr->phase) {
case 1: /* Phase 1: */
colStep = 8; /* 1 pixel per block of 8 per line */
break; /* Start at column 0 */
case 2: /* Phase 2: */
colStep = 8; /* 1 pixels per block of 8 per line */
colNum = 4; /* Start at column 4 */
break;
case 3: /* Phase 3: */
colStep = 4; /* 2 pixels per block of 8 per line */
break; /* Start at column 0 */
case 4: /* Phase 4: */
colStep = 4; /* 2 pixels per block of 8 per line */
colNum = 2; /* Start at column 2 */
break;
case 5: /* Phase 5: */
colStep = 2; /* 4 pixels per block of 8 per line */
break; /* Start at column 0 */
case 6: /* Phase 6: */
colStep = 2; /* 4 pixels per block of 8 per line */
colNum = 1; /* Start at column 1 */
break;
/* Phase 7: */
/* 8 pixels per block of 8 per line */
/* Start at column 0 */
}
}
/*
* Calculate offset into pixelPtr for the first pixel of the line.
*/
offset = pngPtr->currentLine * pngPtr->block.pitch;
/*
* Adjust up for the starting pixel of the line.
*/
offset += colNum * pngPtr->block.pixelSize;
/*
* Calculate the extra number of bytes to skip between columns.
*/
pixStep = (colStep - 1) * pngPtr->block.pixelSize;
for ( ; colNum < pngPtr->block.width ; colNum += colStep) {
if (haveBits < (pngPtr->bitDepth * pngPtr->numChannels)) {
haveBits = 0;
}
for (chan = 0 ; chan < pngPtr->numChannels ; chan++) {
if (!haveBits) {
shifts = 0;
readByte = *p++;
haveBits += 8;
}
if (16 == pngPtr->bitDepth) {
pngPtr->block.pixelPtr[offset++] = readByte;
if (pngPtr->useTRNS) {
lastPixel[chan * 2] = readByte;
}
readByte = *p++;
if (pngPtr->useTRNS) {
lastPixel[(chan * 2) + 1] = readByte;
}
pngPtr->block.pixelPtr[offset++] = readByte;
haveBits = 0;
continue;
}
switch (pngPtr->bitDepth) {
case 1:
pixBits = (unsigned char)((readByte >> (7-shifts)) & 0x01);
break;
case 2:
pixBits = (unsigned char)((readByte >> (6-shifts*2)) & 0x03);
break;
case 4:
pixBits = (unsigned char)((readByte >> (4-shifts*4)) & 0x0f);
break;
case 8:
pixBits = readByte;
break;
}
if (PNG_COLOR_PLTE == pngPtr->colorType) {
pixelPtr[offset++] = pngPtr->palette[pixBits].red;
pixelPtr[offset++] = pngPtr->palette[pixBits].green;
pixelPtr[offset++] = pngPtr->palette[pixBits].blue;
pixelPtr[offset++] = pngPtr->palette[pixBits].alpha;
chan += 2;
} else {
pixelPtr[offset++] = (unsigned char)
(pixBits * pngPtr->bitScale);
if (pngPtr->useTRNS) {
lastPixel[chan] = pixBits;
}
}
haveBits -= pngPtr->bitDepth;
shifts++;
}
/*
* Apply boolean transparency via tRNS data if necessary (where
* necessary means a tRNS chunk was provided and we're not using an
* alpha channel or indexed alpha).
*/
if ((PNG_COLOR_PLTE != pngPtr->colorType) &&
!(pngPtr->colorType & PNG_COLOR_ALPHA)) {
unsigned char alpha;
if (pngPtr->useTRNS) {
if (memcmp(lastPixel, pngPtr->transVal,
pngPtr->bytesPerPixel) == 0) {
alpha = 0x00;
} else {
alpha = 0xff;
}
} else {
alpha = 0xff;
}
pixelPtr[offset++] = alpha;
if (16 == pngPtr->bitDepth) {
pixelPtr[offset++] = alpha;
}
}
offset += pixStep;
}
if (pngPtr->interlace) {
/* Skip lines */
switch (pngPtr->phase) {
case 1: case 2: case 3:
pngPtr->currentLine += 8;
break;
case 4: case 5:
pngPtr->currentLine += 4;
break;
case 6: case 7:
pngPtr->currentLine += 2;
break;
}
/*
* Start the next phase if there are no more lines to do.
*/
if (pngPtr->currentLine >= pngPtr->block.height) {
unsigned long pixels = 0;
while ((!pixels || (pngPtr->currentLine >= pngPtr->block.height))
&& (pngPtr->phase < 7)) {
pngPtr->phase++;
switch (pngPtr->phase) {
case 2:
pixels = (pngPtr->block.width + 3) >> 3;
pngPtr->currentLine = 0;
break;
case 3:
pixels = (pngPtr->block.width + 3) >> 2;
pngPtr->currentLine = 4;
break;
case 4:
pixels = (pngPtr->block.width + 1) >> 2;
pngPtr->currentLine = 0;
break;
case 5:
pixels = (pngPtr->block.width + 1) >> 1;
pngPtr->currentLine = 2;
break;
case 6:
pixels = pngPtr->block.width >> 1;
pngPtr->currentLine = 0;
break;
case 7:
pngPtr->currentLine = 1;
pixels = pngPtr->block.width;
break;
}
}
if (16 == pngPtr->bitDepth) {
pngPtr->phaseSize = 1 + (pngPtr->numChannels * pixels * 2);
} else {
pngPtr->phaseSize = 1 + ((pngPtr->numChannels * pixels *
pngPtr->bitDepth + 7) >> 3);
}
}
} else {
pngPtr->currentLine++;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ReadIDAT --
*
* This function reads the IDAT (pixel data) chunk from the PNG file to
* build the image. It will continue reading until all IDAT chunks have
* been processed or an error occurs.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs or an IDAT chunk is
* invalid.
*
* Side effects:
* The access position in f advances. Memory may be allocated by zlib
* through PNGZAlloc.
*
*----------------------------------------------------------------------
*/
static int
ReadIDAT(
Tcl_Interp *interp,
PNGImage *pngPtr,
int chunkSz,
unsigned long crc)
{
/*
* Process IDAT contents until there is no more in this chunk.
*/
while (chunkSz && !Tcl_ZlibStreamEof(pngPtr->stream)) {
int len1, len2;
/*
* Read another block of input into the zlib stream if data remains.
*/
if (chunkSz) {
Tcl_Obj *inputObj = NULL;
int blockSz = PNG_MIN(chunkSz, PNG_BLOCK_SZ);
unsigned char *inputPtr = NULL;
/*
* Check for end of zlib stream.
*/
if (Tcl_ZlibStreamEof(pngPtr->stream)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"extra data after end of zlib stream", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA",
NULL);
return TCL_ERROR;
}
inputObj = Tcl_NewObj();
Tcl_IncrRefCount(inputObj);
inputPtr = Tcl_SetByteArrayLength(inputObj, blockSz);
/*
* Read the next bit of IDAT chunk data, up to read buffer size.
*/
if (ReadData(interp, pngPtr, inputPtr, blockSz,
&crc) == TCL_ERROR) {
Tcl_DecrRefCount(inputObj);
return TCL_ERROR;
}
chunkSz -= blockSz;
Tcl_ZlibStreamPut(pngPtr->stream, inputObj, TCL_ZLIB_NO_FLUSH);
Tcl_DecrRefCount(inputObj);
}
/*
* Inflate, processing each output buffer's worth as a line of pixels,
* until we cannot fill the buffer any more.
*/
getNextLine:
Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, &len1);
if (Tcl_ZlibStreamGet(pngPtr->stream, pngPtr->thisLineObj,
pngPtr->phaseSize - len1) == TCL_ERROR) {
return TCL_ERROR;
}
Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, &len2);
if (len2 == pngPtr->phaseSize) {
if (pngPtr->phase > 7) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"extra data after final scan line of final phase",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA",
NULL);
return TCL_ERROR;
}
if (DecodeLine(interp, pngPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Swap the current/last lines so that we always have the last
* line processed available, which is necessary for filtering.
*/
{
Tcl_Obj *temp = pngPtr->lastLineObj;
pngPtr->lastLineObj = pngPtr->thisLineObj;
pngPtr->thisLineObj = temp;
}
Tcl_SetByteArrayLength(pngPtr->thisLineObj, 0);
/*
* Try to read another line of pixels out of the buffer
* immediately, but don't allow write past end of block.
*/
if (pngPtr->currentLine < pngPtr->block.height) {
goto getNextLine;
}
}
/*
* Got less than a whole buffer-load of pixels. Either we're going to
* be getting more data from the next IDAT, or we've done what we can
* here.
*/
}
/*
* Ensure that if we've got to the end of the compressed data, we've
* also got to the end of the compressed stream. This sanity check is
* enforced by most PNG readers.
*/
if (chunkSz != 0) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"compressed data after stream finalize in PNG data", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA", NULL);
return TCL_ERROR;
}
return CheckCRC(interp, pngPtr, crc);
}
/*
*----------------------------------------------------------------------
*
* ApplyAlpha --
*
* Applies an overall alpha value to a complete image that has been read.
* This alpha value is specified using the -format option to [image
* create photo].
*
* Results:
* N/A
*
* Side effects:
* The access position in f may change.
*
*----------------------------------------------------------------------
*/
static void
ApplyAlpha(
PNGImage *pngPtr)
{
if (pngPtr->alpha != 1.0) {
register unsigned char *p = pngPtr->block.pixelPtr;
unsigned char *endPtr = p + pngPtr->blockLen;
int offset = pngPtr->block.offset[3];
p += offset;
if (16 == pngPtr->bitDepth) {
register unsigned int channel;
while (p < endPtr) {
channel = (unsigned int)
(((p[0] << 8) | p[1]) * pngPtr->alpha);
*p++ = (unsigned char) (channel >> 8);
*p++ = (unsigned char) (channel & 0xff);
p += offset;
}
} else {
while (p < endPtr) {
p[0] = (unsigned char) (pngPtr->alpha * p[0]);
p += 1 + offset;
}
}
}
}
/*
*----------------------------------------------------------------------
*
* ParseFormat --
*
* This function parses the -format string that can be specified to the
* [image create photo] command to extract options for postprocessing of
* loaded images. Currently, this just allows specifying and applying an
* overall alpha value to the loaded image (for example, to make it
* entirely 50% as transparent as the actual image file).
*
* Results:
* TCL_OK, or TCL_ERROR if the format specification is invalid.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static int
ParseFormat(
Tcl_Interp *interp,
Tcl_Obj *fmtObj,
PNGImage *pngPtr)
{
Tcl_Obj **objv = NULL;
int objc = 0;
static const char *const fmtOptions[] = {
"-alpha", NULL
};
enum fmtOptions {
OPT_ALPHA
};
/*
* Extract elements of format specification as a list.
*/
if (fmtObj &&
Tcl_ListObjGetElements(interp, fmtObj, &objc, &objv) != TCL_OK) {
return TCL_ERROR;
}
for (; objc>0 ; objc--, objv++) {
int optIndex;
/*
* Ignore the "png" part of the format specification.
*/
if (!strcasecmp(Tcl_GetString(objv[0]), "png")) {
continue;
}
if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions,
sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) {
return TCL_ERROR;
}
if (objc < 2) {
Tcl_WrongNumArgs(interp, 1, objv, "value");
return TCL_ERROR;
}
objc--;
objv++;
switch ((enum fmtOptions) optIndex) {
case OPT_ALPHA:
if (Tcl_GetDoubleFromObj(interp, objv[0],
&pngPtr->alpha) == TCL_ERROR) {
return TCL_ERROR;
}
if ((pngPtr->alpha < 0.0) || (pngPtr->alpha > 1.0)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"-alpha value must be between 0.0 and 1.0", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_ALPHA",
NULL);
return TCL_ERROR;
}
break;
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* DecodePNG --
*
* This function handles the entirety of reading a PNG file (or data)
* from the first byte to the last.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O error occurs or any problems are
* detected in the PNG file.
*
* Side effects:
* The access position in f advances. Memory may be allocated and image
* dimensions and contents may change.
*
*----------------------------------------------------------------------
*/
static int
DecodePNG(
Tcl_Interp *interp,
PNGImage *pngPtr,
Tcl_Obj *fmtObj,
Tk_PhotoHandle imageHandle,
int destX,
int destY)
{
unsigned long chunkType;
int chunkSz;
unsigned long crc;
/*
* Parse the PNG signature and IHDR (header) chunk.
*/
if (ReadIHDR(interp, pngPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Extract alpha value from -format object, if specified.
*/
if (ParseFormat(interp, fmtObj, pngPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* The next chunk may either be a PLTE (Palette) chunk or the first of at
* least one IDAT (data) chunks. It could also be one of a number of
* ancillary chunks, but those are skipped for us by the switch in
* ReadChunkHeader().
*
* PLTE is mandatory for color type 3 and forbidden for 2 and 6
*/
if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
&crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (CHUNK_PLTE == chunkType) {
/*
* Finish parsing the PLTE chunk.
*/
if (ReadPLTE(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Begin the next chunk.
*/
if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
&crc) == TCL_ERROR) {
return TCL_ERROR;
}
} else if (PNG_COLOR_PLTE == pngPtr->colorType) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"PLTE chunk required for indexed color", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NEED_PLTE", NULL);
return TCL_ERROR;
}
/*
* The next chunk may be a tRNS (palette transparency) chunk, depending on
* the color type. It must come after the PLTE chunk and before the IDAT
* chunk, but can be present if there is no PLTE chunk because it can be
* used for Grayscale and TrueColor in lieu of an alpha channel.
*/
if (CHUNK_tRNS == chunkType) {
/*
* Finish parsing the tRNS chunk.
*/
if (ReadTRNS(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Begin the next chunk.
*/
if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
&crc) == TCL_ERROR) {
return TCL_ERROR;
}
}
/*
* Other ancillary chunk types could appear here, but for now we're only
* interested in IDAT. The others should have been skipped.
*/
if (chunkType != CHUNK_IDAT) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"at least one IDAT chunk is required", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "NEED_IDAT", NULL);
return TCL_ERROR;
}
/*
* Expand the photo size (if not set by the user) to provide enough space
* for the image being parsed. It does not matter if width or height wrap
* to negative here: Tk will not shrink the image.
*/
if (Tk_PhotoExpand(interp, imageHandle, destX + pngPtr->block.width,
destY + pngPtr->block.height) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* A scan line consists of one byte for a filter type, plus the number of
* bits per color sample times the number of color samples per pixel.
*/
if (pngPtr->block.width > ((INT_MAX - 1) / (pngPtr->numChannels * 2))) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"line size is out of supported range on this architecture",
-1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "LINE_SIZE", NULL);
return TCL_ERROR;
}
if (16 == pngPtr->bitDepth) {
pngPtr->lineSize = 1 + (pngPtr->numChannels * pngPtr->block.width*2);
} else {
pngPtr->lineSize = 1 + ((pngPtr->numChannels * pngPtr->block.width) /
(8 / pngPtr->bitDepth));
if (pngPtr->block.width % (8 / pngPtr->bitDepth)) {
pngPtr->lineSize++;
}
}
/*
* Allocate space for decoding the scan lines.
*/
pngPtr->lastLineObj = Tcl_NewObj();
Tcl_IncrRefCount(pngPtr->lastLineObj);
pngPtr->thisLineObj = Tcl_NewObj();
Tcl_IncrRefCount(pngPtr->thisLineObj);
pngPtr->block.pixelPtr = attemptckalloc(pngPtr->blockLen);
if (!pngPtr->block.pixelPtr) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"memory allocation failed", -1));
Tcl_SetErrorCode(interp, "TK", "MALLOC", NULL);
return TCL_ERROR;
}
/*
* Determine size of the first phase if interlaced. Phase size should
* always be <= line size, so probably not necessary to check for
* arithmetic overflow here: should be covered by line size check.
*/
if (pngPtr->interlace) {
/*
* Only one pixel per block of 8 per line in the first phase.
*/
unsigned int pixels = (pngPtr->block.width + 7) >> 3;
pngPtr->phase = 1;
if (16 == pngPtr->bitDepth) {
pngPtr->phaseSize = 1 + pngPtr->numChannels*pixels*2;
} else {
pngPtr->phaseSize = 1 +
((pngPtr->numChannels*pixels*pngPtr->bitDepth + 7) >> 3);
}
} else {
pngPtr->phaseSize = pngPtr->lineSize;
}
/*
* All of the IDAT (data) chunks must be consecutive.
*/
while (CHUNK_IDAT == chunkType) {
if (ReadIDAT(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
&crc) == TCL_ERROR) {
return TCL_ERROR;
}
}
/*
* Ensure that we've got to the end of the compressed stream now that
* there are no more IDAT segments. This sanity check is enforced by most
* PNG readers.
*/
if (!Tcl_ZlibStreamEof(pngPtr->stream)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"unfinalized data stream in PNG data", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "EXTRA_DATA", NULL);
return TCL_ERROR;
}
/*
* Now skip the remaining chunks which we're also not interested in.
*/
while (CHUNK_IEND != chunkType) {
if (SkipChunk(interp, pngPtr, chunkSz, crc) == TCL_ERROR) {
return TCL_ERROR;
}
if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType,
&crc) == TCL_ERROR) {
return TCL_ERROR;
}
}
/*
* Got the IEND (end of image) chunk. Do some final checks...
*/
if (chunkSz) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"IEND chunk contents must be empty", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_IEND", NULL);
return TCL_ERROR;
}
/*
* Check the CRC on the IEND chunk.
*/
if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* TODO: verify that nothing else comes after the IEND chunk, or do we
* really care?
*/
#if 0
if (ReadData(interp, pngPtr, &c, 1, NULL) != TCL_ERROR) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"extra data following IEND chunk", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_IEND", NULL);
return TCL_ERROR;
}
#endif
/*
* Apply overall image alpha if specified.
*/
ApplyAlpha(pngPtr);
/*
* Copy the decoded image block into the Tk photo image.
*/
if (Tk_PhotoPutBlock(interp, imageHandle, &pngPtr->block, destX, destY,
pngPtr->block.width, pngPtr->block.height,
TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* FileMatchPNG --
*
* This function is invoked by the photo image type to see if a file
* contains image data in PNG format.
*
* Results:
* The return value is 1 if the first characters in file f look like PNG
* data, and 0 otherwise.
*
* Side effects:
* The access position in f may change.
*
*----------------------------------------------------------------------
*/
static int
FileMatchPNG(
Tcl_Channel chan,
const char *fileName,
Tcl_Obj *fmtObj,
int *widthPtr,
int *heightPtr,
Tcl_Interp *interp)
{
PNGImage png;
int match = 0;
InitPNGImage(NULL, &png, chan, NULL, TCL_ZLIB_STREAM_INFLATE);
if (ReadIHDR(interp, &png) == TCL_OK) {
*widthPtr = png.block.width;
*heightPtr = png.block.height;
match = 1;
}
CleanupPNGImage(&png);
return match;
}
/*
*----------------------------------------------------------------------
*
* FileReadPNG --
*
* This function is called by the photo image type to read PNG format
* data from a file and write it into a given photo image.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned then an error
* message is left in the interp's result.
*
* Side effects:
* The access position in file f is changed, and new data is added to the
* image given by imageHandle.
*
*----------------------------------------------------------------------
*/
static int
FileReadPNG(
Tcl_Interp *interp,
Tcl_Channel chan,
const char *fileName,
Tcl_Obj *fmtObj,
Tk_PhotoHandle imageHandle,
int destX,
int destY,
int width,
int height,
int srcX,
int srcY)
{
PNGImage png;
int result = TCL_ERROR;
result = InitPNGImage(interp, &png, chan, NULL, TCL_ZLIB_STREAM_INFLATE);
if (TCL_OK == result) {
result = DecodePNG(interp, &png, fmtObj, imageHandle, destX, destY);
}
CleanupPNGImage(&png);
return result;
}
/*
*----------------------------------------------------------------------
*
* StringMatchPNG --
*
* This function is invoked by the photo image type to see if an object
* contains image data in PNG format.
*
* Results:
* The return value is 1 if the first characters in the data are like PNG
* data, and 0 otherwise.
*
* Side effects:
* The size of the image is placed in widthPre and heightPtr.
*
*----------------------------------------------------------------------
*/
static int
StringMatchPNG(
Tcl_Obj *pObjData,
Tcl_Obj *fmtObj,
int *widthPtr,
int *heightPtr,
Tcl_Interp *interp)
{
PNGImage png;
int match = 0;
InitPNGImage(NULL, &png, NULL, pObjData, TCL_ZLIB_STREAM_INFLATE);
png.strDataBuf = Tcl_GetByteArrayFromObj(pObjData, &png.strDataLen);
if (ReadIHDR(interp, &png) == TCL_OK) {
*widthPtr = png.block.width;
*heightPtr = png.block.height;
match = 1;
}
CleanupPNGImage(&png);
return match;
}
/*
*----------------------------------------------------------------------
*
* StringReadPNG --
*
* This function is called by the photo image type to read PNG format
* data from an object and give it to the photo image.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned then an error
* message is left in the interp's result.
*
* Side effects:
* New data is added to the image given by imageHandle.
*
*----------------------------------------------------------------------
*/
static int
StringReadPNG(
Tcl_Interp *interp,
Tcl_Obj *pObjData,
Tcl_Obj *fmtObj,
Tk_PhotoHandle imageHandle,
int destX,
int destY,
int width,
int height,
int srcX,
int srcY)
{
PNGImage png;
int result = TCL_ERROR;
result = InitPNGImage(interp, &png, NULL, pObjData,
TCL_ZLIB_STREAM_INFLATE);
if (TCL_OK == result) {
result = DecodePNG(interp, &png, fmtObj, imageHandle, destX, destY);
}
CleanupPNGImage(&png);
return result;
}
/*
*----------------------------------------------------------------------
*
* WriteData --
*
* This function writes a bytes from a buffer out to the PNG image.
*
* Results:
* TCL_OK, or TCL_ERROR if the write fails.
*
* Side effects:
* File or buffer will be modified.
*
*----------------------------------------------------------------------
*/
static int
WriteData(
Tcl_Interp *interp,
PNGImage *pngPtr,
const unsigned char *srcPtr,
int srcSz,
unsigned long *crcPtr)
{
if (!srcPtr || !srcSz) {
return TCL_OK;
}
if (crcPtr) {
*crcPtr = Tcl_ZlibCRC32(*crcPtr, srcPtr, srcSz);
}
/*
* TODO: is Tcl_AppendObjToObj faster here? i.e., does Tcl join the
* objects immediately or store them in a multi-object rep?
*/
if (pngPtr->objDataPtr) {
int objSz;
unsigned char *destPtr;
Tcl_GetByteArrayFromObj(pngPtr->objDataPtr, &objSz);
if (objSz > INT_MAX - srcSz) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"image too large to store completely in byte array", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "TOO_LARGE", NULL);
return TCL_ERROR;
}
destPtr = Tcl_SetByteArrayLength(pngPtr->objDataPtr, objSz + srcSz);
if (!destPtr) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"memory allocation failed", -1));
Tcl_SetErrorCode(interp, "TK", "MALLOC", NULL);
return TCL_ERROR;
}
memcpy(destPtr+objSz, srcPtr, srcSz);
} else if (Tcl_Write(pngPtr->channel, (const char *) srcPtr, srcSz) < 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"write to channel failed: %s", Tcl_PosixError(interp)));
return TCL_ERROR;
}
return TCL_OK;
}
static inline int
WriteByte(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned char c,
unsigned long *crcPtr)
{
return WriteData(interp, pngPtr, &c, 1, crcPtr);
}
/*
*----------------------------------------------------------------------
*
* WriteInt32 --
*
* This function writes a 32-bit integer value out to the PNG image as
* four bytes in network byte order.
*
* Results:
* TCL_OK, or TCL_ERROR if the write fails.
*
* Side effects:
* File or buffer will be modified.
*
*----------------------------------------------------------------------
*/
static inline int
WriteInt32(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned long l,
unsigned long *crcPtr)
{
unsigned char pc[4];
pc[0] = (unsigned char) ((l & 0xff000000) >> 24);
pc[1] = (unsigned char) ((l & 0x00ff0000) >> 16);
pc[2] = (unsigned char) ((l & 0x0000ff00) >> 8);
pc[3] = (unsigned char) ((l & 0x000000ff) >> 0);
return WriteData(interp, pngPtr, pc, 4, crcPtr);
}
/*
*----------------------------------------------------------------------
*
* WriteChunk --
*
* Writes a complete chunk to the PNG image, including chunk type,
* length, contents, and CRC.
*
* Results:
* TCL_OK, or TCL_ERROR if the write fails.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static inline int
WriteChunk(
Tcl_Interp *interp,
PNGImage *pngPtr,
unsigned long chunkType,
const unsigned char *dataPtr,
int dataSize)
{
unsigned long crc = Tcl_ZlibCRC32(0, NULL, 0);
int result = TCL_OK;
/*
* Write the length field for the chunk.
*/
result = WriteInt32(interp, pngPtr, dataSize, NULL);
/*
* Write the Chunk Type.
*/
if (TCL_OK == result) {
result = WriteInt32(interp, pngPtr, chunkType, &crc);
}
/*
* Write the contents (if any).
*/
if (TCL_OK == result) {
result = WriteData(interp, pngPtr, dataPtr, dataSize, &crc);
}
/*
* Write out the CRC at the end of the chunk.
*/
if (TCL_OK == result) {
result = WriteInt32(interp, pngPtr, crc, NULL);
}
return result;
}
/*
*----------------------------------------------------------------------
*
* WriteIHDR --
*
* This function writes the PNG header at the beginning of a PNG file,
* which includes information such as dimensions and color type.
*
* Results:
* TCL_OK, or TCL_ERROR if the write fails.
*
* Side effects:
* File or buffer will be modified.
*
*----------------------------------------------------------------------
*/
static int
WriteIHDR(
Tcl_Interp *interp,
PNGImage *pngPtr,
Tk_PhotoImageBlock *blockPtr)
{
unsigned long crc = Tcl_ZlibCRC32(0, NULL, 0);
int result = TCL_OK;
/*
* The IHDR (header) chunk has a fixed size of 13 bytes.
*/
result = WriteInt32(interp, pngPtr, 13, NULL);
/*
* Write the IHDR Chunk Type.
*/
if (TCL_OK == result) {
result = WriteInt32(interp, pngPtr, CHUNK_IHDR, &crc);
}
/*
* Write the image width, height.
*/
if (TCL_OK == result) {
result = WriteInt32(interp, pngPtr, (unsigned long) blockPtr->width,
&crc);
}
if (TCL_OK == result) {
result = WriteInt32(interp, pngPtr, (unsigned long) blockPtr->height,
&crc);
}
/*
* Write bit depth. Although the PNG format supports 16 bits per channel,
* Tk supports only 8 in the internal representation, which blockPtr
* points to.
*/
if (TCL_OK == result) {
result = WriteByte(interp, pngPtr, 8, &crc);
}
/*
* Write out the color type, previously determined.
*/
if (TCL_OK == result) {
result = WriteByte(interp, pngPtr, pngPtr->colorType, &crc);
}
/*
* Write compression method (only one method is defined).
*/
if (TCL_OK == result) {
result = WriteByte(interp, pngPtr, PNG_COMPRESS_DEFLATE, &crc);
}
/*
* Write filter method (only one method is defined).
*/
if (TCL_OK == result) {
result = WriteByte(interp, pngPtr, PNG_FILTMETH_STANDARD, &crc);
}
/*
* Write interlace method as not interlaced.
*
* TODO: support interlace through -format?
*/
if (TCL_OK == result) {
result = WriteByte(interp, pngPtr, PNG_INTERLACE_NONE, &crc);
}
/*
* Write out the CRC at the end of the chunk.
*/
if (TCL_OK == result) {
result = WriteInt32(interp, pngPtr, crc, NULL);
}
return result;
}
/*
*----------------------------------------------------------------------
*
* WriteIDAT --
*
* Writes the IDAT (data) chunk to the PNG image, containing the pixel
* channel data. Currently, image lines are not filtered and writing
* interlaced pixels is not supported.
*
* Results:
* TCL_OK, or TCL_ERROR if the write fails.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static int
WriteIDAT(
Tcl_Interp *interp,
PNGImage *pngPtr,
Tk_PhotoImageBlock *blockPtr)
{
int rowNum, flush = TCL_ZLIB_NO_FLUSH, outputSize, result;
Tcl_Obj *outputObj;
unsigned char *outputBytes;
/*
* Filter and compress each row one at a time.
*/
for (rowNum=0 ; rowNum < blockPtr->height ; rowNum++) {
int colNum;
unsigned char *srcPtr, *destPtr;
srcPtr = blockPtr->pixelPtr + (rowNum * blockPtr->pitch);
destPtr = Tcl_SetByteArrayLength(pngPtr->thisLineObj,
pngPtr->lineSize);
/*
* TODO: use Paeth filtering.
*/
*destPtr++ = PNG_FILTER_NONE;
/*
* Copy each pixel into the destination buffer after the filter type
* before filtering.
*/
for (colNum = 0 ; colNum < blockPtr->width ; colNum++) {
/*
* Copy red or gray channel.
*/
*destPtr++ = srcPtr[blockPtr->offset[0]];
/*
* If not grayscale, copy the green and blue channels.
*/
if (pngPtr->colorType & PNG_COLOR_USED) {
*destPtr++ = srcPtr[blockPtr->offset[1]];
*destPtr++ = srcPtr[blockPtr->offset[2]];
}
/*
* Copy the alpha channel, if used.
*/
if (pngPtr->colorType & PNG_COLOR_ALPHA) {
*destPtr++ = srcPtr[blockPtr->offset[3]];
}
/*
* Point to the start of the next pixel.
*/
srcPtr += blockPtr->pixelSize;
}
/*
* Compress the line of pixels into the destination. If this is the
* last line, finalize the compressor at the same time. Note that this
* can't be just a flush; that leads to a file that some PNG readers
* choke on. [Bug 2984787]
*/
if (rowNum + 1 == blockPtr->height) {
flush = TCL_ZLIB_FINALIZE;
}
if (Tcl_ZlibStreamPut(pngPtr->stream, pngPtr->thisLineObj,
flush) != TCL_OK) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"deflate() returned error", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "DEFLATE", NULL);
return TCL_ERROR;
}
/*
* Swap line buffers to keep the last around for filtering next.
*/
{
Tcl_Obj *temp = pngPtr->lastLineObj;
pngPtr->lastLineObj = pngPtr->thisLineObj;
pngPtr->thisLineObj = temp;
}
}
/*
* Now get the compressed data and write it as one big IDAT chunk.
*/
outputObj = Tcl_NewObj();
(void) Tcl_ZlibStreamGet(pngPtr->stream, outputObj, -1);
outputBytes = Tcl_GetByteArrayFromObj(outputObj, &outputSize);
result = WriteChunk(interp, pngPtr, CHUNK_IDAT, outputBytes, outputSize);
Tcl_DecrRefCount(outputObj);
return result;
}
/*
*----------------------------------------------------------------------
*
* WriteExtraChunks --
*
* Writes an sBIT and a tEXt chunks to the PNG image, describing a bunch
* of not very important metadata that many readers seem to need anyway.
*
* Results:
* TCL_OK, or TCL_ERROR if the write fails.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static int
WriteExtraChunks(
Tcl_Interp *interp,
PNGImage *pngPtr)
{
static const unsigned char sBIT_contents[] = {
8, 8, 8, 8
};
int sBIT_length = 4;
Tcl_DString buf;
/*
* Each byte of each channel is always significant; we always write RGBA
* images with 8 bits per channel as that is what the photo image's basic
* data model is.
*/
switch (pngPtr->colorType) {
case PNG_COLOR_GRAY:
sBIT_length = 1;
break;
case PNG_COLOR_GRAYALPHA:
sBIT_length = 2;
break;
case PNG_COLOR_RGB:
case PNG_COLOR_PLTE:
sBIT_length = 3;
break;
case PNG_COLOR_RGBA:
sBIT_length = 4;
break;
}
if (WriteChunk(interp, pngPtr, CHUNK_sBIT, sBIT_contents, sBIT_length)
!= TCL_OK) {
return TCL_ERROR;
}
/*
* Say that it is Tk that made the PNG. Note that we *need* the NUL at the
* end of "Software" to be transferred; do *not* change the length
* parameter to -1 there!
*/
Tcl_DStringInit(&buf);
Tcl_DStringAppend(&buf, "Software", 9);
Tcl_DStringAppend(&buf, "Tk Toolkit v", -1);
Tcl_DStringAppend(&buf, TK_PATCH_LEVEL, -1);
if (WriteChunk(interp, pngPtr, CHUNK_tEXt,
(unsigned char *) Tcl_DStringValue(&buf),
Tcl_DStringLength(&buf)) != TCL_OK) {
Tcl_DStringFree(&buf);
return TCL_ERROR;
}
Tcl_DStringFree(&buf);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* EncodePNG --
*
* This function handles the entirety of writing a PNG file (or data)
* from the first byte to the last. No effort is made to optimize the
* image data for best compression.
*
* Results:
* TCL_OK, or TCL_ERROR if an I/O or memory error occurs.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static int
EncodePNG(
Tcl_Interp *interp,
Tk_PhotoImageBlock *blockPtr,
PNGImage *pngPtr)
{
int greenOffset, blueOffset, alphaOffset;
/*
* Determine appropriate color type based on color usage (e.g., only red
* and maybe alpha channel = grayscale).
*
* TODO: Check whether this is doing any good; Tk might just be pushing
* full RGBA data all the time through here, even though the actual image
* doesn't need it...
*/
greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
blueOffset = blockPtr->offset[2] - blockPtr->offset[0];
alphaOffset = blockPtr->offset[3];
if ((alphaOffset >= blockPtr->pixelSize) || (alphaOffset < 0)) {
alphaOffset = 0;
} else {
alphaOffset -= blockPtr->offset[0];
}
if ((greenOffset != 0) || (blueOffset != 0)) {
if (alphaOffset) {
pngPtr->colorType = PNG_COLOR_RGBA;
pngPtr->bytesPerPixel = 4;
} else {
pngPtr->colorType = PNG_COLOR_RGB;
pngPtr->bytesPerPixel = 3;
}
} else {
if (alphaOffset) {
pngPtr->colorType = PNG_COLOR_GRAYALPHA;
pngPtr->bytesPerPixel = 2;
} else {
pngPtr->colorType = PNG_COLOR_GRAY;
pngPtr->bytesPerPixel = 1;
}
}
/*
* Allocate buffers for lines for filtering and compressed data.
*/
pngPtr->lineSize = 1 + (pngPtr->bytesPerPixel * blockPtr->width);
pngPtr->blockLen = pngPtr->lineSize * blockPtr->height;
if ((blockPtr->width > (INT_MAX - 1) / (pngPtr->bytesPerPixel)) ||
(blockPtr->height > INT_MAX / pngPtr->lineSize)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
"image is too large to encode pixel data", -1));
Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "TOO_LARGE", NULL);
return TCL_ERROR;
}
pngPtr->lastLineObj = Tcl_NewObj();
Tcl_IncrRefCount(pngPtr->lastLineObj);
pngPtr->thisLineObj = Tcl_NewObj();
Tcl_IncrRefCount(pngPtr->thisLineObj);
/*
* Write out the PNG Signature that all PNGs begin with.
*/
if (WriteData(interp, pngPtr, pngSignature, PNG_SIG_SZ,
NULL) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Write out the IHDR (header) chunk containing image dimensions, color
* type, etc.
*/
if (WriteIHDR(interp, pngPtr, blockPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Write out the extra chunks containing metadata that is of interest to
* other programs more than us.
*/
if (WriteExtraChunks(interp, pngPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Write out the image pixels in the IDAT (data) chunk.
*/
if (WriteIDAT(interp, pngPtr, blockPtr) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Write out the IEND chunk that all PNGs end with.
*/
return WriteChunk(interp, pngPtr, CHUNK_IEND, NULL, 0);
}
/*
*----------------------------------------------------------------------
*
* FileWritePNG --
*
* This function is called by the photo image type to write PNG format
* data to a file.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned then an error
* message is left in the interp's result.
*
* Side effects:
* The specified file is overwritten.
*
*----------------------------------------------------------------------
*/
static int
FileWritePNG(
Tcl_Interp *interp,
const char *filename,
Tcl_Obj *fmtObj,
Tk_PhotoImageBlock *blockPtr)
{
Tcl_Channel chan;
PNGImage png;
int result = TCL_ERROR;
/*
* Open a Tcl file channel where the image data will be stored. Tk ought
* to take care of this, and just provide a channel, but it doesn't.
*/
chan = Tcl_OpenFileChannel(interp, filename, "w", 0644);
if (!chan) {
return TCL_ERROR;
}
/*
* Initalize PNGImage instance for encoding.
*/
if (InitPNGImage(interp, &png, chan, NULL,
TCL_ZLIB_STREAM_DEFLATE) == TCL_ERROR) {
goto cleanup;
}
/*
* Set the translation mode to binary so that CR and LF are not to the
* platform's EOL sequence.
*/
if (Tcl_SetChannelOption(interp, chan, "-translation",
"binary") != TCL_OK) {
goto cleanup;
}
/*
* Write the raw PNG data out to the file.
*/
result = EncodePNG(interp, blockPtr, &png);
cleanup:
Tcl_Close(interp, chan);
CleanupPNGImage(&png);
return result;
}
/*
*----------------------------------------------------------------------
*
* StringWritePNG --
*
* This function is called by the photo image type to write PNG format
* data to a Tcl object and return it in the result.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned then an error
* message is left in the interp's result.
*
* Side effects:
* None
*
*----------------------------------------------------------------------
*/
static int
StringWritePNG(
Tcl_Interp *interp,
Tcl_Obj *fmtObj,
Tk_PhotoImageBlock *blockPtr)
{
Tcl_Obj *resultObj = Tcl_NewObj();
PNGImage png;
int result = TCL_ERROR;
/*
* Initalize PNGImage instance for encoding.
*/
if (InitPNGImage(interp, &png, NULL, resultObj,
TCL_ZLIB_STREAM_DEFLATE) == TCL_ERROR) {
goto cleanup;
}
/*
* Write the raw PNG data into the prepared Tcl_Obj buffer. Set the result
* back to the interpreter if successful.
*/
result = EncodePNG(interp, blockPtr, &png);
if (TCL_OK == result) {
Tcl_SetObjResult(interp, png.objDataPtr);
}
cleanup:
CleanupPNGImage(&png);
return result;
}
/*
* Local Variables:
* c-basic-offset: 4
* fill-column: 78
* End:
*/