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