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