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