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