1/*
2 * png.c --
3 *
4 *  PNG photo image type, Tcl/Tk package
5 *
6 * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net>
7 *
8 * This Tk image format handler reads and writes PNG files in the standard
9 * JFIF file format.  ("PNG" should be the format name.)  It can also read
10 * and write strings containing base64-encoded PNG data.
11 *
12 * Author : Jan Nijtmans *
13 * Date   : 2/13/97        *
14 * Original implementation : Joel Crisp     *
15 *
16 * $Id: png.c 271 2010-06-17 13:40:24Z nijtmans $
17 */
18
19/*
20 * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
21 */
22
23#include "pngtcl.h"
24#include <string.h>
25#include <stdlib.h>
26
27static int SetupPngLibrary(Tcl_Interp *interp);
28
29#define MORE_INITIALIZATION \
30    if (SetupPngLibrary (interp) != TCL_OK) { return TCL_ERROR; }
31
32#include "init.c"
33
34
35
36#define COMPRESS_THRESHOLD 1024
37
38typedef struct png_text_struct_compat
39{
40   png_text compat;
41   png_size_t itxt_length; /* length of the itxt string */
42   png_charp lang;         /* language code, 0-79 characters
43                              or a NULL pointer */
44   png_charp lang_key;     /* keyword translated UTF-8 string, 0 or more
45                              chars or a NULL pointer */
46} png_text_compat;
47
48typedef struct cleanup_info {
49    Tcl_Interp *interp;
50    jmp_buf jmpbuf;
51} cleanup_info;
52
53/*
54 * Prototypes for local procedures defined in this file:
55 */
56
57static int CommonMatchPNG(tkimg_MFile *handle, int *widthPtr,
58	int *heightPtr);
59
60static int CommonReadPNG(png_structp png_ptr,
61        Tcl_Interp* interp, Tcl_Obj *format,
62	Tk_PhotoHandle imageHandle, int destX, int destY, int width,
63	int height, int srcX, int srcY);
64
65static int CommonWritePNG(Tcl_Interp *interp, png_structp png_ptr,
66	png_infop info_ptr, Tcl_Obj *format,
67	Tk_PhotoImageBlock *blockPtr);
68
69static void tk_png_error(png_structp, png_const_charp);
70
71static void tk_png_warning(png_structp, png_const_charp);
72
73/*
74 * These functions are used for all Input/Output.
75 */
76
77static void	tk_png_read(png_structp, png_bytep,
78	png_size_t);
79
80static void	tk_png_write(png_structp, png_bytep,
81	png_size_t);
82
83static void	tk_png_flush(png_structp);
84
85/*
86 *
87 */
88
89static int
90SetupPngLibrary (interp)
91    Tcl_Interp *interp;
92{
93    if (Pngtcl_InitStubs(interp, PNGTCL_VERSION, 0) == NULL) {
94        return TCL_ERROR;
95    }
96    return TCL_OK;
97}
98
99static void
100tk_png_error(png_ptr, error_msg)
101    png_structp png_ptr;
102    png_const_charp error_msg;
103{
104    cleanup_info *info = (cleanup_info *) png_get_error_ptr(png_ptr);
105    Tcl_AppendResult(info->interp, error_msg, (char *) NULL);
106    longjmp(info->jmpbuf,1);
107}
108
109static void
110tk_png_warning(png_ptr, error_msg)
111    png_structp png_ptr;
112    png_const_charp error_msg;
113{
114    return;
115}
116
117static void
118tk_png_read(png_ptr, data, length)
119    png_structp png_ptr;
120    png_bytep data;
121    png_size_t length;
122{
123    if (tkimg_Read((tkimg_MFile *) png_get_progressive_ptr(png_ptr),
124	    (char *) data, (size_t) length) != (int) length) {
125	png_error(png_ptr, "Read Error");
126    }
127}
128
129static void
130tk_png_write(png_ptr, data, length)
131    png_structp png_ptr;
132    png_bytep data;
133    png_size_t length;
134{
135    if (tkimg_Write((tkimg_MFile *) png_get_progressive_ptr(png_ptr),
136	    (char *) data, (size_t) length) != (int) length) {
137	png_error(png_ptr, "Write Error");
138    }
139}
140
141static void
142tk_png_flush(png_ptr)
143    png_structp png_ptr;
144{
145}
146
147static int ChnMatch(
148    Tcl_Channel chan,
149    const char *fileName,
150    Tcl_Obj *format,
151    int *widthPtr,
152    int *heightPtr,
153    Tcl_Interp *interp
154) {
155    tkimg_MFile handle;
156
157    handle.data = (char *) chan;
158    handle.state = IMG_CHAN;
159
160    return CommonMatchPNG(&handle, widthPtr, heightPtr);
161}
162
163static int
164ObjMatch(
165    Tcl_Obj *data,
166    Tcl_Obj *format,
167    int *widthPtr,
168    int *heightPtr,
169    Tcl_Interp *interp
170) {
171    tkimg_MFile handle;
172
173    if (!tkimg_ReadInit(data, '\211', &handle)) {
174	return 0;
175    }
176    return CommonMatchPNG(&handle, widthPtr, heightPtr);
177}
178
179static int
180CommonMatchPNG(handle, widthPtr, heightPtr)
181    tkimg_MFile *handle;
182    int *widthPtr, *heightPtr;
183{
184    unsigned char buf[8];
185
186    if ((tkimg_Read(handle, (char *) buf, 8) != 8)
187	    || (strncmp("\211\120\116\107\15\12\32\12", (char *) buf, 8) != 0)
188	    || (tkimg_Read(handle, (char *) buf, 8) != 8)
189	    || (strncmp("\111\110\104\122", (char *) buf+4, 4) != 0)
190	    || (tkimg_Read(handle, (char *) buf, 8) != 8)) {
191	return 0;
192    }
193    *widthPtr = (buf[0]<<24) + (buf[1]<<16) + (buf[2]<<8) + buf[3];
194    *heightPtr = (buf[4]<<24) + (buf[5]<<16) + (buf[6]<<8) + buf[7];
195    return 1;
196}
197
198static int
199ChnRead(interp, chan, fileName, format, imageHandle,
200	destX, destY, width, height, srcX, srcY)
201    Tcl_Interp *interp;
202    Tcl_Channel chan;
203    const char *fileName;
204    Tcl_Obj *format;
205    Tk_PhotoHandle imageHandle;
206    int destX, destY;
207    int width, height;
208    int srcX, srcY;
209{
210    png_structp png_ptr;
211    tkimg_MFile handle;
212    cleanup_info cleanup;
213
214    handle.data = (char *) chan;
215    handle.state = IMG_CHAN;
216
217    cleanup.interp = interp;
218
219    png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,
220	    (png_voidp) &cleanup,tk_png_error,tk_png_warning);
221    if (!png_ptr) return(0);
222
223    png_set_read_fn(png_ptr, (png_voidp) &handle, tk_png_read);
224
225    return CommonReadPNG(png_ptr, interp, format, imageHandle, destX, destY,
226	    width, height, srcX, srcY);
227}
228
229static int
230ObjRead (interp, dataObj, format, imageHandle,
231	destX, destY, width, height, srcX, srcY)
232    Tcl_Interp *interp;
233    Tcl_Obj *dataObj;
234    Tcl_Obj *format;
235    Tk_PhotoHandle imageHandle;
236    int destX, destY;
237    int width, height;
238    int srcX, srcY;
239{
240    png_structp png_ptr;
241    tkimg_MFile handle;
242    cleanup_info cleanup;
243
244    cleanup.interp = interp;
245
246    png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,
247	    (png_voidp) &cleanup,tk_png_error,tk_png_warning);
248    if (!png_ptr) return TCL_ERROR;
249
250    tkimg_ReadInit(dataObj,'\211',&handle);
251
252    png_set_read_fn(png_ptr,(png_voidp) &handle, tk_png_read);
253
254    return CommonReadPNG(png_ptr, interp, format, imageHandle, destX, destY,
255	    width, height, srcX, srcY);
256}
257
258static int
259CommonReadPNG(png_ptr, interp, format, imageHandle, destX, destY,
260	width, height, srcX, srcY)
261    png_structp png_ptr;
262    Tcl_Interp *interp;
263    Tcl_Obj *format;
264    Tk_PhotoHandle imageHandle;
265    int destX, destY;
266    int width, height;
267    int srcX, srcY;
268{
269    png_infop info_ptr;
270    png_infop end_info;
271    char **png_data = NULL;
272    Tk_PhotoImageBlock block;
273    unsigned int I;
274    png_uint_32 info_width, info_height;
275    int bit_depth, color_type, interlace_type;
276    int intent;
277    int result = TCL_OK;
278
279    info_ptr=png_create_info_struct(png_ptr);
280    if (!info_ptr) {
281	png_destroy_read_struct(&png_ptr,NULL,NULL);
282	return(TCL_ERROR);
283    }
284
285    end_info=png_create_info_struct(png_ptr);
286    if (!end_info) {
287	png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
288	return(TCL_ERROR);
289    }
290
291    if (setjmp((((cleanup_info *) png_get_error_ptr(png_ptr))->jmpbuf))) {
292	if (png_data) {
293	    ckfree((char *)png_data);
294	}
295	png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
296	return TCL_ERROR;
297    }
298
299    png_read_info(png_ptr,info_ptr);
300
301    png_get_IHDR(png_ptr, info_ptr, &info_width, &info_height, &bit_depth,
302	&color_type, &interlace_type, (int *) NULL, (int *) NULL);
303
304    if ((srcX + width) > (int) info_width) {
305	width = info_width - srcX;
306    }
307    if ((srcY + height) > (int) info_height) {
308	height = info_height - srcY;
309    }
310    if ((width <= 0) || (height <= 0)
311	|| (srcX >= (int) info_width)
312	|| (srcY >= (int) info_height)) {
313	png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
314	return TCL_OK;
315    }
316
317    if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) {
318	png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
319	return TCL_ERROR;
320    }
321
322    Tk_PhotoGetImage(imageHandle, &block);
323
324    if (png_set_strip_16 != NULL) {
325	png_set_strip_16(png_ptr);
326    } else if (bit_depth == 16) {
327	block.offset[1] = 2;
328	block.offset[2] = 4;
329    }
330
331    if (png_set_expand != NULL) {
332	png_set_expand(png_ptr);
333    }
334
335    png_read_update_info(png_ptr,info_ptr);
336    block.pixelSize = png_get_channels(png_ptr, info_ptr);
337    block.pitch = png_get_rowbytes(png_ptr, info_ptr);
338
339    if ((color_type & PNG_COLOR_MASK_COLOR) == 0) {
340	/* grayscale image */
341	block.offset[1] = 0;
342	block.offset[2] = 0;
343    }
344    block.width = width;
345    block.height = height;
346
347    if ((color_type & PNG_COLOR_MASK_ALPHA)
348	    || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
349	/* with alpha channel */
350	block.offset[3] = block.pixelSize - 1;
351    } else {
352	/* without alpha channel */
353	block.offset[3] = 0;
354    }
355
356    if (png_get_sRGB && png_get_sRGB(png_ptr, info_ptr, &intent)) {
357	png_set_sRGB(png_ptr, info_ptr, intent);
358    } else if (png_get_gAMA) {
359	double gamma;
360	if (!png_get_gAMA(png_ptr, info_ptr, &gamma)) {
361	    gamma = 0.45455;
362	}
363	png_set_gamma(png_ptr, 1.0, gamma);
364    }
365
366    png_data= (char **) ckalloc(sizeof(char *) * info_height +
367	    info_height * block.pitch);
368
369    for(I=0;I<info_height;I++) {
370	png_data[I]= ((char *) png_data) + (sizeof(char *) * info_height +
371		I * block.pitch);
372    }
373    block.pixelPtr=(unsigned char *) (png_data[srcY]+srcX*block.pixelSize);
374
375    png_read_image(png_ptr,(png_bytepp) png_data);
376
377    if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, destY, width, height,
378	    block.offset[3]? TK_PHOTO_COMPOSITE_OVERLAY: TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
379	result = TCL_ERROR;
380    }
381
382    ckfree((char *) png_data);
383    png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
384
385    return result;
386}
387
388static int
389ChnWrite (interp, filename, format, blockPtr)
390    Tcl_Interp *interp;
391    const char *filename;
392    Tcl_Obj *format;
393    Tk_PhotoImageBlock *blockPtr;
394{
395    png_structp png_ptr;
396    png_infop info_ptr;
397    tkimg_MFile handle;
398    int result;
399    cleanup_info cleanup;
400    Tcl_Channel chan = (Tcl_Channel) NULL;
401
402    chan = tkimg_OpenFileChannel(interp, filename, 0644);
403    if (!chan) {
404	return TCL_ERROR;
405    }
406
407    handle.data = (char *) chan;
408    handle.state = IMG_CHAN;
409
410    cleanup.interp = interp;
411
412    png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
413	    (png_voidp) &cleanup,tk_png_error,tk_png_warning);
414    if (!png_ptr) {
415	Tcl_Close(NULL, chan);
416	return TCL_ERROR;
417    }
418
419    info_ptr=png_create_info_struct(png_ptr);
420    if (!info_ptr) {
421	png_destroy_write_struct(&png_ptr,NULL);
422	Tcl_Close(NULL, chan);
423	return TCL_ERROR;
424    }
425
426    png_set_write_fn(png_ptr,(png_voidp) &handle, tk_png_write, tk_png_flush);
427
428    result = CommonWritePNG(interp, png_ptr, info_ptr, format, blockPtr);
429    Tcl_Close(NULL, chan);
430    return result;
431}
432
433static int StringWrite(
434    Tcl_Interp *interp,
435    Tcl_Obj *format,
436    Tk_PhotoImageBlock *blockPtr
437) {
438    png_structp png_ptr;
439    png_infop info_ptr;
440    tkimg_MFile handle;
441    int result;
442    cleanup_info cleanup;
443    Tcl_DString data;
444
445    Tcl_DStringInit(&data);
446    cleanup.interp = interp;
447
448    png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
449	    (png_voidp) &cleanup, tk_png_error, tk_png_warning);
450    if (!png_ptr) {
451	return TCL_ERROR;
452    }
453
454    info_ptr = png_create_info_struct(png_ptr);
455    if (!info_ptr) {
456	png_destroy_write_struct(&png_ptr,NULL);
457	return TCL_ERROR;
458    }
459
460    png_set_write_fn(png_ptr, (png_voidp) &handle, tk_png_write, tk_png_flush);
461
462    tkimg_WriteInit(&data, &handle);
463
464    result = CommonWritePNG(interp, png_ptr, info_ptr, format, blockPtr);
465    tkimg_Putc(IMG_DONE, &handle);
466    if (result == TCL_OK) {
467	Tcl_DStringResult(interp, &data);
468    } else {
469	Tcl_DStringFree(&data);
470    }
471    return result;
472}
473
474static int
475CommonWritePNG(interp, png_ptr, info_ptr, format, blockPtr)
476    Tcl_Interp *interp;
477    png_structp png_ptr;
478    png_infop info_ptr;
479    Tcl_Obj *format;
480    Tk_PhotoImageBlock *blockPtr;
481{
482    int greenOffset, blueOffset, alphaOffset;
483    int tagcount = 0;
484    Tcl_Obj **tags = (Tcl_Obj **) NULL;
485    int I, pass, number_passes, color_type;
486    int newPixelSize;
487    png_bytep row_pointers = (png_bytep) NULL;
488
489    if (tkimg_ListObjGetElements(interp, format, &tagcount, &tags) != TCL_OK) {
490	return TCL_ERROR;
491    }
492    tagcount = (tagcount > 1) ? (tagcount - 1) / 2: 0;
493
494    if (setjmp((((cleanup_info *) png_get_error_ptr(png_ptr))->jmpbuf))) {
495	if (row_pointers) {
496	    ckfree((char *) row_pointers);
497	}
498	png_destroy_write_struct(&png_ptr,&info_ptr);
499	return TCL_ERROR;
500    }
501    greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
502    blueOffset = blockPtr->offset[2] - blockPtr->offset[0];
503    alphaOffset = blockPtr->offset[0];
504    if (alphaOffset < blockPtr->offset[2]) {
505	alphaOffset = blockPtr->offset[2];
506    }
507    if (++alphaOffset < blockPtr->pixelSize) {
508	alphaOffset -= blockPtr->offset[0];
509    } else {
510	alphaOffset = 0;
511    }
512
513    if (greenOffset || blueOffset) {
514	color_type = PNG_COLOR_TYPE_RGB;
515	newPixelSize = 3;
516    } else {
517	color_type = PNG_COLOR_TYPE_GRAY;
518	newPixelSize = 1;
519    }
520    if (alphaOffset) {
521	color_type |= PNG_COLOR_MASK_ALPHA;
522	newPixelSize++;
523#if 0 /* The function png_set_filler doesn't seem to work; don't known why :-( */
524    } else if ((blockPtr->pixelSize==4) && (newPixelSize == 3)
525	    && (png_set_filler != NULL)) {
526	/*
527	 * The set_filler() function doesn't need to be called
528	 * because the code below can handle all necessary
529	 * re-allocation of memory. Only it is more economically
530	 * to let the PNG library do that, which is only
531	 * possible with v0.95 and higher.
532	 */
533	png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
534	newPixelSize++;
535#endif
536    }
537
538    png_set_IHDR(png_ptr, info_ptr, blockPtr->width, blockPtr->height, 8,
539	    color_type, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_BASE,
540	    PNG_FILTER_TYPE_BASE);
541
542    if (png_set_gAMA) {
543	png_set_gAMA(png_ptr, info_ptr, 1.0);
544    }
545
546    if (tagcount > 0) {
547	png_text_compat text;
548	for(I=0;I<tagcount;I++) {
549	    int length;
550	    memset(&text, 0, sizeof(png_text_compat));
551	    text.compat.key = Tcl_GetStringFromObj(tags[2*I+1], (int *) NULL);
552	    text.compat.text = Tcl_GetStringFromObj(tags[2*I+2], &length);
553	    text.compat.text_length = length;
554	    if (text.compat.text_length>COMPRESS_THRESHOLD) {
555		text.compat.compression = PNG_TEXT_COMPRESSION_zTXt;
556	    } else {
557		text.compat.compression = PNG_TEXT_COMPRESSION_NONE;
558	    }
559	    png_set_text(png_ptr, info_ptr, &text.compat, 1);
560        }
561    }
562    png_write_info(png_ptr,info_ptr);
563
564    number_passes = png_set_interlace_handling(png_ptr);
565
566    if (blockPtr->pixelSize != newPixelSize) {
567	int J, oldPixelSize;
568	png_bytep src, dst;
569	oldPixelSize = blockPtr->pixelSize;
570	row_pointers = (png_bytep)
571		ckalloc(blockPtr->width * newPixelSize);
572	for (pass = 0; pass < number_passes; pass++) {
573	    for(I=0; I<blockPtr->height; I++) {
574		src = (png_bytep) blockPtr->pixelPtr
575			+ I * blockPtr->pitch + blockPtr->offset[0];
576		dst = row_pointers;
577		for (J = blockPtr->width; J > 0; J--) {
578		    memcpy(dst, src, newPixelSize);
579		    src += oldPixelSize;
580		    dst += newPixelSize;
581		}
582		png_write_row(png_ptr, row_pointers);
583	    }
584	}
585	ckfree((char *) row_pointers);
586	row_pointers = NULL;
587    } else {
588	for (pass = 0; pass < number_passes; pass++) {
589	    for(I=0;I<blockPtr->height;I++) {
590		png_write_row(png_ptr, (png_bytep) blockPtr->pixelPtr
591			+ I * blockPtr->pitch + blockPtr->offset[0]);
592	    }
593	}
594    }
595    png_write_end(png_ptr,NULL);
596    png_destroy_write_struct(&png_ptr,&info_ptr);
597
598    return(TCL_OK);
599}
600