3563 lines
90 KiB
C
3563 lines
90 KiB
C
/*
|
||
* 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 == -1) {
|
||
/* 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, (int *)NULL);
|
||
unsigned char *lastLine =
|
||
Tcl_GetByteArrayFromObj(pngPtr->lastLineObj, (int *)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, (int *)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) {
|
||
unsigned char *p = pngPtr->block.pixelPtr;
|
||
unsigned char *endPtr = p + pngPtr->blockLen;
|
||
int offset = pngPtr->block.offset[3];
|
||
|
||
p += offset;
|
||
|
||
if (16 == pngPtr->bitDepth) {
|
||
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) == -1) {
|
||
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:
|
||
*/
|