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  * PNG 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  * The following format options are available:
17  *
18  * Read  PNG image: "png -verbose <bool> -gamma <float> -matte <bool> -alpha <float>"
19  * Write PNG image: None yet.
20  *
21  * -verbose <bool>: If set to true, additional information about the file
22  *                  format is printed to stdout. Default is "false".
23  * -gamma <float>:  Use the specified gamma value when reading an image.
24  *                  This option overwrites gamma values specified in the file.
25  *                  If this option is not specified and no gamma value is in the file,
26  *                  a default value of 1.0 is used.
27  * -matte <bool>:   If set to false, a matte (alpha) channel is ignored
28  *                  during reading. Default is true.
29  * -alpha <float>:  An additional alpha filtering for the overall image, which
30  *                  allows the background on which the image is displayed to show through.
31  *                  This usually also has the effect of desaturating the image.
32  *                  The alphaValue must be between 0.0 and 1.0.
33  *                  Specifying an alpha value, overrides the setting of the matte flag,
34  *                  i.e. reading a file which has no alpha channel (Greyscale, RGB) will
35  *                  add an alpha channel to the image independent of the matte flag setting.
36  */
37 
38 /*
39  * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
40  */
41 
42 #include <png.h>
43 #include <string.h>
44 #include <stdlib.h>
45 #include <tcl.h>
46 
47 static int SetupPngLibrary(Tcl_Interp *interp);
48 
49 #define MORE_INITIALIZATION \
50     if (SetupPngLibrary (interp) != TCL_OK) { return TCL_ERROR; }
51 
52 #include "init.c"
53 
54 
55 #define COMPRESS_THRESHOLD 1024
56 
57 typedef struct png_text_struct_compat
58 {
59    png_text compat;
60    png_size_t itxt_length; /* length of the itxt string */
61    png_charp lang;         /* language code, 0-79 characters
62                               or a NULL pointer */
63    png_charp lang_key;     /* keyword translated UTF-8 string, 0 or more
64                               chars or a NULL pointer */
65 } png_text_compat;
66 
67 typedef struct cleanup_info {
68     Tcl_Interp *interp;
69     jmp_buf jmpbuf;
70 } cleanup_info;
71 
72 typedef struct {
73     int  verbose;
74     int  matte;
75     float alpha;
76     float gamma;
77 } FMTOPT;
78 
79 /*
80  * Prototypes for local procedures defined in this file:
81  */
82 
83 static int CommonMatchPNG(tkimg_MFile *handle, int *widthPtr,
84         int *heightPtr);
85 
86 static int CommonReadPNG(png_structp png_ptr,
87         Tcl_Interp* interp, const char *fileName, Tcl_Obj *format,
88         Tk_PhotoHandle imageHandle, int destX, int destY, int width,
89         int height, int srcX, int srcY);
90 
91 static int CommonWritePNG(Tcl_Interp *interp, png_structp png_ptr,
92         png_infop info_ptr, Tcl_Obj *format,
93         Tk_PhotoImageBlock *blockPtr);
94 
95 static void tk_png_error(png_structp, png_const_charp);
96 
97 static void tk_png_warning(png_structp, png_const_charp);
98 
99 /*
100  * These functions are used for all Input/Output.
101  */
102 
103 static void tk_png_read(png_structp, png_bytep, png_size_t);
104 
105 static void tk_png_write(png_structp, png_bytep, png_size_t);
106 
107 static void tk_png_flush(png_structp);
108 
109 #define OUT Tcl_WriteChars (outChan, str, -1)
PrintReadInfo(int width,int height,int nchans,int bits,double fileGamma,const char * filename,const char * msg)110 static void PrintReadInfo (int width, int height, int nchans, int bits,
111                            double fileGamma, const char *filename, const char *msg)
112 {
113     Tcl_Channel outChan;
114     char str[256];
115 
116     outChan = Tcl_GetStdChannel (TCL_STDOUT);
117     if (!outChan) {
118         return;
119     }
120     sprintf(str, "%s %s\n", msg, filename);                        OUT;
121     sprintf(str, "\tSize in pixel   : %d x %d\n", width, height);  OUT;
122     sprintf(str, "\tNum channels    : %d\n", nchans);              OUT;
123     sprintf(str, "\tBits per channel: %d\n", bits);                OUT;
124     if (fileGamma < 0.0) {
125         sprintf(str, "\tFile gamma      : %s\n", "None");          OUT;
126     } else {
127         sprintf(str, "\tFile gamma      : %f\n", fileGamma);       OUT;
128     }
129     Tcl_Flush(outChan);
130 }
131 #undef OUT
132 
ParseFormatOpts(Tcl_Interp * interp,Tcl_Obj * format,FMTOPT * opts)133 static int ParseFormatOpts(
134     Tcl_Interp *interp,
135     Tcl_Obj *format,
136     FMTOPT *opts
137 ) {
138     static const char *const pngOptions[] = {
139         "-matte", "-alpha", "-gamma", "-verbose", NULL
140     };
141     int objc, i, index;
142     char *optionStr;
143     Tcl_Obj **objv;
144     int boolVal;
145     double doubleVal;
146 
147     opts->matte   = 1;
148     opts->alpha   = -1.0;
149     opts->gamma   = 1.0;
150     opts->verbose = 0;
151 
152     if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK) {
153         return TCL_ERROR;
154     }
155     if (objc) {
156         for (i=1; i<objc; i++) {
157             if (Tcl_GetIndexFromObj(interp, objv[i], (const char *CONST86 *)pngOptions,
158                     "format option", 0, &index) != TCL_OK) {
159                 return TCL_ERROR;
160             }
161             if (++i >= objc) {
162                 Tcl_AppendResult(interp, "No value for option \"",
163                         Tcl_GetStringFromObj (objv[--i], (int *) NULL),
164                         "\"", (char *) NULL);
165                 return TCL_ERROR;
166             }
167             optionStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
168             switch(index) {
169                 case 0:
170                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
171                         Tcl_AppendResult (interp, "Invalid matte mode \"", optionStr,
172                                           "\": should be 1 or 0, on or off, true or false",
173                                           (char *) NULL);
174                         return TCL_ERROR;
175                     }
176                     opts->matte = boolVal;
177                     break;
178                 case 1:
179                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
180                         Tcl_AppendResult (interp, "Invalid alpha value \"", optionStr,
181                                           "\": Must be greater than or equal to zero.", (char *) NULL);
182                         return TCL_ERROR;
183                     }
184                     opts->alpha = doubleVal;
185                     if (opts->alpha < 0.0 ) opts->alpha = 0.0;
186                     if (opts->alpha > 1.0 ) opts->alpha = 1.0;
187                     break;
188                 case 2:
189                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
190                         Tcl_AppendResult (interp, "Invalid gamma value \"", optionStr,
191                                           "\": Must be greater than or equal to zero.", (char *) NULL);
192                         return TCL_ERROR;
193                     }
194                     if (doubleVal >= 0.0) {
195                         opts->gamma = doubleVal;
196                     }
197                     break;
198                 case 3:
199                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
200                         Tcl_AppendResult (interp, "Invalid verbose mode \"", optionStr,
201                                           "\": should be 1 or 0, on or off, true or false",
202                                           (char *) NULL);
203                         return TCL_ERROR;
204                     }
205                     opts->verbose = boolVal;
206                     break;
207             }
208         }
209     }
210     return TCL_OK;
211 }
212 
213 /*
214  *
215  */
216 
217 static int
SetupPngLibrary(Tcl_Interp * interp)218 SetupPngLibrary(
219     Tcl_Interp *interp
220 ) {
221     return TCL_OK;
222 }
223 
224 static void
tk_png_error(png_structp png_ptr,png_const_charp error_msg)225 tk_png_error(
226     png_structp png_ptr,
227     png_const_charp error_msg
228 ) {
229     cleanup_info *info = (cleanup_info *) png_get_error_ptr(png_ptr);
230     Tcl_AppendResult(info->interp, error_msg, (char *) NULL);
231     longjmp(info->jmpbuf,1);
232 }
233 
234 static void
tk_png_warning(png_structp png_ptr,png_const_charp error_msg)235 tk_png_warning(
236     png_structp png_ptr,
237     png_const_charp error_msg
238 ) {
239     return;
240 }
241 
242 static void
tk_png_read(png_structp png_ptr,png_bytep data,png_size_t length)243 tk_png_read(
244     png_structp png_ptr,
245     png_bytep data,
246     png_size_t length
247 ) {
248     if (tkimg_Read2((tkimg_MFile *) png_get_progressive_ptr(png_ptr),
249             (char *) data, (size_t) length) != (int) length) {
250         png_error(png_ptr, "Read Error");
251     }
252 }
253 
254 static void
tk_png_write(png_structp png_ptr,png_bytep data,png_size_t length)255 tk_png_write(
256     png_structp png_ptr,
257     png_bytep data,
258     png_size_t length
259 ) {
260     if (tkimg_Write2((tkimg_MFile *) png_get_progressive_ptr(png_ptr),
261             (char *) data, (size_t) length) != (int) length) {
262         png_error(png_ptr, "Write Error");
263     }
264 }
265 
266 static void
tk_png_flush(png_structp png_ptr)267 tk_png_flush(
268     png_structp png_ptr
269 ) {
270 }
271 
ChnMatch(Tcl_Channel chan,const char * fileName,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)272 static int ChnMatch(
273     Tcl_Channel chan,
274     const char *fileName,
275     Tcl_Obj *format,
276     int *widthPtr,
277     int *heightPtr,
278     Tcl_Interp *interp
279 ) {
280     tkimg_MFile handle;
281 
282     handle.data = (char *) chan;
283     handle.state = IMG_CHAN;
284 
285     return CommonMatchPNG(&handle, widthPtr, heightPtr);
286 }
287 
288 static int
ObjMatch(Tcl_Obj * data,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)289 ObjMatch(
290     Tcl_Obj *data,
291     Tcl_Obj *format,
292     int *widthPtr,
293     int *heightPtr,
294     Tcl_Interp *interp
295 ) {
296     tkimg_MFile handle;
297 
298     if (!tkimg_ReadInit(data, '\211', &handle)) {
299         return 0;
300     }
301     return CommonMatchPNG(&handle, widthPtr, heightPtr);
302 }
303 
304 static int
CommonMatchPNG(tkimg_MFile * handle,int * widthPtr,int * heightPtr)305 CommonMatchPNG(
306     tkimg_MFile *handle,
307     int *widthPtr, int *heightPtr
308 ) {
309     unsigned char buf[8];
310 
311     if ((tkimg_Read2(handle, (char *) buf, 8) != 8)
312             || (strncmp("\211\120\116\107\15\12\32\12", (char *) buf, 8) != 0)
313             || (tkimg_Read2(handle, (char *) buf, 8) != 8)
314             || (strncmp("\111\110\104\122", (char *) buf+4, 4) != 0)
315             || (tkimg_Read2(handle, (char *) buf, 8) != 8)) {
316         return 0;
317     }
318     *widthPtr = (buf[0]<<24) + (buf[1]<<16) + (buf[2]<<8) + buf[3];
319     *heightPtr = (buf[4]<<24) + (buf[5]<<16) + (buf[6]<<8) + buf[7];
320     return 1;
321 }
322 
323 static int
ChnRead(Tcl_Interp * interp,Tcl_Channel chan,const char * fileName,Tcl_Obj * format,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY)324 ChnRead(
325     Tcl_Interp *interp,
326     Tcl_Channel chan,
327     const char *fileName,
328     Tcl_Obj *format,
329     Tk_PhotoHandle imageHandle,
330     int destX, int destY,
331     int width, int height,
332     int srcX, int srcY
333 ) {
334     png_structp png_ptr;
335     tkimg_MFile handle;
336     cleanup_info cleanup;
337 
338     handle.data = (char *) chan;
339     handle.state = IMG_CHAN;
340 
341     cleanup.interp = interp;
342 
343     png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,
344             (png_voidp) &cleanup,tk_png_error,tk_png_warning);
345     if (!png_ptr) return(0);
346 
347     png_set_read_fn(png_ptr, (png_voidp) &handle, tk_png_read);
348 
349     return CommonReadPNG(png_ptr, interp, fileName, format, imageHandle, destX, destY,
350             width, height, srcX, srcY);
351 }
352 
353 static int
ObjRead(Tcl_Interp * interp,Tcl_Obj * dataObj,Tcl_Obj * format,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY)354 ObjRead(
355     Tcl_Interp *interp,
356     Tcl_Obj *dataObj,
357     Tcl_Obj *format,
358     Tk_PhotoHandle imageHandle,
359     int destX, int destY,
360     int width, int height,
361     int srcX, int srcY
362 ) {
363     png_structp png_ptr;
364     tkimg_MFile handle;
365     cleanup_info cleanup;
366 
367     cleanup.interp = interp;
368 
369     png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,
370             (png_voidp) &cleanup,tk_png_error,tk_png_warning);
371     if (!png_ptr) return TCL_ERROR;
372 
373     tkimg_ReadInit(dataObj,'\211',&handle);
374 
375     png_set_read_fn(png_ptr,(png_voidp) &handle, tk_png_read);
376 
377     return CommonReadPNG(png_ptr, interp, "InlineData", format, imageHandle, destX, destY,
378             width, height, srcX, srcY);
379 }
380 
381 static int
CommonReadPNG(png_structp png_ptr,Tcl_Interp * interp,const char * fileName,Tcl_Obj * format,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY)382 CommonReadPNG(
383     png_structp png_ptr,
384     Tcl_Interp *interp,
385     const char *fileName,
386     Tcl_Obj *format,
387     Tk_PhotoHandle imageHandle,
388     int destX, int destY,
389     int width, int height,
390     int srcX, int srcY
391 ) {
392     png_infop info_ptr;
393     png_infop end_info;
394     char **png_data = NULL;
395     Tk_PhotoImageBlock block;
396     unsigned int i;
397     png_uint_32 info_width, info_height;
398     int bit_depth, color_type, interlace_type;
399     int intent;
400     int result = TCL_OK;
401     double fileGamma = -1.0;
402     int useAlpha = 0;
403     int addAlpha = 0;
404     FMTOPT opts;
405 
406     if (ParseFormatOpts(interp, format, &opts) != TCL_OK) {
407         return TCL_ERROR;
408     }
409 
410     info_ptr=png_create_info_struct(png_ptr);
411     if (!info_ptr) {
412         png_destroy_read_struct(&png_ptr,NULL,NULL);
413         return(TCL_ERROR);
414     }
415 
416     end_info=png_create_info_struct(png_ptr);
417     if (!end_info) {
418         png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
419         return(TCL_ERROR);
420     }
421 
422     if (setjmp((((cleanup_info *) png_get_error_ptr(png_ptr))->jmpbuf))) {
423         if (png_data) {
424             ckfree((char *)png_data);
425         }
426         png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
427         return TCL_ERROR;
428     }
429 
430     png_read_info(png_ptr,info_ptr);
431 
432     png_get_IHDR(png_ptr, info_ptr, &info_width, &info_height, &bit_depth,
433         &color_type, &interlace_type, (int *) NULL, (int *) NULL);
434 
435     if ((srcX + width) > (int) info_width) {
436         width = info_width - srcX;
437     }
438     if ((srcY + height) > (int) info_height) {
439         height = info_height - srcY;
440     }
441     if ((width <= 0) || (height <= 0)
442         || (srcX >= (int) info_width)
443         || (srcY >= (int) info_height)) {
444         png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
445         return TCL_OK;
446     }
447 
448     if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) {
449         png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
450         return TCL_ERROR;
451     }
452 
453     Tk_PhotoGetImage(imageHandle, &block);
454 
455 #ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED
456     png_set_scale_16(png_ptr);
457 #endif
458 
459     png_set_expand(png_ptr);
460 
461     if (png_get_sRGB(png_ptr, info_ptr, &intent)) {
462         png_set_sRGB(png_ptr, info_ptr, intent);
463     } else {
464         if (opts.gamma < 0.0) {
465             /* No gamma specified on the command line.
466              * Check, if a gamma value is specified in the file.
467              */
468             if (png_get_gAMA(png_ptr, info_ptr, &fileGamma)) {
469                 png_set_gamma(png_ptr, 1.0, fileGamma);
470             }
471         } else {
472             png_set_gamma(png_ptr, 1.0, opts.gamma);
473         }
474     }
475 
476     if ((color_type & PNG_COLOR_MASK_ALPHA)
477         || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
478         /* Image has an alpha channel.
479            Check, if we don't want to use the alpha channel (matte == false) */
480         if (!opts.matte) {
481             png_set_strip_alpha (png_ptr);
482         }
483     } else {
484         /* Image has no alpha channel.
485            If a valid alpha multiply has been specified, add an alpha channel to the image.
486            The matte flag is ignored. */
487         if (opts.alpha >= 0.0) {
488             png_set_add_alpha(png_ptr, (unsigned int)(opts.alpha*255), PNG_FILLER_AFTER);
489         }
490     }
491 
492     if (opts.verbose) {
493         PrintReadInfo (info_width, info_height, png_get_channels(png_ptr, info_ptr),
494                        bit_depth, fileGamma, fileName, "Reading image:");
495     }
496 
497     /* Note: png_read_update_info may only be called once per info_ptr !! */
498     png_read_update_info(png_ptr, info_ptr);
499 
500     block.pixelSize = png_get_channels(png_ptr, info_ptr);
501     block.pitch = png_get_rowbytes(png_ptr, info_ptr);
502 
503     if ((color_type & PNG_COLOR_MASK_COLOR) == 0) {
504         /* grayscale image */
505         block.offset[1] = 0;
506         block.offset[2] = 0;
507     }
508     block.width = width;
509     block.height = height;
510 
511     if ((color_type & PNG_COLOR_MASK_ALPHA)
512         || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
513         /* Image has an alpha channel.
514            Check, if we don't want to use the alpha channel (matte == false) */
515         if (!opts.matte) {
516             block.offset[3] = 0;
517         } else {
518             block.offset[3] = block.pixelSize - 1;
519             if ( opts.alpha >= 0.0) {
520                 useAlpha = 1;
521             }
522         }
523     } else {
524         /* Image has no alpha channel.
525            If a valid alpha multiply has been specified, add an alpha channel to the image.
526            The matte flag is ignored. */
527         if ( opts.alpha >= 0.0) {
528             addAlpha = 1;
529         } else {
530             block.offset[3] = 0;
531         }
532     }
533 
534     if (addAlpha) {
535         block.offset[3] = block.pixelSize - 1;
536     }
537 
538     png_data = (char **) ckalloc(sizeof(char *) * info_height + info_height * block.pitch);
539 
540     for(i=0;i<info_height;i++) {
541         png_data[i] = ((char *) png_data) + (sizeof(char *) * info_height + i * block.pitch);
542     }
543 
544     png_read_image(png_ptr,(png_bytepp) png_data);
545 
546     block.pixelPtr=(unsigned char *) (png_data[srcY]+srcX*block.pixelSize);
547 
548     if (useAlpha) {
549         unsigned char * alphaPtr = block.pixelPtr + block.offset[3];
550         for(i=0; i<(unsigned int)(height*width); i++) {
551             *alphaPtr = opts.alpha * *alphaPtr;
552             alphaPtr += block.offset[3] + 1 ;
553         }
554     }
555 
556     if (tkimg_PhotoPutBlock(
557         interp, imageHandle, &block,
558         destX, destY, width, height,
559         block.offset[3]? TK_PHOTO_COMPOSITE_OVERLAY: TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
560         result = TCL_ERROR;
561     }
562 
563     ckfree((char *) png_data);
564     png_destroy_read_struct(&png_ptr,&info_ptr,&end_info);
565     return result;
566 }
567 
568 static int
ChnWrite(Tcl_Interp * interp,const char * filename,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)569 ChnWrite(
570     Tcl_Interp *interp,
571     const char *filename,
572     Tcl_Obj *format,
573     Tk_PhotoImageBlock *blockPtr
574 ) {
575     png_structp png_ptr;
576     png_infop info_ptr;
577     tkimg_MFile handle;
578     int result;
579     cleanup_info cleanup;
580     Tcl_Channel chan = (Tcl_Channel) NULL;
581 
582     chan = tkimg_OpenFileChannel(interp, filename, 0644);
583     if (!chan) {
584         return TCL_ERROR;
585     }
586 
587     handle.data = (char *) chan;
588     handle.state = IMG_CHAN;
589 
590     cleanup.interp = interp;
591 
592     png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
593             (png_voidp) &cleanup,tk_png_error,tk_png_warning);
594     if (!png_ptr) {
595         Tcl_Close(NULL, chan);
596         return TCL_ERROR;
597     }
598 
599     info_ptr=png_create_info_struct(png_ptr);
600     if (!info_ptr) {
601         png_destroy_write_struct(&png_ptr,NULL);
602         Tcl_Close(NULL, chan);
603         return TCL_ERROR;
604     }
605 
606     png_set_write_fn(png_ptr,(png_voidp) &handle, tk_png_write, tk_png_flush);
607 
608     result = CommonWritePNG(interp, png_ptr, info_ptr, format, blockPtr);
609     Tcl_Close(NULL, chan);
610     return result;
611 }
612 
StringWrite(Tcl_Interp * interp,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)613 static int StringWrite(
614     Tcl_Interp *interp,
615     Tcl_Obj *format,
616     Tk_PhotoImageBlock *blockPtr
617 ) {
618     png_structp png_ptr;
619     png_infop info_ptr;
620     tkimg_MFile handle;
621     int result;
622     cleanup_info cleanup;
623     Tcl_DString data;
624 
625     Tcl_DStringInit(&data);
626     cleanup.interp = interp;
627 
628     png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
629             (png_voidp) &cleanup, tk_png_error, tk_png_warning);
630     if (!png_ptr) {
631         return TCL_ERROR;
632     }
633 
634     info_ptr = png_create_info_struct(png_ptr);
635     if (!info_ptr) {
636         png_destroy_write_struct(&png_ptr,NULL);
637         return TCL_ERROR;
638     }
639 
640     png_set_write_fn(png_ptr, (png_voidp) &handle, tk_png_write, tk_png_flush);
641 
642     tkimg_WriteInit(&data, &handle);
643 
644     result = CommonWritePNG(interp, png_ptr, info_ptr, format, blockPtr);
645     tkimg_Putc(IMG_DONE, &handle);
646     if (result == TCL_OK) {
647         Tcl_DStringResult(interp, &data);
648     } else {
649         Tcl_DStringFree(&data);
650     }
651     return result;
652 }
653 
654 static int
CommonWritePNG(Tcl_Interp * interp,png_structp png_ptr,png_infop info_ptr,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)655 CommonWritePNG(
656     Tcl_Interp *interp,
657     png_structp png_ptr,
658     png_infop info_ptr,
659     Tcl_Obj *format,
660     Tk_PhotoImageBlock *blockPtr
661 ) {
662     int greenOffset, blueOffset, alphaOffset;
663     int tagcount = 0;
664     Tcl_Obj **tags = (Tcl_Obj **) NULL;
665     int I, pass, number_passes, color_type;
666     int newPixelSize;
667     png_bytep row_pointers = (png_bytep) NULL;
668 
669     if (tkimg_ListObjGetElements(interp, format, &tagcount, &tags) != TCL_OK) {
670         return TCL_ERROR;
671     }
672     tagcount = (tagcount > 1) ? (tagcount - 1) / 2: 0;
673 
674     if (setjmp((((cleanup_info *) png_get_error_ptr(png_ptr))->jmpbuf))) {
675         if (row_pointers) {
676             ckfree((char *) row_pointers);
677         }
678         png_destroy_write_struct(&png_ptr,&info_ptr);
679         return TCL_ERROR;
680     }
681     greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
682     blueOffset = blockPtr->offset[2] - blockPtr->offset[0];
683     alphaOffset = blockPtr->offset[0];
684     if (alphaOffset < blockPtr->offset[2]) {
685         alphaOffset = blockPtr->offset[2];
686     }
687     if (++alphaOffset < blockPtr->pixelSize) {
688         alphaOffset -= blockPtr->offset[0];
689     } else {
690         alphaOffset = 0;
691     }
692 
693     if (greenOffset || blueOffset) {
694         color_type = PNG_COLOR_TYPE_RGB;
695         newPixelSize = 3;
696     } else {
697         color_type = PNG_COLOR_TYPE_GRAY;
698         newPixelSize = 1;
699     }
700     if (alphaOffset) {
701         color_type |= PNG_COLOR_MASK_ALPHA;
702         newPixelSize++;
703 #if 0 /* The function png_set_filler doesn't seem to work; don't known why :-( */
704     } else if ((blockPtr->pixelSize==4) && (newPixelSize == 3)
705             && (png_set_filler != NULL)) {
706         /*
707          * The set_filler() function doesn't need to be called
708          * because the code below can handle all necessary
709          * re-allocation of memory. Only it is more economically
710          * to let the PNG library do that, which is only
711          * possible with v0.95 and higher.
712          */
713         png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
714         newPixelSize++;
715 #endif
716     }
717 
718     png_set_IHDR(png_ptr, info_ptr, blockPtr->width, blockPtr->height, 8,
719             color_type, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_BASE,
720             PNG_FILTER_TYPE_BASE);
721 
722     if (tagcount > 0) {
723         png_text_compat text;
724         for(I=0;I<tagcount;I++) {
725             int length;
726             memset(&text, 0, sizeof(png_text_compat));
727             text.compat.key = Tcl_GetStringFromObj(tags[2*I+1], (int *) NULL);
728             text.compat.text = Tcl_GetStringFromObj(tags[2*I+2], &length);
729             text.compat.text_length = length;
730             if (text.compat.text_length>COMPRESS_THRESHOLD) {
731                 text.compat.compression = PNG_TEXT_COMPRESSION_zTXt;
732             } else {
733                 text.compat.compression = PNG_TEXT_COMPRESSION_NONE;
734             }
735             png_set_text(png_ptr, info_ptr, &text.compat, 1);
736         }
737     }
738     png_write_info(png_ptr,info_ptr);
739 
740     number_passes = png_set_interlace_handling(png_ptr);
741 
742     if (blockPtr->pixelSize != newPixelSize) {
743         int J, oldPixelSize;
744         png_bytep src, dst;
745         oldPixelSize = blockPtr->pixelSize;
746         row_pointers = (png_bytep)
747                 ckalloc(blockPtr->width * newPixelSize);
748         for (pass = 0; pass < number_passes; pass++) {
749             for(I=0; I<blockPtr->height; I++) {
750                 src = (png_bytep) blockPtr->pixelPtr
751                         + I * blockPtr->pitch + blockPtr->offset[0];
752                 dst = row_pointers;
753                 for (J = blockPtr->width; J > 0; J--) {
754                     memcpy(dst, src, newPixelSize);
755                     src += oldPixelSize;
756                     dst += newPixelSize;
757                 }
758                 png_write_row(png_ptr, row_pointers);
759             }
760         }
761         ckfree((char *) row_pointers);
762         row_pointers = NULL;
763     } else {
764         for (pass = 0; pass < number_passes; pass++) {
765             for(I=0;I<blockPtr->height;I++) {
766                 png_write_row(png_ptr, (png_bytep) blockPtr->pixelPtr
767                         + I * blockPtr->pitch + blockPtr->offset[0]);
768             }
769         }
770     }
771     png_write_end(png_ptr,NULL);
772     png_destroy_write_struct(&png_ptr,&info_ptr);
773 
774     return(TCL_OK);
775 }
776