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