diff options
-rw-r--r-- | ChangeLog | 14 | ||||
-rw-r--r-- | doc/photo.n | 50 | ||||
-rw-r--r-- | generic/tkImgPNG.c | 3381 | ||||
-rw-r--r-- | generic/tkInt.h | 3 | ||||
-rw-r--r-- | generic/tkWindow.c | 3 | ||||
-rw-r--r-- | tests/imgPNG.test | 72 | ||||
-rw-r--r-- | unix/Makefile.in | 11 | ||||
-rw-r--r-- | win/Makefile.in | 3 | ||||
-rw-r--r-- | win/makefile.vc | 3 |
9 files changed, 3524 insertions, 16 deletions
@@ -1,8 +1,16 @@ +2008-12-28 Donal K. Fellows <dkf@users.sf.net> + + TIP #244 IMPLEMENTATION + + * generic/tkImgPNG.c, tests/imgPNG.test, doc/photo.n: Adaptation of + tkpng to the Tk core, proving support for PNG image reading and + writing, based on Tcl's zlib support. + 2008-12-27 Joe English <jenglish@users.sourceforge.net> - * generic/ttk/ttkTreeview.c: Fix inconsistent use of - treeArea / headingArea; fixes [Bug 2381555]. ([$tv identify] - didn't work when horizontally scrolled). + * generic/ttk/ttkTreeview.c: Fix inconsistent use of treeArea / + headingArea; fixes [Bug 2381555]. ([$tv identify] didn't work when + horizontally scrolled). 2008-12-21 Donal K. Fellows <dkf@users.sf.net> diff --git a/doc/photo.n b/doc/photo.n index 7024b7f..67dbbdb 100644 --- a/doc/photo.n +++ b/doc/photo.n @@ -9,7 +9,7 @@ '\" Department of Computer Science, '\" Australian National University. '\" -'\" RCS: @(#) $Id: photo.n,v 1.24 2008/09/23 13:36:46 dkf Exp $ +'\" RCS: @(#) $Id: photo.n,v 1.25 2008/12/28 13:08:38 dkf Exp $ '\" .so man.macros .TH photo n 4.0 Tk "Tk Built-In Commands" @@ -27,7 +27,11 @@ transparent. A photo image is stored internally in full color (32 bits per pixel), and is displayed using dithering if necessary. Image data for a photo image can be obtained from a file or a string, or it can be supplied from -C code through a procedural interface. At present, only GIF and PPM/PGM +C code through a procedural interface. At present, only +.VS 8.6 +PNG, +.VE 8.6 +GIF and PPM/PGM formats are supported, but an interface exists to allow additional image file formats to be added easily. A photo image is transparent in regions where no image data has been supplied @@ -42,7 +46,8 @@ Photos support the following \fIoptions\fR: \fB\-data \fIstring\fR Specifies the contents of the image as a string. The string should contain binary data or, for some formats, base64-encoded data (this is -currently guaranteed to be supported for GIF images). The format of the +currently guaranteed to be supported for PNG and GIF images). The +format of the string must be one of those for which there is an image file format handler that will accept string data. If both the \fB\-data\fR and \fB\-file\fR options are specified, the \fB\-file\fR option takes @@ -362,8 +367,8 @@ The photo image code is structured to allow handlers for additional image file formats to be added easily. The photo image code maintains a list of these handlers. Handlers are added to the list by registering them with a call to \fBTk_CreatePhotoImageFormat\fR. The -standard Tk distribution comes with handlers for PPM/PGM and GIF formats, -which are automatically registered on initialization. +standard Tk distribution comes with handlers for PPM/PGM, PNG and GIF +formats, which are automatically registered on initialization. .PP When reading an image file or processing string data specified with the \fB\-data\fR configuration option, the @@ -387,6 +392,27 @@ that, which the handler can use, for example, to specify which variant to use of the formats supported by the handler. Note that not all image handlers may support writing transparency data to a file, even where the target image format does. +.SS "FORMAT SUBOPTIONS" +.PP +.VS 8.6 +Some image formats support sub-options, which are specified at the time that +the image is loaded using additional words in the \fB\-format\fR option. At +the time of writing, the following are supported: +.TP +\fBgif \-index\fI indexValue\fR +. +When parsing a multi-part GIF image, Tk normally only accesses the first +image. By giving the \fB\-index\fR sub-option, the \fIindexValue\fR'th value +may be used instead. The \fIindexValue\fR must be an integer from 0 up to the +number of image parts in the GIF data. +.TP +\fBpng \-alpha\fI alphaValue\fR +. +An additional alpha filtering for the overall image, which allows the +background on which the image is displayed to show through. This usually also +has the effect of desaturating the image. The \fIalphaValue\fR must be between +0.0 and 1.0. +.VE 8.6 .SH "COLOR ALLOCATION" .PP When a photo image is displayed in a window, the photo image code @@ -425,6 +451,7 @@ John Ousterhout. .PP Load an image from a file and tile it to the size of a window, which is useful for producing a tiled background: +.PP .CS # These lines should be called once \fBimage create photo\fR untiled \-file "theFile.ppm" @@ -436,6 +463,19 @@ set width [winfo width .someWidget] set height [winfo height .someWidget] tiled \fBcopy\fR untiled \-to 0 0 $width $height \-shrink .CE +.PP +.VS 8.6 +The PNG image loader allows the application of an additional alpha factor +during loading, which is useful for generating images suitable for disabled +buttons: +.PP +.CS +\fBimage create photo\fR icon \-file "icon.png" +\fBimage create photo\fR iconDisabled \-file "icon.png" \e + \-format "png \-alpha 0.5" +button .b \-image icon \-disabledimage iconDisabled +.CE +.VE 8.6 .SH "SEE ALSO" image(n) .SH KEYWORDS diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c new file mode 100644 index 0000000..eb5ef7c --- /dev/null +++ b/generic/tkImgPNG.c @@ -0,0 +1,3381 @@ +/* + * 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. + * + * RCS: @(#) $Id: tkImgPNG.c,v 1.1 2008/12/28 13:08:39 dkf Exp $ + */ + +#include "tkInt.h" + +#define PNG_INT32(a,b,c,d) \ + (((long)(a) << 24) | ((long)(b) << 16) | ((long)(c) << 8) | (long)(d)) +#define PNG_BLOCK_SZ 1024 /* Process up to 1k at a time. */ +#define PNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/* + * Every PNG image starts with the following 8-byte signature. + */ + +#define PNG_SIG_SZ 8 +static const unsigned char pngSignature[] = { + 137, 80, 78, 71, 13, 10, 26, 10 +}; + +static const int startLine[8] = { + 0, 0, 0, 4, 0, 2, 0, 1 +}; + +/* + * Chunk type flags. + */ + +#define PNG_CF_ANCILLARY 0x10000000L /* Non-critical chunk (can ignore). */ +#define PNG_CF_PRIVATE 0x00100000L /* Application-specific chunk. */ +#define PNG_CF_RESERVED 0x00001000L /* Not used. */ +#define PNG_CF_COPYSAFE 0x00000010L /* Opaque data safe for copying. */ + +/* + * Chunk types, not all of which have support implemented. Note that there are + * others in the official extension set which we will never support (as they + * are officially deprecated). + */ + +#define CHUNK_IDAT PNG_INT32('I','D','A','T') /* Pixel data. */ +#define CHUNK_IEND PNG_INT32('I','E','N','D') /* End of Image. */ +#define CHUNK_IHDR PNG_INT32('I','H','D','R') /* Header. */ +#define CHUNK_PLTE PNG_INT32('P','L','T','E') /* Palette. */ + +#define CHUNK_bKGD PNG_INT32('b','K','G','D') /* Background Color */ +#define CHUNK_cHRM PNG_INT32('c','H','R','M') /* Chroma values. */ +#define CHUNK_gAMA PNG_INT32('g','A','M','A') /* Gamma. */ +#define CHUNK_hIST PNG_INT32('h','I','S','T') /* Histogram. */ +#define CHUNK_iCCP PNG_INT32('i','C','C','P') /* Color profile. */ +#define CHUNK_iTXt PNG_INT32('i','T','X','t') /* Internationalized + * text (comments, + * etc.) */ +#define CHUNK_oFFs PNG_INT32('o','F','F','s') /* Image offset. */ +#define CHUNK_pCAL PNG_INT32('p','C','A','L') /* Pixel calibration + * data. */ +#define CHUNK_pHYs PNG_INT32('p','H','Y','s') /* Physical pixel + * dimensions. */ +#define CHUNK_sBIT PNG_INT32('s','B','I','T') /* Significant bits */ +#define CHUNK_sCAL PNG_INT32('s','C','A','L') /* Physical scale. */ +#define CHUNK_sPLT PNG_INT32('s','P','L','T') /* Suggested + * palette. */ +#define CHUNK_sRGB PNG_INT32('s','R','G','B') /* Standard RGB space + * declaration. */ +#define CHUNK_tEXt PNG_INT32('t','E','X','t') /* Plain Latin-1 + * text. */ +#define CHUNK_tIME PNG_INT32('t','I','M','E') /* Time stamp. */ +#define CHUNK_tRNS PNG_INT32('t','R','N','S') /* Transparency. */ +#define CHUNK_zTXt PNG_INT32('z','T','X','t') /* Compressed Latin-1 + * text. */ + +/* + * Color flags. + */ + +#define PNG_COLOR_INDEXED 1 +#define PNG_COLOR_USED 2 +#define PNG_COLOR_ALPHA 4 + +/* + * Actual color types. + */ + +#define PNG_COLOR_GRAY 0 +#define PNG_COLOR_RGB (PNG_COLOR_USED) +#define PNG_COLOR_PLTE (PNG_COLOR_USED | PNG_COLOR_INDEXED) +#define PNG_COLOR_GRAYALPHA (PNG_COLOR_GRAY | PNG_COLOR_ALPHA) +#define PNG_COLOR_RGBA (PNG_COLOR_USED | PNG_COLOR_ALPHA) + +/* + * Compression Methods. + */ + +#define PNG_COMPRESS_DEFLATE 0 + +/* + * Filter Methods. + */ + +#define PNG_FILTMETH_STANDARD 0 + +/* + * Interlacing Methods. + */ + +#define PNG_INTERLACE_NONE 0 +#define PNG_INTERLACE_ADAM7 1 + +/* + * State information, used to store everything about the PNG image being + * currently parsed or created. + */ + +typedef struct { + /* + * PNG data source/destination channel/object/byte array. + */ + + Tcl_Channel channel; /* Channel for from-file reads. */ + Tcl_Obj *objDataPtr; + unsigned char *strDataBuf; /* Raw source data for from-string reads. */ + int strDataLen; /* Length of source data. */ + unsigned char *base64Data; /* base64 encoded string data. */ + unsigned char base64Bits; /* Remaining bits from last base64 read. */ + unsigned char base64State; /* Current state of base64 decoder. */ + double alpha; /* Alpha from -format option. */ + + /* + * Image header information. + */ + + unsigned char bitDepth; /* Number of bits per pixel. */ + unsigned char colorType; /* Grayscale, TrueColor, etc. */ + unsigned char compression; /* Compression Mode (always zlib). */ + unsigned char filter; /* Filter mode (0 - 3). */ + unsigned char interlace; /* Type of interlacing (if any). */ + unsigned char numChannels; /* Number of channels per pixel. */ + unsigned char bytesPerPixel;/* Bytes per pixel in scan line. */ + int bitScale; /* Scale factor for RGB/Gray depths < 8. */ + int currentLine; /* Current line being unfiltered. */ + unsigned char phase; /* Interlacing phase (0..6). */ + Tk_PhotoImageBlock block; + int blockLen; /* Number of bytes in Tk image pixels. */ + + /* + * For containing data read from PLTE (palette) and tRNS (transparency) + * chunks. + */ + + int paletteLen; /* Number of PLTE entries (1..256). */ + int useTRNS; /* Flag to indicate whether there was a + * palette given. */ + struct { + unsigned char red; + unsigned char green; + unsigned char blue; + unsigned char alpha; + } palette[256]; /* Palette RGB/Transparency table. */ + unsigned char transVal[6]; /* Fully-transparent RGB/Gray Value. */ + + /* + * For compressing and decompressing IDAT chunks. + */ + + Tcl_ZlibStream stream; /* Inflating or deflating stream; this one is + * not bound to a Tcl command. */ + Tcl_Obj *lastLineObj; /* Last line of pixels, for unfiltering. */ + Tcl_Obj *thisLineObj; /* Current line of pixels to process. */ + int lineSize; /* Number of bytes in a PNG line. */ + int phaseSize; /* Number of bytes/line in current phase. */ +} PNGImage; + +/* + * Maximum size of various chunks. + */ + +#define PNG_PLTE_MAXSZ 768 /* 3 bytes/RGB entry, 256 entries max */ +#define PNG_TRNS_MAXSZ 256 /* 1-byte alpha, 256 entries max */ + +/* + * Forward declarations of non-global functions defined in this file: + */ + +static void ApplyAlpha(PNGImage *pngPtr); +static int CheckColor(Tcl_Interp *interp, PNGImage *pngPtr); +static inline int CheckCRC(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned long calculated); +static void CleanupPNGImage(PNGImage *pngPtr); +static int DecodeLine(Tcl_Interp *interp, PNGImage *pngPtr); +static int DecodePNG(Tcl_Interp *interp, PNGImage *pngPtr, + Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle, + int destX, int destY); +static int EncodePNG(Tcl_Interp *interp, + Tk_PhotoImageBlock *blockPtr, PNGImage *pngPtr); +static int FileMatchPNG(Tcl_Channel chan, const char *fileName, + Tcl_Obj *fmtObj, int *widthPtr, int *heightPtr, + Tcl_Interp *interp); +static int FileReadPNG(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *fmtObj, + Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY); +static int FileWritePNG(Tcl_Interp *interp, const char *filename, + Tcl_Obj *fmtObj, Tk_PhotoImageBlock *blockPtr); +static int InitPNGImage(Tcl_Interp *interp, PNGImage *pngPtr, + Tcl_Channel chan, Tcl_Obj *objPtr, int dir); +static inline unsigned char Paeth(int a, int b, int c); +static int ParseFormat(Tcl_Interp *interp, Tcl_Obj *fmtObj, + PNGImage *pngPtr); +static int ReadBase64(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned char *destPtr, int destSz, + unsigned long *crcPtr); +static int ReadByteArray(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned char *destPtr, int destSz, + unsigned long *crcPtr); +static int ReadData(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned char *destPtr, int destSz, + unsigned long *crcPtr); +static int ReadChunkHeader(Tcl_Interp *interp, PNGImage *pngPtr, + int *sizePtr, unsigned long *typePtr, + unsigned long *crcPtr); +static int ReadIDAT(Tcl_Interp *interp, PNGImage *pngPtr, + int chunkSz, unsigned long crc); +static int ReadIHDR(Tcl_Interp *interp, PNGImage *pngPtr); +static inline int ReadInt32(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned long *resultPtr, unsigned long *crcPtr); +static int ReadPLTE(Tcl_Interp *interp, PNGImage *pngPtr, + int chunkSz, unsigned long crc); +static int ReadTRNS(Tcl_Interp *interp, PNGImage *pngPtr, + int chunkSz, unsigned long crc); +static int SkipChunk(Tcl_Interp *interp, PNGImage *pngPtr, + int chunkSz, unsigned long crc); +static int StringMatchPNG(Tcl_Obj *dataObj, Tcl_Obj *fmtObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp); +static int StringReadPNG(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY); +static int StringWritePNG(Tcl_Interp *interp, Tcl_Obj *fmtObj, + Tk_PhotoImageBlock *blockPtr); +static int UnfilterLine(Tcl_Interp *interp, PNGImage *pngPtr); +static inline int WriteByte(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned char c, unsigned long *crcPtr); +static inline int WriteChunk(Tcl_Interp *interp, PNGImage *pngPtr, + unsigned long chunkType, + const unsigned char *dataPtr, int dataSize); +static int WriteData(Tcl_Interp *interp, PNGImage *pngPtr, + const unsigned char *srcPtr, int srcSz, + unsigned long *crcPtr); +static int 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 */ +}; + +/* + *---------------------------------------------------------------------- + * + * 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) { + Tcl_SetResult(interp, "zlib initialization failed", TCL_STATIC); + 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((char *) 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_SetResult(interp, "Unexpected end of image data", TCL_STATIC); + 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_SetResult(interp, "Unexpected end of image data", TCL_STATIC); + 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); + + /* + * Check for read failure. + */ + + if (blockSz < 0) { + /* TODO: failure info... */ + Tcl_SetResult(interp, "Channel read failed", TCL_STATIC); + 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_SetResult(interp, "Unexpected end of file ", TCL_STATIC); + 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_SetResult(interp, "CRC check failed", TCL_STATIC); + 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_SetResult(interp, "Chunk size is out of supported range " + "on this architecture", TCL_STATIC); + 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)) { + Tcl_SetResult(interp, + "Encountered an unsupported criticial chunk type", + TCL_STATIC); + 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_SetResult(interp, "Invalid chunk type", TCL_STATIC); + 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 result = TCL_OK; + 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)) { + result = TCL_ERROR; + } + break; + + case PNG_COLOR_RGB: + pngPtr->numChannels = 3; + if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) { + result = TCL_ERROR; + } + break; + + case PNG_COLOR_PLTE: + pngPtr->numChannels = 1; + if ((1 != pngPtr->bitDepth) && (2 != pngPtr->bitDepth) && + (4 != pngPtr->bitDepth) && (8 != pngPtr->bitDepth)) { + result = TCL_ERROR; + } + break; + + case PNG_COLOR_GRAYALPHA: + pngPtr->numChannels = 2; + if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) { + result = TCL_ERROR; + } + break; + + case PNG_COLOR_RGBA: + pngPtr->numChannels = 4; + if ((8 != pngPtr->bitDepth) && (16 != pngPtr->bitDepth)) { + result = TCL_ERROR; + } + break; + + default: + Tcl_SetResult(interp, "Unknown Color Type field", TCL_STATIC); + return TCL_ERROR; + } + + if (TCL_ERROR == result) { + Tcl_SetResult(interp, "Bit depth is not allowed for given color type", + TCL_STATIC); + 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_SetResult(interp, + "Image pitch is out of supported range on this architecture", + TCL_STATIC); + 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_SetResult(interp, "Image total size is out of supported range " + "on this architecture", TCL_STATIC); + 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_SetResult(interp, "internal error - unknown color type", + TCL_STATIC); + 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 / (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_SetResult(interp, "Data stream does not have a PNG signature", + TCL_STATIC); + 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_SetResult(interp, "Expected IHDR chunk type", TCL_STATIC); + return TCL_ERROR; + } + + if (chunkSz != 13) { + Tcl_SetResult(interp, "Invalid IHDR chunk size", TCL_STATIC); + 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_SetResult(interp, + "Image dimensions are invalid or beyond architecture limits", + TCL_STATIC); + 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 (PNG_COMPRESS_DEFLATE != pngPtr->compression) { + Tcl_SetResult(interp, "Unknown compression method", TCL_STATIC); + 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 (PNG_FILTMETH_STANDARD != pngPtr->filter) { + Tcl_SetResult(interp, "Unknown filter method", TCL_STATIC); + 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_SetResult(interp, "Unknown interlace method", TCL_STATIC); + 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_SetResult(interp, "PLTE chunk type forbidden for grayscale", + TCL_STATIC); + 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_SetResult(interp, "Invalid palette chunk size", TCL_STATIC); + 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_SetResult(interp, + "tRNS chunk not allowed color types with a full alpha channel", + TCL_STATIC); + 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_SetResult(interp, "Invalid tRNS chunk size", TCL_STATIC); + 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_SetResult(interp, + "Size of tRNS chunk is too large for the palette", + TCL_STATIC); + 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_SetResult(interp, + "Invalid tRNS chunk size - must 2 bytes for grayscale", + TCL_STATIC); + 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_SetResult(interp, + "Invalid tRNS chunk size - must 6 bytes for RGB", + TCL_STATIC); + return TCL_ERROR; + } + + /* + * According to the PNG specs, if the bit depth is less than 16, then + * only the lower byte is used. But the tRNS chunk still contains two + * bytes per channel. + */ + + if (16 == pngPtr->bitDepth) { + memcpy(pngPtr->transVal, buffer, 6); + } else { + pngPtr->transVal[0] = buffer[1]; + pngPtr->transVal[1] = buffer[3]; + pngPtr->transVal[2] = buffer[5]; + } + pngPtr->useTRNS = 1; + break; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Paeth -- + * + * Utility function for applying the Paeth filter to a pixel. The Paeth + * filter is a linear function of the pixel to be filtered and the pixels + * to the left, above, and above-left of the pixel to be unfiltered. + * + * Results: + * Result of the Paeth function for the left, above, and above-left + * pixels. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static inline unsigned char +Paeth( + int a, + int b, + int c) +{ + int pa = abs(b - c); + int pb = abs(a - c); + int pc = abs(a + b - c - c); + + if ((pa <= pb) && (pa <= pc)) { + return (unsigned char) a; + } + + if (pb <= pc) { + return (unsigned char) b; + } + + return (unsigned char) c; +} + +/* + *---------------------------------------------------------------------- + * + * UnfilterLine -- + * + * Applies the filter algorithm specified in first byte of a line to the + * line of pixels being read from a PNG image. + * + * PNG specifies four filter algorithms (Sub, Up, Average, and Paeth) + * that combine a pixel's value with those of other pixels in the same + * and/or previous lines. Filtering is intended to make an image more + * compressible. + * + * Results: + * TCL_OK, or TCL_ERROR if the filter type is not recognized. + * + * Side effects: + * Pixel data in thisLineObj are modified. + * + *---------------------------------------------------------------------- + */ + +static int +UnfilterLine( + Tcl_Interp *interp, + PNGImage *pngPtr) +{ + unsigned char *thisLine = + Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, NULL); + unsigned char *lastLine = + Tcl_GetByteArrayFromObj(pngPtr->lastLineObj, NULL); + +#define PNG_FILTER_NONE 0 +#define PNG_FILTER_SUB 1 +#define PNG_FILTER_UP 2 +#define PNG_FILTER_AVG 3 +#define PNG_FILTER_PAETH 4 + + switch (*thisLine) { + case PNG_FILTER_NONE: /* Nothing to do */ + break; + case PNG_FILTER_SUB: { /* Sub(x) = Raw(x) - Raw(x-bpp) */ + unsigned char *rawBpp = thisLine + 1; + unsigned char *raw = rawBpp + pngPtr->bytesPerPixel; + unsigned char *end = thisLine + pngPtr->phaseSize; + + while (raw < end) { + *raw++ += *rawBpp++; + } + break; + } + case PNG_FILTER_UP: /* Up(x) = Raw(x) - Prior(x) */ + if (pngPtr->currentLine > startLine[pngPtr->phase]) { + unsigned char *prior = lastLine + 1; + unsigned char *raw = thisLine + 1; + unsigned char *end = thisLine + pngPtr->phaseSize; + + while (raw < end) { + *raw++ += *prior++; + } + } + break; + case PNG_FILTER_AVG: + /* Avg(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) */ + if (pngPtr->currentLine > startLine[pngPtr->phase]) { + unsigned char *prior = lastLine + 1; + unsigned char *rawBpp = thisLine + 1; + unsigned char *raw = rawBpp; + unsigned char *end = thisLine + pngPtr->phaseSize; + unsigned char *end2 = raw + pngPtr->bytesPerPixel; + + while ((raw < end2) && (raw < end)) { + *raw++ += *prior++ / 2; + } + + while (raw < end) { + *raw++ += (unsigned char) + (((int) *rawBpp++ + (int) *prior++) / 2); + } + } else { + unsigned char *rawBpp = thisLine + 1; + unsigned char *raw = rawBpp + pngPtr->bytesPerPixel; + unsigned char *end = thisLine + pngPtr->phaseSize; + + while (raw < end) { + *raw++ += *rawBpp++ / 2; + } + } + break; + case PNG_FILTER_PAETH: + /* Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) */ + if (pngPtr->currentLine > startLine[pngPtr->phase]) { + unsigned char *priorBpp = lastLine + 1; + unsigned char *prior = priorBpp; + unsigned char *rawBpp = thisLine + 1; + unsigned char *raw = rawBpp; + unsigned char *end = thisLine + pngPtr->phaseSize; + unsigned char *end2 = rawBpp + pngPtr->bytesPerPixel; + + while ((raw < end) && (raw < end2)) { + *raw++ += *prior++; + } + + while (raw < end) { + *raw++ += Paeth(*rawBpp++, *prior++, *priorBpp++); + } + } else { + unsigned char *rawBpp = thisLine + 1; + unsigned char *raw = rawBpp + pngPtr->bytesPerPixel; + unsigned char *end = thisLine + pngPtr->phaseSize; + + while (raw < end) { + *raw++ += *rawBpp++; + } + } + break; + default: + Tcl_SetResult(interp, "Invalid filter type", TCL_STATIC); + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * DecodeLine -- + * + * Unfilters a line of pixels from the PNG source data and decodes the + * data into the Tk_PhotoImageBlock for later copying into the Tk image. + * + * Results: + * TCL_OK, or TCL_ERROR if the filter type is not recognized. + * + * Side effects: + * Pixel data in thisLine and block are modified and state information + * updated. + * + *---------------------------------------------------------------------- + */ + +static int +DecodeLine( + Tcl_Interp *interp, + PNGImage *pngPtr) +{ + unsigned char *pixelPtr = pngPtr->block.pixelPtr; + int colNum = 0; /* Current pixel column */ + unsigned char chan = 0; /* Current channel (0..3) = (R, G, B, A) */ + unsigned char readByte = 0; /* Current scan line byte */ + int haveBits = 0; /* Number of bits remaining in current byte */ + unsigned char pixBits = 0; /* Extracted bits for current channel */ + int shifts = 0; /* Number of channels extracted from byte */ + int offset = 0; /* Current offset into pixelPtr */ + int colStep = 1; /* Column increment each pass */ + int pixStep = 0; /* extra pixelPtr increment each pass */ + unsigned char lastPixel[6]; + unsigned char *p = Tcl_GetByteArrayFromObj(pngPtr->thisLineObj, NULL); + + p++; + if (UnfilterLine(interp, pngPtr) == TCL_ERROR) { + return TCL_ERROR; + } + + if (pngPtr->interlace) { + switch (pngPtr->phase) { + case 1: /* Phase 1: */ + colStep = 8; /* 1 pixel per block of 8 per line */ + break; /* Start at column 0 */ + case 2: /* Phase 2: */ + colStep = 8; /* 1 pixels per block of 8 per line */ + colNum = 4; /* Start at column 4 */ + break; + case 3: /* Phase 3: */ + colStep = 4; /* 2 pixels per block of 8 per line */ + break; /* Start at column 0 */ + case 4: /* Phase 4: */ + colStep = 4; /* 2 pixels per block of 8 per line */ + colNum = 2; /* Start at column 2 */ + break; + case 5: /* Phase 5: */ + colStep = 2; /* 4 pixels per block of 8 per line */ + break; /* Start at column 0 */ + case 6: /* Phase 6: */ + colStep = 2; /* 4 pixels per block of 8 per line */ + colNum = 1; /* Start at column 1 */ + break; + /* Phase 7: */ + /* 8 pixels per block of 8 per line */ + /* Start at column 0 */ + } + } + + /* + * Calculate offset into pixelPtr for the first pixel of the line. + */ + + offset = pngPtr->currentLine * pngPtr->block.pitch; + + /* + * Adjust up for the starting pixel of the line. + */ + + offset += colNum * pngPtr->block.pixelSize; + + /* + * Calculate the extra number of bytes to skip between columns. + */ + + pixStep = (colStep - 1) * pngPtr->block.pixelSize; + + for ( ; colNum < pngPtr->block.width ; colNum += colStep) { + if (haveBits < (pngPtr->bitDepth * pngPtr->numChannels)) { + haveBits = 0; + } + + for (chan = 0 ; chan < pngPtr->numChannels ; chan++) { + if (!haveBits) { + shifts = 0; + readByte = *p++; + haveBits += 8; + } + + if (16 == pngPtr->bitDepth) { + pngPtr->block.pixelPtr[offset++] = readByte; + + if (pngPtr->useTRNS) { + lastPixel[chan * 2] = readByte; + } + + readByte = *p++; + + if (pngPtr->useTRNS) { + lastPixel[(chan * 2) + 1] = readByte; + } + + pngPtr->block.pixelPtr[offset++] = readByte; + + haveBits = 0; + continue; + } + + switch (pngPtr->bitDepth) { + case 1: + pixBits = (unsigned char)((readByte >> (7-shifts)) & 0x01); + break; + case 2: + pixBits = (unsigned char)((readByte >> (6-shifts*2)) & 0x03); + break; + case 4: + pixBits = (unsigned char)((readByte >> (4-shifts*4)) & 0x0f); + break; + case 8: + pixBits = readByte; + break; + } + + if (PNG_COLOR_PLTE == pngPtr->colorType) { + pixelPtr[offset++] = pngPtr->palette[pixBits].red; + pixelPtr[offset++] = pngPtr->palette[pixBits].green; + pixelPtr[offset++] = pngPtr->palette[pixBits].blue; + pixelPtr[offset++] = pngPtr->palette[pixBits].alpha; + chan += 2; + } else { + pixelPtr[offset++] = (unsigned char) + (pixBits * pngPtr->bitScale); + + if (pngPtr->useTRNS) { + lastPixel[chan] = pixBits; + } + } + + haveBits -= pngPtr->bitDepth; + shifts++; + } + + /* + * Apply boolean transparency via tRNS data if necessary (where + * necessary means a tRNS chunk was provided and we're not using an + * alpha channel or indexed alpha). + */ + + if ((PNG_COLOR_PLTE != pngPtr->colorType) && + ((pngPtr->colorType & PNG_COLOR_ALPHA) == 0)) { + 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_SetResult(interp, "Extra data after end of zlib stream", + TCL_STATIC); + return TCL_ERROR; + } + + inputObj = Tcl_NewObj(); + Tcl_SetByteArrayLength(inputObj, PNG_BLOCK_SZ); + Tcl_IncrRefCount(inputObj); + + /* + * Read the next bit of IDAT chunk data, up to read buffer size. + */ + + inputPtr = Tcl_GetByteArrayFromObj(inputObj, NULL); + 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_SetResult(interp, + "Extra data after final scan line of final phase", + TCL_STATIC); + return TCL_ERROR; + } + + if (DecodeLine(interp, pngPtr) == TCL_ERROR) { + return TCL_ERROR; + } + + /* + * Swap the current/last lines so that we always have the last + * line processed available, which is necessary for filtering. + */ + + { + Tcl_Obj *temp = pngPtr->lastLineObj; + + pngPtr->lastLineObj = pngPtr->thisLineObj; + pngPtr->thisLineObj = temp; + } + Tcl_SetByteArrayLength(pngPtr->thisLineObj, 0); + + /* + * Try to read another line of pixels out of the buffer + * immediately. + */ + + goto getNextLine; + } + + /* + * Got less than a whole buffer-load of pixels. Either we're going to + * be getting more data from the next IDAT, or we've done what we can + * here. + */ + } + + if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) { + return TCL_ERROR; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ApplyAlpha -- + * + * Applies an overall alpha value to a complete image that has been read. + * This alpha value is specified using the -format option to [image + * create photo]. + * + * Results: + * N/A + * + * Side effects: + * The access position in f may change. + * + *---------------------------------------------------------------------- + */ + +static void +ApplyAlpha( + PNGImage *pngPtr) +{ + if (pngPtr->alpha != 1.0) { + register unsigned char *p = pngPtr->block.pixelPtr; + unsigned char *endPtr = p + pngPtr->blockLen; + int offset = pngPtr->block.offset[3]; + + p += offset; + + if (16 == pngPtr->bitDepth) { + register int channel; + + while (p < endPtr) { + channel = (unsigned char) + (((p[0] << 8) | p[1]) * pngPtr->alpha); + + *p++ = (unsigned char) (channel >> 8); + *p++ = (unsigned char) (channel & 0xff); + + p += offset; + } + } else { + while (p < endPtr) { + p[0] = (unsigned char) (pngPtr->alpha * p[0]); + p += 1 + offset; + } + } + } +} + +/* + *---------------------------------------------------------------------- + * + * ParseFormat -- + * + * This function parses the -format string that can be specified to the + * [image create photo] command to extract options for postprocessing of + * loaded images. Currently, this just allows specifying and applying an + * overall alpha value to the loaded image (for example, to make it + * entirely 50% as transparent as the actual image file). + * + * Results: + * TCL_OK, or TCL_ERROR if the format specification is invalid. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +ParseFormat( + Tcl_Interp *interp, + Tcl_Obj *fmtObj, + PNGImage *pngPtr) +{ + Tcl_Obj **objv = NULL; + int objc = 0; + static const char *fmtOptions[] = { + "png", "-alpha", NULL + }; + enum fmtOptions { + OPT_PNG, OPT_ALPHA + }; + + /* + * Extract elements of format specification as a list. + */ + + if (fmtObj && + Tcl_ListObjGetElements(interp, fmtObj, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + + for (; objc>0 ; objc--, objv++) { + int optIndex; + + if (Tcl_GetIndexFromObj(interp, objv[0], fmtOptions, "option", 0, + &optIndex) == TCL_ERROR) { + return TCL_ERROR; + } + + /* + * Ignore the "png" part of the format specification. + */ + + if (OPT_PNG == optIndex) { + continue; + } + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "value"); + return TCL_ERROR; + } + + objc--; + objv++; + + switch ((enum fmtOptions) optIndex) { + case OPT_PNG: + break; + + case OPT_ALPHA: + if (Tcl_GetDoubleFromObj(interp, objv[0], + &pngPtr->alpha) == TCL_ERROR) { + return TCL_ERROR; + } + + if ((pngPtr->alpha < 0.0) || (pngPtr->alpha > 1.0)) { + Tcl_SetResult(interp, + "-alpha value must be between 0.0 and 1.0", + TCL_STATIC); + 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_SetResult(interp, "PLTE chunk required for indexed color", + TCL_STATIC); + 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 (CHUNK_IDAT != chunkType) { + Tcl_SetResult(interp, "At least one IDAT chunk is required", + TCL_STATIC); + 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_SetResult(interp, + "Line size is out of supported range on this architecture", + TCL_STATIC); + 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 = (unsigned char *) + attemptckalloc(pngPtr->blockLen); + + if (!pngPtr->block.pixelPtr) { + Tcl_SetResult(interp, "Memory allocation failed", TCL_STATIC); + 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; + } + } + + /* + * 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_SetResult(interp, "IEND chunk contents must be empty", + TCL_STATIC); + 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_SetResult(interp, "Extra data following IEND chunk", TCL_STATIC); + 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; + Tcl_SavedResult sya; + + Tcl_SaveResult(interp, &sya); + + InitPNGImage(interp, &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); + Tcl_RestoreResult(interp, &sya); + + 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; + Tcl_SavedResult sya; + + Tcl_SaveResult(interp, &sya); + InitPNGImage(interp, &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); + Tcl_RestoreResult(interp, &sya); + 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_SetResult(interp, + "Image too large to store completely in byte array", + TCL_STATIC); + return TCL_ERROR; + } + + destPtr = Tcl_SetByteArrayLength(pngPtr->objDataPtr, objSz + srcSz); + + if (!destPtr) { + Tcl_SetResult(interp, "Memory allocation failed", TCL_STATIC); + return TCL_ERROR; + } + + memcpy(destPtr+objSz, srcPtr, srcSz); + } else if (Tcl_Write(pngPtr->channel, (const char *) srcPtr, srcSz) < 0) { + /* TODO: reason */ + + Tcl_SetResult(interp, "Write to channel failed", TCL_STATIC); + 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, flush at the same time. + */ + + if (rowNum + 1 == blockPtr->height) { + flush = TCL_ZLIB_FLUSH; + } + if (Tcl_ZlibStreamPut(pngPtr->stream, pngPtr->thisLineObj, + flush) != TCL_OK) { + Tcl_SetResult(interp, "deflate() returned error", TCL_STATIC); + 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; +} + +/* + *---------------------------------------------------------------------- + * + * EncodePNG -- + * + * This function handles the entirety of writing a PNG file (or data) + * from the first byte to the last. No effort is made to optimize the + * image data for best compression. + * + * Results: + * TCL_OK, or TCL_ERROR if an I/O or memory error occurs. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +EncodePNG( + Tcl_Interp *interp, + Tk_PhotoImageBlock *blockPtr, + PNGImage *pngPtr) +{ + int greenOffset, blueOffset, alphaOffset; + + /* + * Determine appropriate color type based on color usage (e.g., only red + * and maybe alpha channel = grayscale). + * + * TODO: Check whether this is doing any good; Tk might just be pushing + * full RGBA data all the time through here, even though the actual image + * doesn't need it... + */ + + greenOffset = blockPtr->offset[1] - blockPtr->offset[0]; + blueOffset = blockPtr->offset[2] - blockPtr->offset[0]; + alphaOffset = blockPtr->offset[3]; + if ((alphaOffset >= blockPtr->pixelSize) || (alphaOffset < 0)) { + alphaOffset = 0; + } else { + alphaOffset -= blockPtr->offset[0]; + } + + if ((greenOffset != 0) || (blueOffset != 0)) { + if (alphaOffset) { + pngPtr->colorType = PNG_COLOR_RGBA; + pngPtr->bytesPerPixel = 4; + } else { + pngPtr->colorType = PNG_COLOR_RGBA; + pngPtr->bytesPerPixel = 3; + } + } else { + if (alphaOffset) { + pngPtr->colorType = PNG_COLOR_GRAYALPHA; + pngPtr->bytesPerPixel = 2; + } else { + pngPtr->colorType = PNG_COLOR_GRAY; + pngPtr->bytesPerPixel = 1; + } + } + + /* + * Allocate buffers for lines for filtering and compressed data. + */ + + pngPtr->lineSize = 1 + (pngPtr->bytesPerPixel * blockPtr->width); + pngPtr->blockLen = pngPtr->lineSize * blockPtr->height; + + if ((blockPtr->width > (INT_MAX - 1) / (pngPtr->bytesPerPixel)) || + (blockPtr->height > INT_MAX / pngPtr->lineSize)) { + Tcl_SetResult(interp, "Image is too large to encode pixel data", + TCL_STATIC); + 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 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: + */ diff --git a/generic/tkInt.h b/generic/tkInt.h index 214b428..41ba794 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: $Id: tkInt.h,v 1.98 2008/12/10 05:02:51 das Exp $ + * RCS: $Id: tkInt.h,v 1.99 2008/12/28 13:08:39 dkf Exp $ */ #ifndef _TKINT @@ -962,6 +962,7 @@ MODULE_SCOPE const Tk_SmoothMethod tkBezierSmoothMethod; MODULE_SCOPE Tk_ImageType tkBitmapImageType; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtGIF; MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); +MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPPM; MODULE_SCOPE TkMainInfo *tkMainWindowList; MODULE_SCOPE Tk_ImageType tkPhotoImageType; diff --git a/generic/tkWindow.c b/generic/tkWindow.c index 597b329..37b4a43 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkWindow.c,v 1.101 2008/12/10 00:34:51 das Exp $ + * RCS: @(#) $Id: tkWindow.c,v 1.102 2008/12/28 13:08:39 dkf Exp $ */ #include "tkInt.h" @@ -373,6 +373,7 @@ CreateTopLevelWindow( */ Tk_CreatePhotoImageFormat(&tkImgFmtGIF); + Tk_CreatePhotoImageFormat(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); } diff --git a/tests/imgPNG.test b/tests/imgPNG.test new file mode 100644 index 0000000..1d38ce6 --- /dev/null +++ b/tests/imgPNG.test @@ -0,0 +1,72 @@ +# This file is a Tcl script to test out the code in tkImgFmtPNG.c, which reads +# and write PNG-format image files for photo widgets. The files is organized +# in the standard fashion for Tcl tests. +# +# Copyright (c) 1994-1997 Sun Microsystems, Inc. +# Copyright (c) 1998-1999 by Scriptics Corporation. +# Copyright (c) 1998 Willem van Schaik (images only) +# Copyright (c) 2008 Donal K. Fellows +# All rights reserved. +# +# RCS: @(#) $Id: imgPNG.test,v 1.1 2008/12/28 13:08:38 dkf Exp $ + +package require tcltest 2.2 +namespace import ::tcltest::* +eval tcltest::configure $argv +tcltest::loadTestedCommands + +namespace eval png { + variable encoded + # Key names are from the names of the source images, which come from + # http://www.schaik.com/pngsuite/pngsuite.html + array set encoded { + basn0g08 "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAAAAABWESUoAAAABGdBTUEAAYagMeiWXwAAAEFJREFUeJxjZGAkABQIyLMMBQWMDwgp+PcfP2B5MBwUMMoRkGdkonlcDAYFjI/wyv7/z/iH5nExGBQwyuCVZWQEAFDl/nE14thZAAAAAElFTkSuQmCC" + basn2c08 "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAABGdBTUEAAYagMeiWXwAAAEhJREFUeJzt1cEJADAMAkCF7JH9t3ITO0Qr9KH4zuErtA0EO4AKFPgcoO3kfUx4QIECD0qHH8KEBxQo8KB0OCOpQIG7cHejwAGCsfleD0DPSwAAAABJRU5ErkJggg==" + basn3p08 "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAAYagMeiWXwAAAwBQTFRFIkQA9f/td/93y///EQoAOncAIiL//xH/EQAAIiIA/6xVZv9m/2Zm/wH/IhIA3P//zP+ZRET/AFVVIgAAy8v/REQAVf9Vy8sAMxoA/+zc7f//5P/L/9zcRP9EZmb/MwAARCIA7e3/ZmYA/6RE//+q7e0AAMvL/v///f/+//8BM/8zVSoAAQH/iIj/AKqqAQEARAAAiIgA/+TLulsAIv8iZjIA//+Zqqr/VQAAqqoAy2MAEf8R1P+qdzoA/0RE3GsAZgAAAf8BiEIA7P/ca9wA/9y6ADMzAO0A7XMA//+ImUoAEf//dwAA/4MB/7q6/nsA//7/AMsA/5mZIv//iAAA//93AIiI/9z/GjMAAACqM///AJkAmQAAAAABMmYA/7r/RP///6r/AHcAAP7+qgAASpkA//9m/yIiAACZi/8RVf///wEB/4j/AFUAABER///+//3+pP9EZv///2b/ADMA//9V/3d3AACI/0T/ABEAd///AGZm///tAAEA//XtERH///9E/yL//+3tEREAiP//AAB3k/8iANzcMzP//gD+urr/mf//MzMAY8sAuroArP9V///c//8ze/4A7QDtVVX/qv//3Nz/VVUAAABm3NwA3ADcg/8Bd3f//v7////L/1VVd3cA/v4AywDLAAD+AQIAAQAAEiIA//8iAEREm/8z/9SqAABVmZn/mZkAugC6KlUA/8vLtP9m/5sz//+6qgCqQogAU6oA/6qqAADtALq6//8RAP4AAABEAJmZmQCZ/8yZugAAiACIANwA/5MiAADc/v/+qlMAdwB3AgEAywAAAAAz/+3/ALoA/zMz7f/t/8SIvP93AKoAZgBmACIi3AAA/8v/3P/c/4sRAADLAAEBVQBVAIgAAAAiAf//y//L7QAA/4iIRABEW7oA/7x3/5n/AGYAuv+6AHd3c+0A/gAAMwAzAAC6/3f/AEQAqv+q//7+AAARIgAixP+IAO3tmf+Z/1X/ACIA/7RmEQARChEA/xER3P+6uv//iP+IAQAB/zP/uY7TYgAAAbFJREFUeJwNwQcACAQQAMBHqIxIZCs7Mwlla1hlZ+8VitCw9yoqNGiYDatsyt6jjIadlVkysve+u5jC9xTmV/qyl6bcJR7kAQZzg568xXmuE2lIyUNM5So7OMAFIhvp+YgGvEtFNnOKeJonSEvwP9NZzhHiOfLzBXPoxKP8yD6iPMXITjP+oTdfsp14lTJMJjGtOMFQfiFe4wWK8BP7qUd31hBNqMos2tKYFbRnJdGGjTzPz2yjEA1ZSKymKCM5ylaWcJrZxCZK8jgfU4vc/MW3xE7K8RUvsZb3Wc/XxCEqk4v/qMQlFvMZcZIafMOnLKM13zGceJNqPMU4KnCQAqQgbrKHpXSgFK/Qn6REO9YxjWE8Sx2SMJD4jfl8wgzy0YgPuEeUJQcD6EoWWpCaHsQkHuY9RpGON/icK0RyrvE680jG22TlHaIbx6jLnySkF+M5QxzmD6pwkTsMoSAdidqsojipuMyHzOQ4sYgfyElpzjKGErQkqvMyC7jFv9xmBM2JuTzDRDLxN4l4jF1EZjIwmhfZzSOMpT4xiH70IQG/k5En2UKcowudycsG8jCBmtwHgRv+EIeWyOAAAAAASUVORK5CYII=" + basn6a08 "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAYagMeiWXwAAAG9JREFUeJzt1jEKgDAMRuEnZGhPofc/VQSPIcTdxUV4HVLoUCj8H00o2YoBMF57fpz/ujODHXUFRwPKBqj5DVigB041HiJ9gFyCVOMbsEIPXNwuAHkgiJL/4qABNqB7QAeUPBAE2QAZUDZAfwEb8ABSIBqcFg+4TAAAAABJRU5ErkJggg==" + } + +# $encoded(basn0g08), $encoded(basn2c08), $encoded(basn3p08), $encoded(basn6a08) +test png-1.1 {reading basic images; grayscale} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $encoded(basn0g08) + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {32 32} +test png-1.2 {reading basic images; color} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $encoded(basn2c08) + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {32 32} +test png-1.3 {reading basic images; color with palette} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $encoded(basn3p08) + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {32 32} +test png-1.4 {reading basic images; alpha} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $encoded(basn6a08) + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {32 32} + +} +namespace delete png +image delete {*}[image names] +cleanupTests +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# End: diff --git a/unix/Makefile.in b/unix/Makefile.in index 8388610..5bc01ac 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -5,7 +5,7 @@ # "autoconf" program (constructs like "@foo@" will get replaced in the # actual Makefile. # -# RCS: @(#) $Id: Makefile.in,v 1.147 2008/12/05 10:48:52 dkf Exp $ +# RCS: @(#) $Id: Makefile.in,v 1.148 2008/12/28 13:08:38 dkf Exp $ # Current Tk version; used in various names. @@ -348,8 +348,8 @@ CANV_OBJS = tkCanvas.o tkCanvArc.o tkCanvBmap.o tkCanvImg.o \ tkCanvLine.o tkCanvPoly.o tkCanvPs.o tkCanvText.o \ tkCanvUtil.o tkCanvWind.o tkRectOval.o tkTrig.o -IMAGE_OBJS = tkImage.o tkImgBmap.o tkImgGIF.o tkImgPPM.o tkImgPhoto.o \ - tkImgPhInstance.o +IMAGE_OBJS = tkImage.o tkImgBmap.o tkImgGIF.o tkImgPNG.o tkImgPPM.o \ + tkImgPhoto.o tkImgPhInstance.o TEXT_OBJS = tkText.o tkTextBTree.o tkTextDisp.o tkTextImage.o tkTextIndex.o \ tkTextMark.o tkTextTag.o tkTextWind.o @@ -441,7 +441,7 @@ GENERIC_SRCS = \ $(GENERIC_DIR)/tkCanvWind.c $(GENERIC_DIR)/tkRectOval.c \ $(GENERIC_DIR)/tkTrig.c $(GENERIC_DIR)/tkImage.c \ $(GENERIC_DIR)/tkImgBmap.c $(GENERIC_DIR)/tkImgGIF.c \ - $(GENERIC_DIR)/tkImgPPM.c \ + $(GENERIC_DIR)/tkImgPNG.c $(GENERIC_DIR)/tkImgPPM.c \ $(GENERIC_DIR)/tkImgPhoto.c $(GENERIC_DIR)/tkImgPhInstance.c \ $(GENERIC_DIR)/tkText.c \ $(GENERIC_DIR)/tkTextBTree.c $(GENERIC_DIR)/tkTextDisp.c \ @@ -1086,6 +1086,9 @@ tkImgBmap.o: $(GENERIC_DIR)/tkImgBmap.c tkImgGIF.o: $(GENERIC_DIR)/tkImgGIF.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkImgGIF.c +tkImgPNG.o: $(GENERIC_DIR)/tkImgPNG.c + $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkImgPNG.c + tkImgPPM.o: $(GENERIC_DIR)/tkImgPPM.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkImgPPM.c diff --git a/win/Makefile.in b/win/Makefile.in index 5ad13eb..94a5b28 100644 --- a/win/Makefile.in +++ b/win/Makefile.in @@ -4,7 +4,7 @@ # "autoconf" program (constructs like "@foo@" will get replaced in the # actual Makefile. # -# RCS: @(#) $Id: Makefile.in,v 1.82 2008/12/05 10:48:52 dkf Exp $ +# RCS: @(#) $Id: Makefile.in,v 1.83 2008/12/28 13:08:39 dkf Exp $ TCLVERSION = @TCL_VERSION@ TCLPATCHL = @TCL_PATCH_LEVEL@ @@ -314,6 +314,7 @@ TK_OBJS = \ tkImage.$(OBJEXT) \ tkImgBmap.$(OBJEXT) \ tkImgGIF.$(OBJEXT) \ + tkImgPNG.$(OBJEXT) \ tkImgPPM.$(OBJEXT) \ tkImgPhoto.$(OBJEXT) \ tkImgPhInstance.$(OBJEXT) \ diff --git a/win/makefile.vc b/win/makefile.vc index 0d3d374..9cf8bdb 100644 --- a/win/makefile.vc +++ b/win/makefile.vc @@ -13,7 +13,7 @@ # Copyright (c) 2003-2008 Pat Thoyts. # #------------------------------------------------------------------------------ -# RCS: @(#) $Id: makefile.vc,v 1.126 2008/10/19 21:22:36 patthoyts Exp $ +# RCS: @(#) $Id: makefile.vc,v 1.127 2008/12/28 13:08:39 dkf Exp $ #------------------------------------------------------------------------------ # Check to see we are configured to build with MSVC (MSDEVDIR or MSVCDIR) @@ -322,6 +322,7 @@ TKOBJS = \ $(TMP_DIR)\tkImage.obj \ $(TMP_DIR)\tkImgBmap.obj \ $(TMP_DIR)\tkImgGIF.obj \ + $(TMP_DIR)\tkImgPNG.obj \ $(TMP_DIR)\tkImgPPM.obj \ $(TMP_DIR)\tkImgPhoto.obj \ $(TMP_DIR)\tkImgPhInstance.obj \ |