1/* STARTHEADER 2 * 3 * File : ppm.c 4 * 5 * Author : Paul Obermeier (paul@poSoft.de) 6 * 7 * Date : Mon Jan 22 21:32:48 CET 2001 8 * 9 * Copyright : (C) 2001-2009 Paul Obermeier 10 * 11 * Description : 12 * 13 * A photo image handler for the PPM/PGM image file formats. 14 * 15 * The following image types are supported: 16 * 17 * Grayscale (PGM): 8-bit and 16-bit, 1 channel per pixel. 18 * True-color (PPM): 8-bit and 16-bit, 3 channels per pixel. 19 * 20 * Both types can be stored as pure ASCII or as binary files. 21 * 22 * List of currently supported features: 23 * 24 * Type | Read | Write | 25 * | -file | -data | -file | -data | 26 * ----------------------------------------------- 27 * PGM 8-bit ASCII | Yes | Yes | No | No | 28 * PGM 8-bit BINARY | Yes | Yes | No | No | 29 * PGM 16-bit ASCII | Yes | Yes | No | No | 30 * PGM 16-bit BINARY | Yes | Yes | No | No | 31 * PPM 8-bit ASCII | Yes | Yes | Yes | Yes | 32 * PPM 8-bit BINARY | Yes | Yes | Yes | Yes | 33 * PPM 16-bit ASCII | Yes | Yes | No | No | 34 * PPM 16-bit BINARY | Yes | Yes | No | No | 35 * 36 * The following format options are available: 37 * 38 * Read image: "ppm -verbose <bool> -gamma <float> 39 * -min <float> -max <float> -scanorder <string>" 40 * Write image: "ppm -verbose <bool> -ascii <bool>" 41 * 42 * -verbose <bool>: If set to true, additional information about the file 43 * format is printed to stdout. Default is false. 44 * -gamma <float>: Specify a gamma correction to be applied when mapping 45 * the input data to 8-bit image values. 46 * Default is 1.0. 47 * -min <float>: Specify the minimum pixel value to be used for mapping 48 * the input data to 8-bit image values. 49 * Default is the minimum value found in the image data. 50 * -max <float>: Specify the maximum pixel value to be used for mapping 51 * the input data to 8-bit image values. 52 * Default is the maximum value found in the image data. 53 * -scanorder <string>: Specify the scanline order of the input image. Convention 54 * is storing scan lines from top to bottom. 55 * Possible values: "TopDown" or "BottomUp". 56 * -ascii <bool>: If set to true, file is written in PPM ASCII format (P3). 57 * Default is false, i.e. write in binary format (P6). 58 * 59 * Notes: 60 * 61 * - Part of this code was taken from Tk's tkImgPPM.c: 62 * 63 * >> tkImgPPM.c -- 64 * >> 65 * >> A photo image file handler for PPM (Portable PixMap) files. 66 * >> 67 * >> Copyright (c) 1994 The Australian National University. 68 * >> Copyright (c) 1994-1997 Sun Microsystems, Inc. 69 * >> 70 * >> See the file "license.terms" for information on usage and redistribution 71 * >> of this file, and for a DISCLAIMER OF ALL WARRANTIES. 72 * >> 73 * >> Author: Paul Mackerras (paulus@cs.anu.edu.au), 74 * >> Department of Computer Science, 75 * >> Australian National University. 76 * 77 * ENDHEADER 78 * 79 * $Id: ppm.c 298 2010-08-28 12:56:41Z obermeier $ 80 * 81 */ 82 83#include <stdlib.h> 84#include <math.h> 85 86/* 87 * Generic initialization code, parameterized via CPACKAGE and PACKAGE. 88 */ 89 90#include "init.c" 91 92 93/* 94#define DEBUG_LOCAL 95*/ 96 97/* 98 * Define PGM and PPM, i.e. gray images and color images. 99 */ 100 101#define PGM 1 102#define PPM 2 103 104/* Some general defines and typedefs. */ 105#define TRUE 1 106#define FALSE 0 107#define MIN(a,b) ((a)<(b)? (a): (b)) 108#define MAX(a,b) ((a)>(b)? (a): (b)) 109#define BOTTOM_UP 0 110#define TOP_DOWN 1 111 112#define strIntel "Intel" 113#define strMotorola "Motorola" 114#define strTopDown "TopDown" 115#define strBottomUp "BottomUp" 116 117typedef unsigned char Boln; /* Boolean value: TRUE or FALSE */ 118typedef unsigned char UByte; /* Unsigned 8 bit integer */ 119typedef char Byte; /* Signed 8 bit integer */ 120typedef unsigned short UShort; /* Unsigned 16 bit integer */ 121typedef short Short; /* Signed 16 bit integer */ 122typedef int UInt; /* Unsigned 32 bit integer */ 123typedef int Int; /* Signed 32 bit integer */ 124typedef float Float; /* IEEE 32 bit floating point */ 125typedef double Double; /* IEEE 64 bit floating point */ 126 127typedef struct { 128 Float minVal; 129 Float maxVal; 130 Float gamma; 131 Boln verbose; 132 Boln writeAscii; 133 Int scanOrder; 134} FMTOPT; 135 136/* PPM file header structure */ 137typedef struct { 138 Int width; 139 Int height; 140 Int maxVal; 141 Boln isAscii; 142} PPMHEADER; 143 144/* Structure to hold information about the PPM file being processed. */ 145typedef struct { 146 PPMHEADER th; 147 UByte *pixbuf; 148 UShort *ushortBuf; 149 UByte *ubyteBuf; 150} PPMFILE; 151 152#define MAXCHANS 4 153 154#define MINGAMMA 0.01 155#define MAXGAMMA 100.0 156#define GTABSIZE 257 157 158/* Given a pixel value in Float format, "valIn", and a gamma-correction 159 * lookup table, "tab", macro "gcorrectFloat" returns the gamma-corrected 160 * pixel value in "valOut". 161 */ 162 163#define gcorrectFloat(valIn,tab,valOut) \ 164 { \ 165 Int gc_i; \ 166 Float gc_t; \ 167 gc_t = (valIn) * (Float)(GTABSIZE - 2); \ 168 gc_i = (Int)gc_t; \ 169 gc_t -= (Int)gc_i; \ 170 (valOut) = (Float)((tab)[gc_i] * (1.0-gc_t) + (tab)[gc_i+1] * gc_t);\ 171 } 172 173static Boln gtableFloat (Float gamma, Float table[]) 174{ 175 Int i; 176 177 if (gamma < MINGAMMA || gamma > MAXGAMMA) { 178 printf ("Invalid gamma value %f\n", gamma); 179 return FALSE; 180 } 181 for (i = 0; i < GTABSIZE - 1; ++i) { 182 table[i] = (Float)pow((Float)i / (Float)(GTABSIZE - 2), 1.0 / gamma); 183 } 184 table[GTABSIZE - 1] = 1.0; 185 return TRUE; 186} 187 188/* If no gamma correction is needed (i.e. gamma == 1.0), specify NULL for 189 * parameter gtable. 190 */ 191static void UShortGammaUByte (Int n, const UShort shortIn[], 192 const Float gtable[], UByte ubOut[]) 193{ 194 const UShort *src, *stop; 195 Float ftmp; 196 Int itmp; 197 UByte *ubdest; 198 199 src = shortIn; 200 stop = shortIn + n; 201 ubdest = ubOut; 202 203 /* Handle a gamma value of 1.0 (gtable == NULL) as a special case. 204 Quite nice speed improvement for the maybe most used case. */ 205 if (gtable) { 206 while (src < stop) { 207 ftmp = (Float)(*src / 65535.0); 208 ftmp = MAX((Float)0.0, MIN(ftmp, (Float)1.0)); 209 gcorrectFloat(ftmp, gtable, ftmp); 210 itmp = (Int)(ftmp * 255.0 + 0.5); 211 *ubdest = MAX (0, MIN (itmp, 255)); 212 ++ubdest; 213 ++src; 214 } 215 } else { 216 while (src < stop) { 217 itmp = (Int)(*src / 256); 218 *ubdest = MAX (0, MIN (itmp, 255)); 219 ++ubdest; 220 ++src; 221 } 222 } 223 return; 224} 225 226/* This function determines at runtime, whether we are on an Intel system. */ 227 228static int isIntel (void) 229{ 230 unsigned long val = 513; 231 /* On Intel (little-endian) systems this value is equal to "\01\02\00\00". 232 On big-endian systems this value equals "\00\00\02\01" */ 233 return memcmp(&val, "\01\02", 2) == 0; 234} 235 236#define OUT Tcl_WriteChars (outChan, str, -1) 237static void printImgInfo (int width, int height, int maxVal, int isAscii, int nChans, 238 FMTOPT *opts, const char *filename, const char *msg) 239{ 240 Tcl_Channel outChan; 241 char str[256]; 242 243 outChan = Tcl_GetStdChannel (TCL_STDOUT); 244 if (!outChan) { 245 return; 246 } 247 sprintf (str, "%s %s\n", msg, filename); OUT; 248 sprintf (str, "\tSize in pixel : %d x %d\n", width, height); OUT; 249 sprintf (str, "\tMaximum value : %d\n", maxVal); OUT; 250 sprintf (str, "\tNo. of channels : %d\n", nChans); OUT; 251 sprintf (str, "\tGamma correction : %f\n", opts->gamma); OUT; 252 sprintf (str, "\tMinimum map value: %f\n", opts->minVal); OUT; 253 sprintf (str, "\tMaximum map value: %f\n", opts->maxVal); OUT; 254 sprintf (str, "\tVertical encoding: %s\n", opts->scanOrder == TOP_DOWN? 255 strTopDown: strBottomUp); OUT; 256 sprintf (str, "\tAscii format : %s\n", isAscii? "Yes": "No"); OUT; 257 sprintf (str, "\tHost byte order : %s\n", isIntel ()? strIntel: strMotorola); OUT; 258 Tcl_Flush (outChan); 259} 260#undef OUT 261 262static void ppmClose (PPMFILE *tf) 263{ 264 if (tf->pixbuf) ckfree ((char *)tf->pixbuf); 265 if (tf->ushortBuf) ckfree ((char *)tf->ushortBuf); 266 if (tf->ubyteBuf) ckfree ((char *)tf->ubyteBuf); 267 return; 268} 269 270static int getNextVal (Tcl_Interp *interp, tkimg_MFile *handle, UInt *val) 271{ 272 char c, buf[TCL_INTEGER_SPACE]; 273 UInt i; 274 275 /* First skip leading whitespaces. */ 276 while (tkimg_Read (handle, &c, 1) == 1) { 277 if (!isspace (c)) { 278 break; 279 } 280 } 281 282 buf[0] = c; 283 i = 1; 284 while (tkimg_Read (handle, &c, 1) == 1 && i < TCL_INTEGER_SPACE) { 285 if (isspace (c)) { 286 buf[i] = '\0'; 287 sscanf (buf, "%u", val); 288 return TRUE; 289 } 290 buf[i++] = c; 291 } 292 Tcl_AppendResult (interp, "cannot read next ASCII value", (char *) NULL); 293 return FALSE; 294} 295 296static Boln readUShortRow (Tcl_Interp *interp, tkimg_MFile *handle, UShort *pixels, 297 Int nShorts, char *buf, Boln swapBytes, Boln isAscii) 298{ 299 UShort *mPtr = pixels; 300 char *bufPtr = buf; 301 UInt i, val; 302 303 #ifdef DEBUG_LOCAL 304 printf ("Reading %d UShorts\n", nShorts); 305 #endif 306 if (isAscii) { 307 for (i=0; i<nShorts; i++) { 308 if (!getNextVal (interp, handle, &val)) { 309 return FALSE; 310 } 311 pixels[i] = (UShort) val; 312 } 313 return TRUE; 314 } 315 316 if (2 * nShorts != tkimg_Read (handle, buf, 2 * nShorts)) 317 return FALSE; 318 319 if (swapBytes) { 320 for (i=0; i<nShorts; i++) { 321 ((char *)mPtr)[0] = bufPtr[1]; 322 ((char *)mPtr)[1] = bufPtr[0]; 323 mPtr++; 324 bufPtr += 2; 325 } 326 } else { 327 for (i=0; i<nShorts; i++) { 328 ((char *)mPtr)[0] = bufPtr[0]; 329 ((char *)mPtr)[1] = bufPtr[1]; 330 mPtr++; 331 bufPtr += 2; 332 } 333 } 334 return TRUE; 335} 336 337static Boln readUByteRow (Tcl_Interp *interp, tkimg_MFile *handle, UByte *pixels, 338 Int nBytes, char *buf, Boln swapBytes, Boln isAscii) 339{ 340 UByte *mPtr = pixels; 341 char *bufPtr = buf; 342 UInt i, val; 343 344 #ifdef DEBUG_LOCAL 345 printf ("Reading %d UBytes\n", nBytes); 346 #endif 347 if (isAscii) { 348 for (i=0; i<nBytes; i++) { 349 if (!getNextVal (interp, handle, &val)) { 350 return FALSE; 351 } 352 pixels[i] = (UByte) val; 353 } 354 return TRUE; 355 } 356 357 if (nBytes != tkimg_Read (handle, buf, nBytes)) 358 return FALSE; 359 360 for (i=0; i<nBytes; i++) { 361 ((char *)mPtr)[0] = bufPtr[0]; 362 mPtr++; 363 bufPtr += 1; 364 } 365 return TRUE; 366} 367 368static Boln readUShortFile (Tcl_Interp *interp, tkimg_MFile *handle, UShort *buf, Int width, Int height, 369 Int nchan, Boln swapBytes, Boln isAscii, Boln verbose, 370 Float minVals[], Float maxVals[]) 371{ 372 Int x, y, c; 373 UShort *bufPtr = buf; 374 char *line; 375 376 #ifdef DEBUG_LOCAL 377 printf ("readUShortFile: Width=%d Height=%d nchan=%d swapBytes=%s\n", 378 width, height, nchan, swapBytes? "yes": "no"); 379 #endif 380 for (c=0; c<nchan; c++) { 381 minVals[c] = (Float)1.0E30; 382 maxVals[c] = (Float)-1.0E30; 383 } 384 line = ckalloc (sizeof (UShort) * nchan * width); 385 386 for (y=0; y<height; y++) { 387 if (!readUShortRow (interp, handle, bufPtr, nchan * width, line, swapBytes, isAscii)) 388 return FALSE; 389 for (x=0; x<width; x++) { 390 for (c=0; c<nchan; c++) { 391 if (*bufPtr > maxVals[c]) maxVals[c] = *bufPtr; 392 if (*bufPtr < minVals[c]) minVals[c] = *bufPtr; 393 bufPtr++; 394 } 395 } 396 } 397 if (verbose) { 398 printf ("\tMinimum pixel values :"); 399 for (c=0; c<nchan; c++) { 400 printf (" %d", (UShort)minVals[c]); 401 } 402 printf ("\n"); 403 printf ("\tMaximum pixel values :"); 404 for (c=0; c<nchan; c++) { 405 printf (" %d", (UShort)maxVals[c]); 406 } 407 printf ("\n"); 408 fflush (stdout); 409 } 410 ckfree (line); 411 return TRUE; 412} 413 414static Boln readUByteFile (Tcl_Interp *interp, tkimg_MFile *handle, UByte *buf, Int width, Int height, 415 Int nchan, Boln swapBytes, Boln isAscii, Boln verbose, 416 Float minVals[], Float maxVals[]) 417{ 418 Int x, y, c; 419 UByte *bufPtr = buf; 420 char *line; 421 422 #ifdef DEBUG_LOCAL 423 printf ("readUByteFile: Width=%d Height=%d nchan=%d swapBytes=%s\n", 424 width, height, nchan, swapBytes? "yes": "no"); 425 #endif 426 for (c=0; c<nchan; c++) { 427 minVals[c] = (Float)1.0E30; 428 maxVals[c] = (Float)-1.0E30; 429 } 430 line = ckalloc (sizeof (UByte) * nchan * width); 431 432 for (y=0; y<height; y++) { 433 if (!readUByteRow (interp, handle, bufPtr, nchan * width, line, swapBytes, isAscii)) 434 return FALSE; 435 for (x=0; x<width; x++) { 436 for (c=0; c<nchan; c++) { 437 if (*bufPtr > maxVals[c]) maxVals[c] = *bufPtr; 438 if (*bufPtr < minVals[c]) minVals[c] = *bufPtr; 439 bufPtr++; 440 } 441 } 442 } 443 if (verbose) { 444 printf ("\tMinimum pixel values :"); 445 for (c=0; c<nchan; c++) { 446 printf (" %d", (UByte)minVals[c]); 447 } 448 printf ("\n"); 449 printf ("\tMaximum pixel values :"); 450 for (c=0; c<nchan; c++) { 451 printf (" %d", (UByte)maxVals[c]); 452 } 453 printf ("\n"); 454 fflush (stdout); 455 } 456 ckfree (line); 457 return TRUE; 458} 459 460static Boln remapUShortValues (UShort *buf, Int width, Int height, Int nchan, 461 Float minVals[], Float maxVals[]) 462{ 463 Int x, y, c; 464 UShort *bufPtr = buf; 465 Float m[MAXCHANS], t[MAXCHANS]; 466 467 for (c=0; c<nchan; c++) { 468 m[c] = (Float)((65535.0 - 0.0) / (maxVals[c] - minVals[c])); 469 t[c] = (Float)(0.0 - m[c] * minVals[c]); 470 } 471 for (y=0; y<height; y++) { 472 for (x=0; x<width; x++) { 473 for (c=0; c<nchan; c++) { 474 *bufPtr = (UShort)(*bufPtr * m[c] + t[c]); 475 bufPtr++; 476 } 477 } 478 } 479 return TRUE; 480} 481 482static int ParseFormatOpts (interp, format, opts) 483 Tcl_Interp *interp; 484 Tcl_Obj *format; 485 FMTOPT *opts; 486{ 487 static const char *const ppmOptions[] = { 488 "-verbose", "-min", "-max", "-gamma", "-scanorder", "-ascii" 489 }; 490 int objc, length, c, i, index; 491 Tcl_Obj **objv; 492 const char *verboseStr, *minStr, *maxStr, *gammaStr, *scanorderStr, *asciiStr; 493 494 /* Initialize format options with default values. */ 495 verboseStr = "0"; 496 minStr = "0.0"; 497 maxStr = "0.0"; 498 gammaStr = "1.0"; 499 scanorderStr = strTopDown; 500 asciiStr = "0"; 501 502 if (tkimg_ListObjGetElements (interp, format, &objc, &objv) != TCL_OK) 503 return TCL_ERROR; 504 if (objc) { 505 for (i=1; i<objc; i++) { 506 if (Tcl_GetIndexFromObj(interp, objv[i], (CONST84 char *CONST86 *)ppmOptions, 507 "format option", 0, &index) != TCL_OK) { 508 return TCL_ERROR; 509 } 510 if (++i >= objc) { 511 Tcl_AppendResult (interp, "No value for option \"", 512 Tcl_GetStringFromObj (objv[--i], (int *) NULL), 513 "\"", (char *) NULL); 514 return TCL_ERROR; 515 } 516 switch(index) { 517 case 0: 518 verboseStr = Tcl_GetStringFromObj(objv[i], (int *) NULL); 519 break; 520 case 1: 521 minStr = Tcl_GetStringFromObj(objv[i], (int *) NULL); 522 break; 523 case 2: 524 maxStr = Tcl_GetStringFromObj(objv[i], (int *) NULL); 525 break; 526 case 3: 527 gammaStr = Tcl_GetStringFromObj(objv[i], (int *) NULL); 528 break; 529 case 4: 530 scanorderStr = Tcl_GetStringFromObj(objv[i], (int *) NULL); 531 break; 532 case 5: 533 asciiStr = Tcl_GetStringFromObj(objv[i], (int *) NULL); 534 break; 535 } 536 } 537 } 538 539 opts->minVal = (Float)atof(minStr); 540 opts->maxVal = (Float)atof(maxStr); 541 opts->gamma = (Float)atof(gammaStr); 542 543 c = verboseStr[0]; length = strlen (verboseStr); 544 if (!strncmp (verboseStr, "1", length) || \ 545 !strncmp (verboseStr, "true", length) || \ 546 !strncmp (verboseStr, "on", length)) { 547 opts->verbose = 1; 548 } else if (!strncmp (verboseStr, "0", length) || \ 549 !strncmp (verboseStr, "false", length) || \ 550 !strncmp (verboseStr, "off", length)) { 551 opts->verbose = 0; 552 } else { 553 Tcl_AppendResult (interp, "invalid verbose mode \"", verboseStr, 554 "\": should be 1 or 0, on or off, true or false", 555 (char *) NULL); 556 return TCL_ERROR; 557 } 558 559 c = scanorderStr[0]; length = strlen (scanorderStr); 560 if (!strncmp (scanorderStr, strTopDown, length)) { 561 opts->scanOrder = TOP_DOWN; 562 } else if (!strncmp (scanorderStr, strBottomUp, length)) { 563 opts->scanOrder = BOTTOM_UP; 564 } else { 565 Tcl_AppendResult (interp, "invalid scanline order \"", scanorderStr, 566 "\": should be TopDown or BottomUp", 567 (char *) NULL); 568 return TCL_ERROR; 569 } 570 571 c = asciiStr[0]; length = strlen (asciiStr); 572 if (!strncmp (asciiStr, "1", length) || \ 573 !strncmp (asciiStr, "true", length) || \ 574 !strncmp (asciiStr, "on", length)) { 575 opts->writeAscii = 1; 576 } else if (!strncmp (asciiStr, "0", length) || \ 577 !strncmp (asciiStr, "false", length) || \ 578 !strncmp (asciiStr, "off", length)) { 579 opts->writeAscii = 0; 580 } else { 581 Tcl_AppendResult (interp, "invalid ascii mode \"", asciiStr, 582 "\": should be 1 or 0, on or off, true or false", 583 (char *) NULL); 584 return TCL_ERROR; 585 } 586 587 return TCL_OK; 588} 589 590/* 591 * Prototypes for local procedures defined in this file: 592 */ 593 594static int CommonMatch (tkimg_MFile *handle, int *widthPtr, 595 int *heightPtr, int *maxIntensityPtr); 596static int CommonRead (Tcl_Interp *interp, tkimg_MFile *handle, 597 const char *filename, Tcl_Obj *format, 598 Tk_PhotoHandle imageHandle, int destX, int destY, 599 int width, int height, int srcX, int srcY); 600static int CommonWrite (Tcl_Interp *interp, 601 const char *filename, Tcl_Obj *format, 602 tkimg_MFile *handle, Tk_PhotoImageBlock *blockPtr); 603static int ReadPPMFileHeader (tkimg_MFile *handle, int *widthPtr, 604 int *heightPtr, int *maxIntensityPtr, Boln *isAsciiPtr); 605 606 607/* 608 *---------------------------------------------------------------------- 609 * 610 * ChnMatch -- 611 * 612 * This procedure is invoked by the photo image type to see if 613 * a file contains image data in PPM format. 614 * 615 * Results: 616 * The return value is >0 if the first characters in file "f" look 617 * like PPM data, and 0 otherwise. 618 * 619 * Side effects: 620 * The access position in f may change. 621 * 622 *---------------------------------------------------------------------- 623 */ 624 625static int ChnMatch( 626 Tcl_Channel chan, /* The image file, open for reading. */ 627 const char *filename, /* The name of the image file. */ 628 Tcl_Obj *format, /* User-specified format object, or NULL. */ 629 int *widthPtr, /* The dimensions of the image are */ 630 int *heightPtr, /* returned here if the file is a valid 631 * PPM file. */ 632 Tcl_Interp *interp /* Interpreter to use for reporting errors. */ 633) { 634 tkimg_MFile handle; 635 int dummy; 636 637 handle.data = (char *) chan; 638 handle.state = IMG_CHAN; 639 640 return CommonMatch(&handle, widthPtr, heightPtr, &dummy); 641} 642 643static int ObjMatch( 644 Tcl_Obj *data, 645 Tcl_Obj *format, 646 int *widthPtr, 647 int *heightPtr, 648 Tcl_Interp *interp 649) { 650 tkimg_MFile handle; 651 int dummy; 652 653 tkimg_ReadInit(data, 'P', &handle); 654 return CommonMatch(&handle, widthPtr, heightPtr, &dummy); 655} 656 657static int CommonMatch(handle, widthPtr, heightPtr, maxIntensityPtr) 658 tkimg_MFile *handle; 659 int *widthPtr; 660 int *heightPtr; 661 int *maxIntensityPtr; 662{ 663 Boln dummy; 664 return ReadPPMFileHeader(handle, widthPtr, heightPtr, maxIntensityPtr, &dummy); 665} 666 667 668/* 669 *---------------------------------------------------------------------- 670 * 671 * ChnRead -- 672 * 673 * This procedure is called by the photo image type to read 674 * PPM format data from a file and write it into a given 675 * photo image. 676 * 677 * Results: 678 * A standard TCL completion code. If TCL_ERROR is returned 679 * then an error message is left in the interp's result. 680 * 681 * Side effects: 682 * The access position in file f is changed, and new data is 683 * added to the image given by imageHandle. 684 * 685 *---------------------------------------------------------------------- 686 */ 687 688static int ChnRead(interp, chan, filename, format, imageHandle, 689 destX, destY, width, height, srcX, srcY) 690 Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ 691 Tcl_Channel chan; /* The image file, open for reading. */ 692 const char *filename; /* The name of the image file. */ 693 Tcl_Obj *format; /* User-specified format string, or NULL. */ 694 Tk_PhotoHandle imageHandle; /* The photo image to write into. */ 695 int destX, destY; /* Coordinates of top-left pixel in 696 * photo image to be written to. */ 697 int width, height; /* Dimensions of block of photo image to 698 * be written to. */ 699 int srcX, srcY; /* Coordinates of top-left pixel to be used 700 * in image being read. */ 701{ 702 tkimg_MFile handle; 703 704 handle.data = (char *) chan; 705 handle.state = IMG_CHAN; 706 707 return CommonRead (interp, &handle, filename, format, imageHandle, 708 destX, destY, width, height, srcX, srcY); 709} 710 711static int ObjRead (interp, data, format, imageHandle, 712 destX, destY, width, height, srcX, srcY) 713 Tcl_Interp *interp; 714 Tcl_Obj *data; 715 Tcl_Obj *format; 716 Tk_PhotoHandle imageHandle; 717 int destX, destY; 718 int width, height; 719 int srcX, srcY; 720{ 721 tkimg_MFile handle; 722 723 tkimg_ReadInit (data, 'P', &handle); 724 return CommonRead (interp, &handle, "InlineData", format, imageHandle, 725 destX, destY, width, height, srcX, srcY); 726} 727 728static int CommonRead (interp, handle, filename, format, imageHandle, 729 destX, destY, width, height, srcX, srcY) 730 Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ 731 tkimg_MFile *handle; /* The image file, open for reading. */ 732 const char *filename; /* The name of the image file. */ 733 Tcl_Obj *format; /* User-specified format string, or NULL. */ 734 Tk_PhotoHandle imageHandle; /* The photo image to write into. */ 735 int destX, destY; /* Coordinates of top-left pixel in 736 * photo image to be written to. */ 737 int width, height; /* Dimensions of block of photo image to 738 * be written to. */ 739 int srcX, srcY; /* Coordinates of top-left pixel to be used 740 * in image being read. */ 741{ 742 Int fileWidth, fileHeight, maxIntensity; 743 Int x, y, c; 744 int type; 745 Tk_PhotoImageBlock block; 746 FMTOPT opts; 747 PPMFILE tf; 748 Boln swapBytes, isAscii; 749 int stopY, outY; 750 int bytesPerPixel; 751 Float minVals[MAXCHANS], maxVals[MAXCHANS]; 752 UByte *pixbufPtr; 753 UShort *ushortBufPtr; 754 UByte *ubyteBufPtr; 755 Float gtable[GTABSIZE]; 756 757 memset (&tf, 0, sizeof (PPMFILE)); 758 759 swapBytes = isIntel (); 760 761 if (ParseFormatOpts (interp, format, &opts) != TCL_OK) { 762 return TCL_ERROR; 763 } 764 765 type = ReadPPMFileHeader (handle, &fileWidth, &fileHeight, &maxIntensity, &isAscii); 766 if (type == 0) { 767 Tcl_AppendResult(interp, "couldn't read PPM header from file \"", 768 filename, "\"", NULL); 769 return TCL_ERROR; 770 } 771 772 if ((fileWidth <= 0) || (fileHeight <= 0)) { 773 Tcl_AppendResult(interp, "PPM image file \"", filename, 774 "\" has dimension(s) <= 0", (char *) NULL); 775 return TCL_ERROR; 776 } 777 if ((maxIntensity <= 0) || (maxIntensity >= 65536)) { 778 char buffer[TCL_INTEGER_SPACE]; 779 780 sprintf(buffer, "%d", maxIntensity); 781 Tcl_AppendResult(interp, "PPM image file \"", filename, 782 "\" has bad maximum intensity value ", buffer, 783 (char *) NULL); 784 return TCL_ERROR; 785 } 786 787 bytesPerPixel = maxIntensity >= 256? 2: 1; 788 789 gtableFloat (opts.gamma, gtable); 790 791 if (opts.verbose) 792 printImgInfo (fileWidth, fileHeight, maxIntensity, isAscii, type==PGM? 1: 3, 793 &opts, filename, "Reading image:"); 794 795 if ((srcX + width) > fileWidth) { 796 width = fileWidth - srcX; 797 } 798 if ((srcY + height) > fileHeight) { 799 height = fileHeight - srcY; 800 } 801 if ((width <= 0) || (height <= 0) 802 || (srcX >= fileWidth) || (srcY >= fileHeight)) { 803 return TCL_OK; 804 } 805 806 if (type == PGM) { 807 block.pixelSize = 1; 808 block.offset[1] = 0; 809 block.offset[2] = 0; 810 } 811 else { 812 block.pixelSize = 3; 813 block.offset[1] = 1; 814 block.offset[2] = 2; 815 } 816 block.offset[3] = block.offset[0] = 0; 817 block.width = width; 818 block.height = 1; 819 block.pitch = block.pixelSize * fileWidth; 820 tf.pixbuf = (UByte *) ckalloc (fileWidth * block.pixelSize); 821 block.pixelPtr = tf.pixbuf + srcX * block.pixelSize; 822 823 switch (bytesPerPixel) { 824 case 2: { 825 tf.ushortBuf = (UShort *)ckalloc (fileWidth*fileHeight*block.pixelSize*sizeof (UShort)); 826 if (!readUShortFile(interp, handle, tf.ushortBuf, fileWidth, fileHeight, block.pixelSize, 827 swapBytes, isAscii, opts.verbose, minVals, maxVals)) { 828 ppmClose (&tf); 829 return TCL_ERROR; 830 } 831 break; 832 } 833 case 1: { 834 tf.ubyteBuf = (UByte *)ckalloc (fileWidth*fileHeight*block.pixelSize*sizeof (UByte)); 835 if (!readUByteFile (interp, handle, tf.ubyteBuf, fileWidth, fileHeight, block.pixelSize, 836 swapBytes, isAscii, opts.verbose, minVals, maxVals)) { 837 ppmClose (&tf); 838 return TCL_ERROR; 839 } 840 break; 841 } 842 } 843 844 if (opts.minVal != 0.0 || opts.maxVal != 0.0) { 845 for (c=0; c<block.pixelSize; c++) { 846 minVals[c] = opts.minVal; 847 maxVals[c] = opts.maxVal; 848 } 849 } 850 switch (bytesPerPixel) { 851 case 2: { 852 remapUShortValues (tf.ushortBuf, fileWidth, fileHeight, block.pixelSize, 853 minVals, maxVals); 854 break; 855 } 856 } 857 858 if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) { 859 ppmClose (&tf); 860 return TCL_ERROR; 861 } 862 863 stopY = srcY + height; 864 outY = destY; 865 866 for (y=0; y<stopY; y++) { 867 pixbufPtr = tf.pixbuf; 868 switch (bytesPerPixel) { 869 case 2: { 870 if (opts.scanOrder == BOTTOM_UP) { 871 ushortBufPtr = tf.ushortBuf + (fileHeight -1 - y) * fileWidth * block.pixelSize; 872 } else { 873 ushortBufPtr = tf.ushortBuf + y * fileWidth * block.pixelSize; 874 } 875 UShortGammaUByte (fileWidth * block.pixelSize, ushortBufPtr, 876 opts.gamma != 1.0? gtable: NULL, pixbufPtr); 877 ushortBufPtr += fileWidth * block.pixelSize; 878 break; 879 } 880 case 1: { 881 if (opts.scanOrder == BOTTOM_UP) { 882 ubyteBufPtr = tf.ubyteBuf + (fileHeight -1 - y) * fileWidth * block.pixelSize; 883 } else { 884 ubyteBufPtr = tf.ubyteBuf + y * fileWidth * block.pixelSize; 885 } 886 for (x=0; x<fileWidth * block.pixelSize; x++) { 887 pixbufPtr[x] = ubyteBufPtr[x]; 888 } 889 ubyteBufPtr += fileWidth * block.pixelSize; 890 break; 891 } 892 } 893 if (y >= srcY) { 894 if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, 895 width, 1, 896 block.offset[3]? 897 TK_PHOTO_COMPOSITE_SET: 898 TK_PHOTO_COMPOSITE_OVERLAY) == TCL_ERROR) { 899 ppmClose (&tf); 900 return TCL_ERROR; 901 } 902 outY++; 903 } 904 } 905 ppmClose (&tf); 906 return TCL_OK; 907} 908 909 910/* 911 *---------------------------------------------------------------------- 912 * 913 * ChnWrite -- 914 * 915 * This procedure is invoked to write image data to a file in PPM 916 * format. 917 * 918 * Results: 919 * A standard TCL completion code. If TCL_ERROR is returned 920 * then an error message is left in the interp's result. 921 * 922 * Side effects: 923 * Data is written to the file given by "filename". 924 * 925 *---------------------------------------------------------------------- 926 */ 927 928static int ChnWrite (interp, filename, format, blockPtr) 929 Tcl_Interp *interp; 930 const char *filename; 931 Tcl_Obj *format; 932 Tk_PhotoImageBlock *blockPtr; 933{ 934 Tcl_Channel chan; 935 tkimg_MFile handle; 936 int result; 937 938 chan = tkimg_OpenFileChannel (interp, filename, 0644); 939 if (!chan) { 940 return TCL_ERROR; 941 } 942 943 handle.data = (char *) chan; 944 handle.state = IMG_CHAN; 945 946 result = CommonWrite (interp, filename, format, &handle, blockPtr); 947 if (Tcl_Close(interp, chan) == TCL_ERROR) { 948 return TCL_ERROR; 949 } 950 return result; 951} 952 953static int StringWrite( 954 Tcl_Interp *interp, 955 Tcl_Obj *format, 956 Tk_PhotoImageBlock *blockPtr 957) { 958 tkimg_MFile handle; 959 int result; 960 Tcl_DString data; 961 962 Tcl_DStringInit(&data); 963 tkimg_WriteInit (&data, &handle); 964 result = CommonWrite (interp, "InlineData", format, &handle, blockPtr); 965 tkimg_Putc(IMG_DONE, &handle); 966 967 if (result == TCL_OK) { 968 Tcl_DStringResult(interp, &data); 969 } else { 970 Tcl_DStringFree(&data); 971 } 972 return result; 973} 974 975static int writeAsciiRow (tkimg_MFile *handle, const unsigned char *scanline, int nBytes) 976{ 977 int i; 978 char buf[TCL_INTEGER_SPACE]; 979 980 for (i=0; i<nBytes; i++) { 981 sprintf (buf, "%d\n", scanline[i]); 982 if (tkimg_Write(handle, buf, strlen(buf)) != (int)strlen(buf)) { 983 return i; 984 } 985 } 986 return nBytes; 987} 988 989static int CommonWrite (interp, filename, format, handle, blockPtr) 990 Tcl_Interp *interp; 991 const char *filename; 992 Tcl_Obj *format; 993 tkimg_MFile *handle; 994 Tk_PhotoImageBlock *blockPtr; 995{ 996 int w, h; 997 int redOff, greenOff, blueOff, nBytes; 998 unsigned char *scanline, *scanlinePtr; 999 unsigned char *pixelPtr, *pixLinePtr; 1000 char header[16 + TCL_INTEGER_SPACE * 2]; 1001 FMTOPT opts; 1002 1003 if (ParseFormatOpts (interp, format, &opts) != TCL_OK) { 1004 return TCL_ERROR; 1005 } 1006 1007 sprintf(header, "P%d\n%d %d\n255\n", opts.writeAscii? 3: 6, 1008 blockPtr->width, blockPtr->height); 1009 if (tkimg_Write(handle, header, strlen(header)) != (int)strlen(header)) { 1010 goto writeerror; 1011 } 1012 1013 pixLinePtr = blockPtr->pixelPtr + blockPtr->offset[0]; 1014 redOff = 0; 1015 greenOff = blockPtr->offset[1] - blockPtr->offset[0]; 1016 blueOff = blockPtr->offset[2] - blockPtr->offset[0]; 1017 1018 nBytes = blockPtr->width * 3; /* Only RGB images allowed. */ 1019 scanline = (unsigned char *) ckalloc((unsigned) nBytes); 1020 for (h = blockPtr->height; h > 0; h--) { 1021 pixelPtr = pixLinePtr; 1022 scanlinePtr = scanline; 1023 for (w = blockPtr->width; w > 0; w--) { 1024 *(scanlinePtr++) = pixelPtr[redOff]; 1025 *(scanlinePtr++) = pixelPtr[greenOff]; 1026 *(scanlinePtr++) = pixelPtr[blueOff]; 1027 pixelPtr += blockPtr->pixelSize; 1028 } 1029 if (opts.writeAscii) { 1030 if (writeAsciiRow (handle, scanline, nBytes) != nBytes) { 1031 goto writeerror; 1032 } 1033 } else { 1034 if (tkimg_Write(handle, (char *) scanline, nBytes) != nBytes) { 1035 goto writeerror; 1036 } 1037 } 1038 pixLinePtr += blockPtr->pitch; 1039 } 1040 ckfree ((char *) scanline); 1041 return TCL_OK; 1042 1043 writeerror: 1044 Tcl_AppendResult(interp, "Error writing \"", filename, "\": ", 1045 (char *) NULL); 1046 return TCL_ERROR; 1047} 1048 1049 1050/* 1051 *---------------------------------------------------------------------- 1052 * 1053 * ReadPPMFileHeader -- 1054 * 1055 * This procedure reads the PPM header from the beginning of a 1056 * PPM file and returns information from the header. 1057 * 1058 * Results: 1059 * The return value is PGM if file "f" appears to start with 1060 * a valid PGM header, PPM if "f" appears to start with a valid 1061 * PPM header, and 0 otherwise. If the header is valid, 1062 * then *widthPtr and *heightPtr are modified to hold the 1063 * dimensions of the image and *maxIntensityPtr is modified to 1064 * hold the value of a "fully on" intensity value. 1065 * 1066 * Side effects: 1067 * The access position in f advances. 1068 * 1069 *---------------------------------------------------------------------- 1070 */ 1071 1072static int 1073ReadPPMFileHeader (handle, widthPtr, heightPtr, maxIntensityPtr, isAsciiPtr) 1074 tkimg_MFile *handle; /* Image file to read the header from */ 1075 int *widthPtr, *heightPtr; /* The dimensions of the image are 1076 * returned here. */ 1077 int *maxIntensityPtr; /* The maximum intensity value for 1078 * the image is stored here. */ 1079 Boln *isAsciiPtr; 1080{ 1081#define BUFFER_SIZE 1000 1082 char buffer[BUFFER_SIZE]; 1083 int i, numFields; 1084 int type = 0; 1085 char c; 1086 1087 /* 1088 * Read 4 space-separated fields from the file, ignoring 1089 * comments (any line that starts with "#"). 1090 */ 1091 1092 if (tkimg_Read(handle, &c, 1) != 1) { 1093 return 0; 1094 } 1095 i = 0; 1096 for (numFields = 0; numFields < 4; numFields++) { 1097 /* 1098 * Skip comments and white space. 1099 */ 1100 1101 while (1) { 1102 while (isspace((unsigned char)c)) { 1103 if (tkimg_Read(handle, &c, 1) != 1) { 1104 return 0; 1105 } 1106 } 1107 if (c != '#') { 1108 break; 1109 } 1110 do { 1111 if (tkimg_Read(handle, &c, 1) != 1) { 1112 return 0; 1113 } 1114 } while (c != '\n'); 1115 } 1116 1117 /* 1118 * Read a field (everything up to the next white space). 1119 */ 1120 1121 while (!isspace((unsigned char)c)) { 1122 if (i < (BUFFER_SIZE-2)) { 1123 buffer[i] = c; 1124 i++; 1125 } 1126 if (tkimg_Read(handle, &c, 1) != 1) { 1127 goto done; 1128 } 1129 } 1130 if (i < (BUFFER_SIZE-1)) { 1131 buffer[i] = ' '; 1132 i++; 1133 } 1134 } 1135 done: 1136 buffer[i] = 0; 1137 1138 /* 1139 * Parse the fields, which are: id, width, height, maxIntensity. 1140 */ 1141 1142 *isAsciiPtr = 0; 1143 if (strncmp(buffer, "P6 ", 3) == 0) { 1144 type = PPM; 1145 } else if (strncmp(buffer, "P3 ", 3) == 0) { 1146 type = PPM; 1147 *isAsciiPtr = 1; 1148 } else if (strncmp(buffer, "P5 ", 3) == 0) { 1149 type = PGM; 1150 } else if (strncmp(buffer, "P2 ", 3) == 0) { 1151 type = PGM; 1152 *isAsciiPtr = 1; 1153 } else { 1154 return 0; 1155 } 1156 if (sscanf(buffer+3, "%d %d %d", widthPtr, heightPtr, maxIntensityPtr) 1157 != 3) { 1158 return 0; 1159 } 1160 return type; 1161} 1162