1/* 2 * jpeg.c -- 3 * 4 * JPEG 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 JPEG files in the standard 9 * JFIF file format. ("JPEG" should be the format name.) It can also read 10 * and write strings containing base64-encoded JPEG data. 11 * 12 * Several options can be provided in the format string, for example: 13 * 14 * imageObject read input.jpg -shrink -format "jpeg -grayscale" 15 * imageObject write output.jpg -format "jpeg -quality 50 -progressive" 16 * 17 * The supported options for reading are: 18 * -fast: Fast, low-quality processing 19 * -grayscale: Force incoming image to grayscale 20 * The supported options for writing are: 21 * -quality N: Compression quality (0..100; 5-95 is useful range) 22 * Default value: 75 23 * -smooth N: Perform smoothing (10-30 is enough for most GIF's) 24 * Default value: 0 25 * -grayscale: Create monochrome JPEG file 26 * -optimize: Optimize Huffman table 27 * -progressive: Create progressive JPEG file 28 * 29 * 30 * Copyright (c) 1996-1997 Thomas G. Lane. 31 * This file is based on tkImgPPM.c from the Tk 4.2 distribution. 32 * That file is 33 * Copyright (c) 1994 The Australian National University. 34 * Copyright (c) 1994-1996 Sun Microsystems, Inc. 35 * 36 * See the file "license.terms" for information on usage and redistribution 37 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 38 * 39 * You will need a copy of the IJG JPEG library, version 5 or later, 40 * to use this file. If you didn't receive it with this package, see 41 * ftp://ftp.uu.net/graphics/jpeg/ 42 * 43 * Author: Tom Lane (tgl@sss.pgh.pa.us) 44 * 45 * Modified for dynamical loading, reading from channels and Tcl_Obj's by: 46 * Jan Nijtmans (nijtmans@users.sourceforge.net) 47 * 48 * $Id: jpeg.c 271 2010-06-17 13:40:24Z nijtmans $ 49 */ 50 51/* 52 * Generic initialization code, parameterized via CPACKAGE and PACKAGE. 53 */ 54 55#include "tkimg.h" 56#include "jpegtcl.h" 57 58static int SetupJPegLibrary(Tcl_Interp *interp); 59 60#define MORE_INITIALIZATION \ 61 if (SetupJPegLibrary (interp) != TCL_OK) { return TCL_ERROR; } 62 63#include "init.c" 64 65/* system includes */ 66#include <stdlib.h> 67#include <string.h> 68#include <setjmp.h> 69 70/* 71 * The format record for the JPEG file format: 72 */ 73 74 75/* 76 * Declarations for libjpeg source and destination managers to handle 77 * reading and writing base64-encoded strings and Tcl_Channel's. 78 */ 79 80#define STRING_BUF_SIZE 4096 /* choose any convenient size */ 81 82typedef struct source_mgr { /* Source manager for reading from string */ 83 struct jpeg_source_mgr pub; /* public fields */ 84 85 tkimg_MFile handle; /* base64 stream */ 86 JOCTET buffer[STRING_BUF_SIZE]; /* buffer for a chunk of decoded data */ 87} *src_ptr; 88 89typedef struct destination_mgr { /* Manager for string output */ 90 struct jpeg_destination_mgr pub; /* public fields */ 91 92 tkimg_MFile handle; /* base64 stream */ 93 JOCTET buffer[STRING_BUF_SIZE]; /* buffer for a chunk of decoded data */ 94} *dest_ptr; 95 96/* 97 * Other declarations 98 */ 99 100struct my_error_mgr { /* Extended libjpeg error manager */ 101 struct jpeg_error_mgr pub; /* public fields */ 102 jmp_buf setjmp_buffer; /* for return to caller from error exit */ 103}; 104 105/* 106 * Prototypes for local procedures defined in this file: 107 */ 108 109static int CommonMatch(tkimg_MFile *handle, 110 int *widthPtr, int *heightPtr); 111 112static int CommonRead(Tcl_Interp *interp, 113 j_decompress_ptr cinfo, Tcl_Obj *format, 114 Tk_PhotoHandle imageHandle, int destX, int destY, 115 int width, int height, int srcX, int srcY); 116 117static int CommonWrite(Tcl_Interp *interp, 118 j_compress_ptr cinfo, Tcl_Obj *format, 119 Tk_PhotoImageBlock *blockPtr); 120 121static void my_jpeg_obj_src(j_decompress_ptr, Tcl_Obj *); 122static void my_jpeg_channel_src(j_decompress_ptr, Tcl_Channel); 123static boolean fill_input_buffer(j_decompress_ptr); 124static void skip_input_data(j_decompress_ptr, long); 125static void dummy_source(j_decompress_ptr); 126static void my_jpeg_string_dest(j_compress_ptr, Tcl_DString*); 127static void my_jpeg_channel_dest(j_compress_ptr, Tcl_Channel); 128static void my_init_destination(j_compress_ptr); 129static boolean my_empty_output_buffer(j_compress_ptr); 130static void my_term_destination(j_compress_ptr); 131static void my_error_exit(j_common_ptr cinfo); 132static void my_output_message(j_common_ptr cinfo); 133static void append_jpeg_message(Tcl_Interp *interp, 134 j_common_ptr cinfo); 135 136 137 138static int 139SetupJPegLibrary (interp) 140 Tcl_Interp *interp; 141{ 142 struct jpeg_compress_struct *cinfo; /* libjpeg's parameter structure */ 143 struct my_error_mgr jerror; /* for controlling libjpeg error handling */ 144 int i; 145 146 if (Jpegtcl_InitStubs(interp, JPEGTCL_VERSION, 0) == NULL) { 147 return TCL_ERROR; 148 } 149 150 /* The followin code tries to determine if the JPEG library is 151 valid at all. The library might be configured differently, 152 which will produce core dumps. Also it might be that 153 fields appear in different places in jpeg_compress_struct 154 or jpeg_decompress_struct. This will make the library totally 155 unusable. In stead of a core-dump, we better have a proper 156 error message */ 157 158 /* overallocat size, so we don't get a core-dump if the library 159 thinks that the structure is much larger */ 160 161 cinfo = (struct jpeg_compress_struct *) ckalloc(8*sizeof(struct jpeg_compress_struct)); 162 cinfo->err = jpeg_std_error(&jerror.pub); 163 jerror.pub.error_exit = my_error_exit; 164 jerror.pub.output_message = my_output_message; 165 /* Establish the setjmp return context for my_error_exit to use. */ 166 if (setjmp(jerror.setjmp_buffer)) { 167 /* If we get here, the JPEG library is invalid. */ 168 jpeg_destroy_compress(cinfo); 169 ckfree((char *)cinfo); 170 171 if (interp) { 172 Tcl_AppendResult(interp, "couldn't use \"", "jpegtcl", 173 "\": please upgrade to at least version 6a", (char *) NULL); 174 } 175 return TCL_ERROR; 176 } 177 178 /* Now we can initialize libjpeg. */ 179 ((char *) cinfo)[sizeof(struct jpeg_compress_struct)] = 53; 180 jpeg_create_compress(cinfo); 181 if (((char *) cinfo)[sizeof(struct jpeg_compress_struct)] != 53) { 182 /* Oops. The library changed this value, which is outside the 183 * structure. Definitely, the library is invalid!!!! */ 184 ERREXIT(cinfo, JMSG_NOMESSAGE); 185 } 186 187 /* Set up JPEG compression parameters. */ 188 cinfo->image_width = 16; 189 cinfo->image_height = 16; 190 cinfo->input_components = 3; 191 cinfo->in_color_space = JCS_RGB; 192 cinfo->data_precision = -1; 193 cinfo->optimize_coding = TRUE; 194 cinfo->dct_method = -1; 195 cinfo->X_density = 0; 196 cinfo->Y_density = 0; 197 jpeg_set_defaults(cinfo); 198 199 if ((cinfo->data_precision != BITS_IN_JSAMPLE) || 200 (cinfo->optimize_coding != FALSE) || 201 (cinfo->dct_method != JDCT_DEFAULT) || 202 (cinfo->X_density != 1) || 203 (cinfo->Y_density != 1)) { 204 ERREXIT(cinfo, JMSG_NOMESSAGE); 205 } 206 for (i = 0; i < NUM_ARITH_TBLS; i++) { 207 if ((cinfo->arith_dc_L[i] != 0) || 208 (cinfo->arith_dc_U[i] != 1) || 209 (cinfo->arith_ac_K[i] != 5)) { 210 ERREXIT(cinfo, JMSG_NOMESSAGE); 211 } 212 } 213 jpeg_destroy_compress(cinfo); 214 ckfree((char *) cinfo); 215 return TCL_OK; 216} 217 218 219/* 220 *---------------------------------------------------------------------- 221 * 222 * ChnMatch -- 223 * 224 * This procedure is invoked by the photo image type to see if 225 * a channel contains image data in JPEG format. 226 * 227 * Results: 228 * The return value is >0 if the first characters in channel "chan" 229 * look like JPEG data, and 0 otherwise. For a valid file, the 230 * image dimensions are determined. 231 * 232 * Side effects: 233 * The access position in f may change. 234 * 235 *---------------------------------------------------------------------- 236 */ 237 238static int ChnMatch( 239 Tcl_Channel chan, /* The image channel, open for reading. */ 240 const char *fileName, /* The name of the image file. */ 241 Tcl_Obj *format, /* User-specified format string, or NULL. */ 242 int *widthPtr, /* The dimensions of the image are */ 243 int *heightPtr, /* returned here if the file is a valid 244 * JPEG file. */ 245 Tcl_Interp *interp 246) { 247 tkimg_MFile handle; 248 249 handle.data = (char *) chan; 250 handle.state = IMG_CHAN; 251 return CommonMatch(&handle, widthPtr, heightPtr); 252} 253 254/* 255 *---------------------------------------------------------------------- 256 * 257 * ObjMatch -- 258 * 259 * This procedure is invoked by the photo image type to see if 260 * a string contains image data in JPEG format. 261 * 262 * Results: 263 * The return value is >0 if the first characters in the string look 264 * like JPEG data, and 0 otherwise. For a valid image, the image 265 * dimensions are determined. 266 * 267 * Side effects: 268 * the size of the image is placed in widthPtr and heightPtr. 269 * 270 *---------------------------------------------------------------------- 271 */ 272 273static int ObjMatch( 274 Tcl_Obj *data, /* the object containing the image data */ 275 Tcl_Obj *format, /* User-specified format object, or NULL. */ 276 int *widthPtr, /* The dimensions of the image are */ 277 int *heightPtr, /* returned here if the string is a valid 278 * JPEG image. */ 279 Tcl_Interp *interp 280) { 281 tkimg_MFile handle; 282 283 tkimg_ReadInit(data, '\377', &handle); 284 return CommonMatch(&handle, widthPtr, heightPtr); 285} 286 287/* 288 *---------------------------------------------------------------------- 289 * 290 * CommonMatch -- 291 * 292 * This procedure is invoked by the photo image type to see if 293 * a string contains image data in JPEG format. 294 * 295 * Results: 296 * The return value is >0 if the first characters in the string look 297 * like JPEG data, and 0 otherwise. For a valid image, the image 298 * dimensions are determined. 299 * 300 * Side effects: 301 * the size of the image is placed in widthPtr and heightPtr. 302 * 303 *---------------------------------------------------------------------- 304 */ 305 306static int 307CommonMatch(handle, widthPtr, heightPtr) 308 tkimg_MFile *handle; /* the "file" handle */ 309 int *widthPtr, *heightPtr; /* The dimensions of the image are 310 * returned here if the string is a valid 311 * JPEG image. */ 312{ 313 char buf[256]; 314 int i; 315 316 i = tkimg_Read(handle, buf, 3); 317 if ((i != 3)||strncmp(buf,"\377\330\377", 3)) { 318 return 0; 319 } 320 321 buf[0] = buf[2]; 322 /* at top of loop: have just read first FF of a marker into buf[0] */ 323 for (;;) { 324 /* get marker type byte, skipping any padding FFs */ 325 while (buf[0] == (char) 0xff) { 326 if (tkimg_Read(handle, buf,1) != 1) { 327 return 0; 328 } 329 } 330 /* look for SOF0, SOF1, or SOF2, which are the only JPEG variants 331 * currently accepted by libjpeg. 332 */ 333 if (buf[0] == (char) 0xc0 || buf[0] == (char) 0xc1 334 || buf[0] == (char) 0xc2) 335 break; 336 /* nope, skip the marker parameters */ 337 if (tkimg_Read(handle, buf, 2) != 2) { 338 return 0; 339 } 340 i = ((buf[0] & 0x0ff)<<8) + (buf[1] & 0x0ff) - 1; 341 while (i>256) { 342 tkimg_Read(handle, buf, 256); 343 i -= 256; 344 } 345 if ((i<1) || (tkimg_Read(handle, buf, i)) != i) { 346 return 0; 347 } 348 buf[0] = buf[i-1]; 349 /* skip any inter-marker junk (there shouldn't be any, really) */ 350 while (buf[0] != (char) 0xff) { 351 if (tkimg_Read(handle, buf,1) != 1) { 352 return 0; 353 } 354 } 355 } 356 /* Found the SOFn marker, get image dimensions */ 357 if (tkimg_Read(handle, buf, 7) != 7) { 358 return 0; 359 } 360 *heightPtr = ((buf[3] & 0x0ff)<<8) + (buf[4] & 0x0ff); 361 *widthPtr = ((buf[5] & 0x0ff)<<8) + (buf[6] & 0x0ff); 362 363 return 1; 364} 365 366/* 367 *---------------------------------------------------------------------- 368 * 369 * ChnRead -- 370 * 371 * This procedure is called by the photo image type to read 372 * JPEG format data from a channel, and give it to 373 * the photo image. 374 * 375 * Results: 376 * A standard TCL completion code. If TCL_ERROR is returned 377 * then an error message is left in interp->result. 378 * 379 * Side effects: 380 * New data is added to the image given by imageHandle. 381 * 382 *---------------------------------------------------------------------- 383 */ 384 385static int 386ChnRead(interp, chan, fileName, format, imageHandle, destX, destY, 387 width, height, srcX, srcY) 388 Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ 389 Tcl_Channel chan; /* The image channel, open for reading. */ 390 const char *fileName; /* The name of the image file. */ 391 Tcl_Obj *format; /* User-specified format string, or NULL. */ 392 Tk_PhotoHandle imageHandle; /* The photo image to write into. */ 393 int destX, destY; /* Coordinates of top-left pixel in 394 * photo image to be written to. */ 395 int width, height; /* Dimensions of block of photo image to 396 * be written to. */ 397 int srcX, srcY; /* Coordinates of top-left pixel to be used 398 * in image being read. */ 399{ 400 struct jpeg_decompress_struct cinfo; /* libjpeg's parameter structure */ 401 struct my_error_mgr jerror; /* for controlling libjpeg error handling */ 402 int result; 403 404 /* Initialize JPEG error handler */ 405 /* We set up the normal JPEG error routines, then override error_exit. */ 406 cinfo.err = jpeg_std_error(&jerror.pub); 407 jerror.pub.error_exit = my_error_exit; 408 jerror.pub.output_message = my_output_message; 409 410 /* Establish the setjmp return context for my_error_exit to use. */ 411 if (setjmp(jerror.setjmp_buffer)) { 412 /* If we get here, the JPEG code has signaled an error. */ 413 Tcl_AppendResult(interp, "couldn't read JPEG string: ", (char *) NULL); 414 append_jpeg_message(interp, (j_common_ptr) &cinfo); 415 jpeg_destroy_decompress(&cinfo); 416 return TCL_ERROR; 417 } 418 419 /* Now we can initialize libjpeg. */ 420 jpeg_CreateDecompress(&cinfo, JPEG_LIB_VERSION, 421 (size_t) sizeof(struct jpeg_decompress_struct)); 422 my_jpeg_channel_src(&cinfo, chan); 423 424 /* Share code with ObjRead. */ 425 result = CommonRead(interp, &cinfo, format, imageHandle, 426 destX, destY, width, height, srcX, srcY); 427 428 /* Reclaim libjpeg's internal resources. */ 429 jpeg_destroy_decompress(&cinfo); 430 431 return result; 432} 433 434/* 435 *---------------------------------------------------------------------- 436 * 437 * ObjRead -- 438 * 439 * This procedure is called by the photo image type to read 440 * JPEG format data from a base64 encoded string, and give it to 441 * the photo image. 442 * 443 * Results: 444 * A standard TCL completion code. If TCL_ERROR is returned 445 * then an error message is left in interp->result. 446 * 447 * Side effects: 448 * New data is added to the image given by imageHandle. 449 * 450 *---------------------------------------------------------------------- 451 */ 452 453static int 454ObjRead(interp, data, format, imageHandle, destX, destY, 455 width, height, srcX, srcY) 456 Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ 457 Tcl_Obj *data; /* Object containing the image data. */ 458 Tcl_Obj *format; /* User-specified format object, or NULL. */ 459 Tk_PhotoHandle imageHandle; /* The photo image to write into. */ 460 int destX, destY; /* Coordinates of top-left pixel in 461 * photo image to be written to. */ 462 int width, height; /* Dimensions of block of photo image to 463 * be written to. */ 464 int srcX, srcY; /* Coordinates of top-left pixel to be used 465 * in image being read. */ 466{ 467 struct jpeg_decompress_struct cinfo; /* libjpeg's parameter structure */ 468 struct my_error_mgr jerror; /* for controlling libjpeg error handling */ 469 int result; 470 471 /* Initialize JPEG error handler */ 472 /* We set up the normal JPEG error routines, then override error_exit. */ 473 cinfo.err = jpeg_std_error(&jerror.pub); 474 jerror.pub.error_exit = my_error_exit; 475 jerror.pub.output_message = my_output_message; 476 477 /* Establish the setjmp return context for my_error_exit to use. */ 478 if (setjmp(jerror.setjmp_buffer)) { 479 /* If we get here, the JPEG code has signaled an error. */ 480 Tcl_AppendResult(interp, "couldn't read JPEG string: ", (char *) NULL); 481 append_jpeg_message(interp, (j_common_ptr) &cinfo); 482 jpeg_destroy_decompress(&cinfo); 483 return TCL_ERROR; 484 } 485 486 /* Now we can initialize libjpeg. */ 487 jpeg_CreateDecompress(&cinfo, JPEG_LIB_VERSION, 488 (size_t) sizeof(struct jpeg_decompress_struct)); 489 my_jpeg_obj_src(&cinfo, data); 490 491 /* Share code with ChnRead. */ 492 result = CommonRead(interp, &cinfo, format, imageHandle, 493 destX, destY, width, height, srcX, srcY); 494 495 /* Reclaim libjpeg's internal resources. */ 496 jpeg_destroy_decompress(&cinfo); 497 498 return result; 499} 500 501/* 502 *---------------------------------------------------------------------- 503 * 504 * CommonRead -- 505 * 506 * The common guts of ChnRead and ObjRead. 507 * The decompress struct has already been set up and the 508 * appropriate data source manager initialized. 509 * The caller should do jpeg_destroy_decompress upon return. 510 * 511 *---------------------------------------------------------------------- 512 */ 513static int 514CommonRead(interp, cinfo, format, imageHandle, destX, destY, 515 width, height, srcX, srcY) 516 Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ 517 j_decompress_ptr cinfo; /* Already-constructed decompress struct. */ 518 Tcl_Obj *format; /* User-specified format string, or NULL. */ 519 Tk_PhotoHandle imageHandle; /* The photo image to write into. */ 520 int destX, destY; /* Coordinates of top-left pixel in 521 * photo image to be written to. */ 522 int width, height; /* Dimensions of block of photo image to 523 * be written to. */ 524 int srcX, srcY; /* Coordinates of top-left pixel to be used 525 * in image being read. */ 526{ 527 static const char *const jpegReadOptions[] = {"-fast", "-grayscale", NULL}; 528 int fileWidth, fileHeight, stopY, curY, outY, outWidth, outHeight; 529 Tk_PhotoImageBlock block; 530 JSAMPARRAY buffer; /* Output row buffer */ 531 int objc, i, index; 532 Tcl_Obj **objv = (Tcl_Obj **) NULL; 533 534 /* Ready to read header data. */ 535 jpeg_read_header(cinfo, TRUE); 536 537 /* This code only supports 8-bit-precision JPEG files. */ 538 if ((cinfo->data_precision != 8) || 539 (sizeof(JSAMPLE) != sizeof(unsigned char))) { 540 Tcl_AppendResult(interp, "Unsupported JPEG precision", (char *) NULL); 541 return TCL_ERROR; 542 } 543 544 /* Process format parameters. */ 545 if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK) { 546 return TCL_ERROR; 547 } 548 if (objc) { 549 for (i=1; i<objc; i++) { 550 if (Tcl_GetIndexFromObj(interp, objv[i], (CONST84 char *CONST86 *)jpegReadOptions, 551 "format option", 0, &index)!=TCL_OK) { 552 return TCL_ERROR; 553 } 554 switch (index) { 555 case 0: { 556 /* Select fast processing mode. */ 557 cinfo->two_pass_quantize = FALSE; 558 cinfo->dither_mode = JDITHER_ORDERED; 559 cinfo->dct_method = JDCT_FASTEST; 560 cinfo->do_fancy_upsampling = FALSE; 561 break; 562 } 563 case 1: { 564 /* Force monochrome output. */ 565 cinfo->out_color_space = JCS_GRAYSCALE; 566 break; 567 } 568 } 569 } 570 } 571 572 jpeg_start_decompress(cinfo); 573 574 /* Check dimensions. */ 575 fileWidth = (int) cinfo->output_width; 576 fileHeight = (int) cinfo->output_height; 577 if ((srcX + width) > fileWidth) { 578 outWidth = fileWidth - srcX; 579 } else { 580 outWidth = width; 581 } 582 if ((srcY + height) > fileHeight) { 583 outHeight = fileHeight - srcY; 584 } else { 585 outHeight = height; 586 } 587 if ((outWidth <= 0) || (outHeight <= 0) 588 || (srcX >= fileWidth) || (srcY >= fileHeight)) { 589 return TCL_OK; 590 } 591 592 /* Check colorspace. */ 593 switch (cinfo->out_color_space) { 594 case JCS_GRAYSCALE: 595 /* a single-sample grayscale pixel is expanded into equal R,G,B values */ 596 block.pixelSize = 1; 597 block.offset[0] = 0; 598 block.offset[1] = 0; 599 block.offset[2] = 0; 600 break; 601 case JCS_RGB: 602 /* note: this pixel layout assumes default configuration of libjpeg. */ 603 block.pixelSize = 3; 604 block.offset[0] = 0; 605 block.offset[1] = 1; 606 block.offset[2] = 2; 607 break; 608 default: 609 Tcl_AppendResult(interp, "Unsupported JPEG color space", (char *) NULL); 610 return TCL_ERROR; 611 } 612 block.width = outWidth; 613 block.height = 1; 614 block.pitch = block.pixelSize * fileWidth; 615 block.offset[3] = block.offset[0]; 616 617 if (tkimg_PhotoExpand(interp, imageHandle, destX + outWidth, destY + outHeight) == TCL_ERROR) { 618 jpeg_abort_decompress(cinfo); 619 return TCL_ERROR; 620 } 621 622 /* Make a temporary one-row-high sample array */ 623 buffer = (*cinfo->mem->alloc_sarray) 624 ((j_common_ptr) cinfo, JPOOL_IMAGE, 625 cinfo->output_width * cinfo->output_components, 1); 626 block.pixelPtr = (unsigned char *) buffer[0] + srcX * block.pixelSize; 627 628 /* Read as much of the data as we need to */ 629 stopY = srcY + outHeight; 630 outY = destY; 631 for (curY = 0; curY < stopY; curY++) { 632 jpeg_read_scanlines(cinfo, buffer, 1); 633 if (curY >= srcY) { 634 if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, outWidth, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) { 635 jpeg_abort_decompress(cinfo); 636 return TCL_ERROR; 637 } 638 outY++; 639 } 640 } 641 642 /* Do normal cleanup if we read the whole image; else early abort */ 643 if (cinfo->output_scanline == cinfo->output_height) 644 jpeg_finish_decompress(cinfo); 645 else 646 jpeg_abort_decompress(cinfo); 647 648 return TCL_OK; 649} 650 651/* 652 *---------------------------------------------------------------------- 653 * 654 * ChnWrite -- 655 * 656 * This procedure is invoked to write image data to a file in JPEG 657 * format. 658 * 659 * Results: 660 * A standard TCL completion code. If TCL_ERROR is returned 661 * then an error message is left in interp->result. 662 * 663 * Side effects: 664 * Data is written to the file given by "fileName". 665 * 666 *---------------------------------------------------------------------- 667 */ 668 669static int 670ChnWrite(interp, fileName, format, blockPtr) 671 Tcl_Interp *interp; 672 const char *fileName; 673 Tcl_Obj *format; 674 Tk_PhotoImageBlock *blockPtr; 675{ 676 struct jpeg_compress_struct cinfo; /* libjpeg's parameter structure */ 677 struct my_error_mgr jerror; /* for controlling libjpeg error handling */ 678 Tcl_Channel chan; 679 int result; 680 681 chan = tkimg_OpenFileChannel(interp, fileName, 0644); 682 if (!chan) { 683 return TCL_ERROR; 684 } 685 686 /* Initialize JPEG error handler */ 687 /* We set up the normal JPEG error routines, then override error_exit. */ 688 cinfo.err = jpeg_std_error(&jerror.pub); 689 jerror.pub.error_exit = my_error_exit; 690 jerror.pub.output_message = my_output_message; 691 692 /* Establish the setjmp return context for my_error_exit to use. */ 693 if (setjmp(jerror.setjmp_buffer)) { 694 /* If we get here, the JPEG code has signaled an error. */ 695 Tcl_AppendResult(interp, "couldn't write JPEG file \"", fileName, 696 "\": ", (char *) NULL); 697 append_jpeg_message(interp, (j_common_ptr) &cinfo); 698 jpeg_destroy_compress(&cinfo); 699 Tcl_Close(interp, chan); 700 return TCL_ERROR; 701 } 702 703 /* Now we can initialize libjpeg. */ 704 jpeg_create_compress(&cinfo); 705 my_jpeg_channel_dest(&cinfo, chan); 706 707 /* Share code with StringWrite. */ 708 result = CommonWrite(interp, &cinfo, format, blockPtr); 709 710 jpeg_destroy_compress(&cinfo); 711 if (Tcl_Close(interp, chan) == TCL_ERROR) { 712 return TCL_ERROR; 713 } 714 return result; 715} 716 717/* 718 *---------------------------------------------------------------------- 719 * 720 * StringWrite -- 721 * 722 * This procedure is called by the photo image type to write 723 * JPEG format data to a base-64 encoded string from the photo block. 724 * 725 * Results: 726 * A standard TCL completion code. If TCL_ERROR is returned 727 * then an error message is left in interp->result. 728 * 729 * Side effects: 730 * None. 731 * 732 *---------------------------------------------------------------------- 733 */ 734 735static int StringWrite( 736 Tcl_Interp *interp, 737 Tcl_Obj *format, 738 Tk_PhotoImageBlock *blockPtr 739) { 740 struct jpeg_compress_struct cinfo; /* libjpeg's parameter structure */ 741 struct my_error_mgr jerror; /* for controlling libjpeg error handling */ 742 int result; 743 Tcl_DString data; 744 745 Tcl_DStringInit(&data); 746 747 /* Initialize JPEG error handler */ 748 /* We set up the normal JPEG error routines, then override error_exit. */ 749 cinfo.err = jpeg_std_error(&jerror.pub); 750 jerror.pub.error_exit = my_error_exit; 751 jerror.pub.output_message = my_output_message; 752 753 /* Establish the setjmp return context for my_error_exit to use. */ 754 if (setjmp(jerror.setjmp_buffer)) { 755 /* If we get here, the JPEG code has signaled an error. */ 756 Tcl_AppendResult(interp, "couldn't write JPEG string: ", (char *) NULL); 757 append_jpeg_message(interp, (j_common_ptr) &cinfo); 758 result = TCL_ERROR; 759 goto writeend; 760 } 761 762 /* Now we can initialize libjpeg. */ 763 jpeg_create_compress(&cinfo); 764 my_jpeg_string_dest(&cinfo, &data); 765 766 /* Share code with ChnWrite. */ 767 result = CommonWrite(interp, &cinfo, format, blockPtr); 768 769writeend: 770 771 jpeg_destroy_compress(&cinfo); 772 if (result == TCL_OK) { 773 Tcl_DStringResult(interp, &data); 774 } else { 775 Tcl_DStringFree(&data); 776 } 777 778 return result; 779} 780 781/* 782 *---------------------------------------------------------------------- 783 * 784 * CommonWrite -- 785 * 786 * The common guts of ChnWrite and StringWrite. 787 * The compress struct has already been set up and the 788 * appropriate data destination manager initialized. 789 * The caller should do jpeg_destroy_compress upon return, 790 * and also close the destination as necessary. 791 * 792 *---------------------------------------------------------------------- 793 */ 794 795static int 796CommonWrite(interp, cinfo, format, blockPtr) 797 Tcl_Interp *interp; 798 j_compress_ptr cinfo; 799 Tcl_Obj *format; 800 Tk_PhotoImageBlock *blockPtr; 801{ 802 static const char *const jpegWriteOptions[] = {"-grayscale", "-optimize", 803 "-progressive", "-quality", "-smooth", NULL}; 804 JSAMPROW row_pointer[1]; /* pointer to original data scanlines */ 805 JSAMPARRAY buffer; /* Intermediate row buffer */ 806 JSAMPROW bufferPtr; 807 int w, h; 808 int greenOffset, blueOffset, alphaOffset; 809 unsigned char *pixelPtr, *pixLinePtr; 810 int objc, i, index, grayscale = 0; 811 Tcl_Obj **objv = (Tcl_Obj **) NULL; 812 813 greenOffset = blockPtr->offset[1] - blockPtr->offset[0]; 814 blueOffset = blockPtr->offset[2] - blockPtr->offset[0]; 815 alphaOffset = blockPtr->offset[0]; 816 if (alphaOffset < blockPtr->offset[2]) { 817 alphaOffset = blockPtr->offset[2]; 818 } 819 if (++alphaOffset < blockPtr->pixelSize) { 820 alphaOffset -= blockPtr->offset[0]; 821 } else { 822 alphaOffset = 0; 823 } 824 825 /* Set up JPEG compression parameters. */ 826 cinfo->image_width = blockPtr->width; 827 cinfo->image_height = blockPtr->height; 828 cinfo->input_components = 3; 829 cinfo->in_color_space = JCS_RGB; 830 831 jpeg_set_defaults(cinfo); 832 833 /* Parse options, if any, and alter default parameters */ 834 835 if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK) { 836 return TCL_ERROR; 837 } 838 if (objc) { 839 for (i=1; i<objc; i++) { 840 if (Tcl_GetIndexFromObj(interp, objv[i], (CONST84 char *CONST86 *)jpegWriteOptions, 841 "format option", 0, &index)!=TCL_OK) { 842 return TCL_ERROR; 843 } 844 switch (index) { 845 case 0: { 846 grayscale = 1; 847 break; 848 } 849 case 1: { 850 cinfo->optimize_coding = TRUE; 851 break; 852 } 853 case 2: { 854 if (jpeg_simple_progression != NULL) { 855 /* Select simple progressive mode. */ 856 jpeg_simple_progression(cinfo); 857 } 858 break; 859 } 860 case 3: { 861 int quality = 0; 862 if (++i >= objc) { 863 Tcl_AppendResult(interp, "No value for option \"", 864 Tcl_GetStringFromObj(objv[--i], (int *) NULL), "\"", (char *) NULL); 865 return TCL_ERROR; 866 } 867 if (Tcl_GetIntFromObj(interp, objv[i], &quality) != TCL_OK) { 868 return TCL_ERROR; 869 } 870 jpeg_set_quality(cinfo, quality, FALSE); 871 break; 872 } 873 case 4: { 874 int smooth = 0; 875 if (++i >= objc) { 876 Tcl_AppendResult(interp, "No value for option \"", 877 Tcl_GetStringFromObj(objv[--i], (int *) NULL), "\"", (char *) NULL); 878 return TCL_ERROR; 879 } 880 if (Tcl_GetIntFromObj(interp, objv[i], &smooth) != TCL_OK) { 881 return TCL_ERROR; 882 } 883 cinfo->smoothing_factor = smooth; 884 break; 885 } 886 } 887 } 888 } 889 890 pixLinePtr = blockPtr->pixelPtr + blockPtr->offset[0]; 891 greenOffset = blockPtr->offset[1] - blockPtr->offset[0]; 892 blueOffset = blockPtr->offset[2] - blockPtr->offset[0]; 893 if ((jpeg_set_colorspace != NULL) && 894 (grayscale || (!greenOffset && !blueOffset))) { 895 /* Generate monochrome JPEG file if source block is grayscale. */ 896 jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); 897 } 898 899 jpeg_start_compress(cinfo, TRUE); 900 901 /* note: we assume libjpeg is configured for standard RGB pixel order. */ 902 if ((greenOffset == 1) && (blueOffset == 2) 903 && (blockPtr->pixelSize == 3)) { 904 /* No need to reformat pixels before passing data to libjpeg */ 905 for (h = blockPtr->height; h > 0; h--) { 906 row_pointer[0] = (JSAMPROW) pixLinePtr; 907 jpeg_write_scanlines(cinfo, row_pointer, 1); 908 pixLinePtr += blockPtr->pitch; 909 } 910 } else { 911 /* Must convert data format. Create a one-scanline work buffer. */ 912 buffer = (*cinfo->mem->alloc_sarray) 913 ((j_common_ptr) cinfo, JPOOL_IMAGE, 914 cinfo->image_width * cinfo->input_components, 1); 915 for (h = blockPtr->height; h > 0; h--) { 916 pixelPtr = pixLinePtr; 917 bufferPtr = buffer[0]; 918 for (w = blockPtr->width; w > 0; w--) { 919 if (alphaOffset && !pixelPtr[alphaOffset]) { 920 /* if pixel is transparant, better use gray 921 * than the default black. 922 */ 923 *bufferPtr++ = 0xd9; 924 *bufferPtr++ = 0xd9; 925 *bufferPtr++ = 0xd9; 926 } else { 927 *bufferPtr++ = pixelPtr[0]; 928 *bufferPtr++ = pixelPtr[greenOffset]; 929 *bufferPtr++ = pixelPtr[blueOffset]; 930 } 931 pixelPtr += blockPtr->pixelSize; 932 } 933 jpeg_write_scanlines(cinfo, buffer, 1); 934 pixLinePtr += blockPtr->pitch; 935 } 936 } 937 938 jpeg_finish_compress(cinfo); 939 return TCL_OK; 940} 941 942/* 943 * libjpeg source manager for reading from base64-encoded strings 944 * and from Tcl_Channels. 945 */ 946 947static void 948my_jpeg_obj_src (cinfo, dataObj) 949 j_decompress_ptr cinfo; 950 Tcl_Obj *dataObj; 951{ 952 src_ptr src; 953 954 src = (src_ptr) 955 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, 956 sizeof(struct source_mgr)); 957 cinfo->src = (struct jpeg_source_mgr *) src; 958 959 src->pub.init_source = dummy_source; 960 src->pub.fill_input_buffer = fill_input_buffer; 961 src->pub.skip_input_data = skip_input_data; 962 src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ 963 src->pub.term_source = dummy_source; 964 965 tkimg_ReadInit(dataObj, '\377', &src->handle); 966 967 src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ 968 src->pub.next_input_byte = NULL; /* until buffer loaded */ 969} 970 971static boolean 972fill_input_buffer(cinfo) 973 j_decompress_ptr cinfo; 974{ 975 src_ptr src = (src_ptr) cinfo->src; 976 int nbytes; 977 978 nbytes = tkimg_Read(&src->handle, (char *) src->buffer, STRING_BUF_SIZE); 979 980 if (nbytes <= 0) { 981 /* Insert a fake EOI marker */ 982 src->buffer[0] = (JOCTET) 0xFF; 983 src->buffer[1] = (JOCTET) JPEG_EOI; 984 nbytes = 2; 985 } 986 987 src->pub.next_input_byte = src->buffer; 988 src->pub.bytes_in_buffer = nbytes; 989 990 return TRUE; 991} 992 993static void 994skip_input_data(cinfo, num_bytes) 995 j_decompress_ptr cinfo; 996 long num_bytes; 997{ 998 src_ptr src = (src_ptr) cinfo->src; 999 1000 if (num_bytes > 0) { 1001 while (num_bytes > (long) src->pub.bytes_in_buffer) { 1002 num_bytes -= (long) src->pub.bytes_in_buffer; 1003 fill_input_buffer(cinfo); 1004 } 1005 src->pub.next_input_byte += (size_t) num_bytes; 1006 src->pub.bytes_in_buffer -= (size_t) num_bytes; 1007 } 1008} 1009 1010static void 1011dummy_source(cinfo) 1012 j_decompress_ptr cinfo; 1013{ 1014 /* no work necessary here */ 1015} 1016 1017/* 1018 * libjpeg source manager for reading from channels. 1019 */ 1020static void 1021my_jpeg_channel_src (cinfo, chan) 1022 j_decompress_ptr cinfo; 1023 Tcl_Channel chan; 1024{ 1025 src_ptr src; 1026 1027 src = (src_ptr) 1028 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, 1029 sizeof(struct source_mgr)); 1030 cinfo->src = (struct jpeg_source_mgr *) src; 1031 1032 src->pub.init_source = dummy_source; 1033 src->pub.fill_input_buffer = fill_input_buffer; 1034 src->pub.skip_input_data = skip_input_data; 1035 src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ 1036 src->pub.term_source = dummy_source; 1037 1038 src->handle.data = (char *) chan; 1039 src->handle.state = IMG_CHAN; 1040 1041 src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ 1042 src->pub.next_input_byte = NULL; /* until buffer loaded */ 1043} 1044 1045 1046/* 1047 * libjpeg destination manager for writing to base64-encoded strings 1048 * and Tcl_Channel's. 1049 */ 1050 1051static void 1052my_jpeg_string_dest (cinfo, dstring) 1053 j_compress_ptr cinfo; 1054 Tcl_DString* dstring; 1055{ 1056 dest_ptr dest; 1057 1058 if (cinfo->dest == NULL) { /* first time for this JPEG object? */ 1059 cinfo->dest = (struct jpeg_destination_mgr *) 1060 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, 1061 sizeof(struct destination_mgr)); 1062 } 1063 1064 dest = (dest_ptr) cinfo->dest; 1065 dest->pub.init_destination = my_init_destination; 1066 dest->pub.empty_output_buffer = my_empty_output_buffer; 1067 dest->pub.term_destination = my_term_destination; 1068 Tcl_DStringSetLength(dstring, dstring->spaceAvl); 1069 dest->handle.buffer = dstring; 1070 dest->handle.data = Tcl_DStringValue(dstring); 1071 dest->handle.state = 0; 1072 dest->handle.length = 0; 1073} 1074 1075static void 1076my_jpeg_channel_dest (cinfo, chan) 1077 j_compress_ptr cinfo; 1078 Tcl_Channel chan; 1079{ 1080 dest_ptr dest; 1081 1082 if (cinfo->dest == NULL) { /* first time for this JPEG object? */ 1083 cinfo->dest = (struct jpeg_destination_mgr *) 1084 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, 1085 sizeof(struct destination_mgr)); 1086 } 1087 1088 dest = (dest_ptr) cinfo->dest; 1089 dest->pub.init_destination = my_init_destination; 1090 dest->pub.empty_output_buffer = my_empty_output_buffer; 1091 dest->pub.term_destination = my_term_destination; 1092 dest->handle.data = (char *) chan; 1093 dest->handle.state = IMG_CHAN; 1094} 1095 1096static void 1097my_init_destination (cinfo) 1098 j_compress_ptr cinfo; 1099{ 1100 dest_ptr dest = (dest_ptr) cinfo->dest; 1101 dest->pub.next_output_byte = dest->buffer; 1102 dest->pub.free_in_buffer = STRING_BUF_SIZE; 1103} 1104 1105static boolean 1106my_empty_output_buffer (cinfo) 1107 j_compress_ptr cinfo; 1108{ 1109 dest_ptr dest = (dest_ptr) cinfo->dest; 1110 if (tkimg_Write(&dest->handle, (char *) dest->buffer, STRING_BUF_SIZE) 1111 != STRING_BUF_SIZE) 1112 ERREXIT(cinfo, JERR_FILE_WRITE); 1113 1114 dest->pub.next_output_byte = dest->buffer; 1115 dest->pub.free_in_buffer = STRING_BUF_SIZE; 1116 1117 return TRUE; 1118} 1119 1120static void 1121my_term_destination (cinfo) 1122 j_compress_ptr cinfo; 1123{ 1124 dest_ptr dest = (dest_ptr) cinfo->dest; 1125 int datacount = STRING_BUF_SIZE - (int) dest->pub.free_in_buffer; 1126 1127 /* Write any data remaining in the buffer */ 1128 if (datacount > 0) { 1129 if (tkimg_Write(&dest->handle, (char *) dest->buffer, datacount) 1130 != datacount) 1131 ERREXIT(cinfo, JERR_FILE_WRITE); 1132 } 1133 /* Empty any partial-byte from the base64 encoder */ 1134 tkimg_Putc(IMG_DONE, &dest->handle); 1135} 1136 1137 1138/* 1139 * Error handler to replace (or extend, really) libjpeg's default handler 1140 */ 1141 1142static void 1143my_error_exit (cinfo) 1144 j_common_ptr cinfo; 1145{ 1146 struct my_error_mgr *myerr = (struct my_error_mgr *) cinfo->err; 1147 /* Exit back to outer level */ 1148 longjmp(myerr->setjmp_buffer, 1); 1149} 1150 1151static void 1152append_jpeg_message (interp, cinfo) 1153 Tcl_Interp *interp; 1154 j_common_ptr cinfo; 1155{ 1156 /* Append libjpeg error message to interp->result */ 1157 char buffer[JMSG_LENGTH_MAX]; 1158 (*cinfo->err->format_message) (cinfo, buffer); 1159 Tcl_AppendResult(interp, buffer, (char *) NULL); 1160} 1161 1162static void 1163my_output_message (cinfo) 1164 j_common_ptr cinfo; 1165{ 1166 /* Override libjpeg's output_message to do nothing. 1167 * This ensures that warning messages will not appear on stderr, 1168 * even for a corrupted JPEG file. Too bad there's no way 1169 * to report a "warning" message to the calling Tcl script. 1170 */ 1171} 1172