1 /* STARTHEADER
2  *
3  * File :       raw.c
4  *
5  * Author :     Paul Obermeier (paul@poSoft.de)
6  *
7  * Date :       2001 / 02 / 21
8  *
9  * Copyright :  (C) 2001-2019 Paul Obermeier
10  *
11  * Description :
12  *
13  * A photo image handler for raw matrix data interpreted as image files.
14  *
15  * The following image types are currently supported:
16  *
17  * Grayscale image:  1 channel  of 32-bit floating point   values.
18  *                   1 channel  of 16-bit unsigned integer values.
19  *                   1 channel  of  8-bit unsigned integer values.
20  * True-color image: 3 channels of 32-bit floating point   values.
21  *                   3 channels of 16-bit unsigned integer values.
22  *                   3 channels of  8-bit unsigned integer values.
23  *
24  * List of currently supported features:
25  *
26  * Type   |     Read      |     Write     |
27  *        | -file | -data | -file | -data |
28  * ----------------------------------------
29  * Gray   | Yes   | Yes   | Yes   | Yes   |
30  * RGB    | Yes   | Yes   | Yes   | Yes   |
31  *
32  * There are 2 supported file formats:
33  * One with the pure raw data only, the other with a 7 line ASCII header of the
34  * following form:
35  *
36  *     Magic=RAW\n              File format identifier. Fixed value.
37  *     Width=128\n              Image width in pixels.
38  *     Height=128\n             Image height in pixels.
39  *     NumChan=1\n              Possible values: 1 or 3.
40  *     ByteOrder=Intel\n        Possible values: "Intel" or "Motorola".
41  *     ScanOrder=TopDown\n      Possible values: "TopDown" or "BottomUp".
42  *     PixelType=byte\n         Possible values: "float", "short" or "byte".
43  *
44  * The following format options are available:
45  *
46  * For raw images with header:
47  * Read  RAW image: "raw -useheader true -verbose <bool> -map <enum>
48  *                       -gamma <float> -min <float> -max <float>
49  *                       -cutoff <float> -saturation <float> -printagc <bool>"
50  * Write RAW image: "raw -useheader true -verbose <bool> -nchan <int>
51  *                       -scanorder <string>"
52  *
53  * For raw images without header:
54  * Read  RAW image: "raw -useheader false -verbose <bool> -map <enum>
55  *                       -gamma <float> -min <float> -max <float>
56  *                       -cutoff <float> -saturation <float> -printagc <bool>
57  *                       -nchan <int> -scanorder <string> -byteorder <string>
58  *                       -width <int> -height <int>
59  *                       -pixeltype <string> -uuencode <bool>"
60  * Write RAW image: "raw -useheader false -verbose <bool> -nchan <int>
61  *                       -scanorder <string>"
62  *
63  * -verbose <bool>:     If set to true, additional information about the file
64  *                      format is printed to stdout. Default is false.
65  * -useheader <bool>:   If set to true, use file header information for reading
66  *                      and writing. Default is true.
67  *
68  * -map <enum>:         Specify the mode when mapping the 32 or 16-bit values
69  *                      of the image to 8-bit gray scale values for displaying.
70  *                      Valid mapping mode strings are: none, minmax, agc.
71  *                      Default mode is minmax.
72  *
73  *                      Mode "none":
74  *                      If mapping mode is set to "none", no mapping of input
75  *                      values is done. Use this mode, if the image already
76  *                      contains RGB values in the range of 0 ..255.
77  *                      When using mode "none", no information about the
78  *                      minimum and maximum pixel values is gathered during
79  *                      reading and therefore no verbose output is printed.
80  *                      On the other hand reading the image is faster.
81  *
82  *                      Mode "minmax":
83  *                      "minmax" maps the minimum and maximum values of the image data to
84  *                      256 gray scale values.
85  *
86  *                      Mode "agc":
87  *                      "agc" applies an automatic gain control algorithmn to the
88  *                      image data.
89  *                      Currently implemented for 1-channel 32-bit float images only.
90  * -gamma <float>:      Specify a gamma correction to be applied when mapping
91  *                      the input data to 8-bit image values.
92  *                      Default is 1.0.
93  *                      Valid for mapping modes: minmax, agc
94  * -max <float>:        Specify the maximum pixel value to be used for mapping
95  *                      the input data to 8-bit image values.
96  *                      Default is the maximum value found in the image data.
97  * -min <float>:        Specify the minimum pixel value to be used for mapping
98  *                      the input data to 8-bit image values.
99  *                      Default is the minimum value found in the image data.
100  * -saturation <float>: If option is given, an Automatic Gain Control algorithmn is
101  *                      applied to the input values. The supplied value specifies the
102  *                      saturation value, i.e. all pixel values greater than the
103  *                      saturation are mapped to white.
104  *                      Valid for mapping mode: agc
105  * -cutoff <float>:     If option is given, an Automatic Gain Control algorithmn is
106  *                      applied to the input values. The supplied value specifies the
107  *                      cut-off value in percent.
108  *                      Valid for mapping mode: agc
109  * -printagc <bool>:    If set to true, additional information about the Automatic
110  *                      Gain Control is printed to stdout. Default is false.
111  *                      Valid for mapping mode: agc
112  *
113  * -nchan <int>:        Specify the number of channels of the input image.
114  *                      Only valid, if reading image data without header.
115  *                      Default is 1.
116  * -width <int>:        Specify the width of the input image. Only valid, if
117  *                      reading image data without header. Default is 128.
118  * -height <int>:       Specify the height of the input image. Only valid, if
119  *                      reading image data without header. Default is 128.
120  * -byteorder <string>: Specify the byteorder of the input image. Only valid, if
121  *                      reading image data without header.
122  *                      Possible values: "Intel" or "Motorola".
123  *                      Default is assuming the same byteorder as that of the
124  *                      host computer.
125  * -scanorder <string>: Specify the scanline order of the input image. Only
126  *                      valid, if reading image data without header.
127  *                      Possible values: "TopDown" or "BottomUp".
128  *                      Default is "TopDown".
129  * -pixeltype <string>: Specify the type of the pixel values.
130  *                      Only valid, if reading image data without header.
131  *                      Possible values: "float", "short" or "byte".
132  *                      Default is "byte".
133  * -uuencode <bool>:    If set to false, do not assume, that the image data stored in a
134  *                      variable is uuencoded. Default is true, i.e. the image data is
135  *                      assumed to be uuencoded.
136  *
137  * Notes:
138  *                      Currently RAW files are only written in "byte" pixel format.
139  *
140  * ENDHEADER
141  *
142  */
143 
144 #include <stdlib.h>
145 #include <math.h>
146 
147 /*
148  * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
149  */
150 
151 #include "init.c"
152 
153 /* Maximum length of a header line. */
154 #define HEADLEN 100
155 
156 /* Header fields. */
157 #define strMagic     "Magic=%s\n"
158 #define strWidth     "Width=%d\n"
159 #define strHeight    "Height=%d\n"
160 #define strNumChan   "NumChan=%d\n"
161 #define strByteOrder "ByteOrder=%s\n"
162 #define strScanOrder "ScanOrder=%s\n"
163 #define strPixelType "PixelType=%s\n"
164 
165 /* Header fields possible values. */
166 #define strIntel    "Intel"
167 #define strMotorola "Motorola"
168 #define strTopDown  "TopDown"
169 #define strBottomUp "BottomUp"
170 #define strFloat    "float"
171 #define strUShort   "short"
172 #define strUByte    "byte"
173 
174 #define strUnknown  "Unknown"
175 
176 #define BOTTOM_UP   0
177 #define TOP_DOWN    1
178 #define INTEL       0
179 #define MOTOROLA    1
180 #define TYPE_FLOAT  0
181 #define TYPE_USHORT 1
182 #define TYPE_UBYTE  2
183 
184 /* Some general defines and typedefs. */
185 #define TRUE  1
186 #define FALSE 0
187 
188 typedef unsigned char Boln;     /* Boolean value: TRUE or FALSE */
189 typedef unsigned char UByte;    /* Unsigned  8 bit integer */
190 typedef char  Byte;             /* Signed    8 bit integer */
191 typedef unsigned short UShort;  /* Unsigned 16 bit integer */
192 typedef short Short;            /* Signed   16 bit integer */
193 typedef int UInt;               /* Unsigned 32 bit integer */
194 typedef int Int;                /* Signed   32 bit integer */
195 typedef float Float;            /* IEEE     32 bit floating point */
196 typedef double Double;          /* IEEE     64 bit floating point */
197 
198 /* RAW file header structure */
199 typedef struct {
200     char  id[3];
201     Int   nChans;
202     Int   width;
203     Int   height;
204     Int   scanOrder;
205     Int   byteOrder;
206     Int   pixelType;
207 } RAWHEADER;
208 
209 /* Format options structure for use with ParseFormatOpts */
210 typedef struct {
211     Int   width;
212     Int   height;
213     Int   nchan;
214     Int   scanOrder;
215     Int   byteOrder;
216     Int   pixelType;
217     Int   mapMode;
218     Float gamma;        /* IMG_MAP_MINMAX and IMG_MAP_AGC */
219     Float minVal;       /* IMG_MAP_MINMAX */
220     Float maxVal;       /* IMG_MAP_MINMAX */
221     Float saturation;   /* IMG_MAP_AGC */
222     Float cutOff;       /* IMG_MAP_AGC */
223     Boln  verbose;
224     Boln  printAgc;
225     Boln  uuencode;
226     Boln  useHeader;
227 } FMTOPT;
228 
229 /* Structure to hold information about the image file being processed. */
230 typedef struct {
231     RAWHEADER th;
232     UByte  *pixbuf;
233     Float  *floatBuf;
234     UShort *ushortBuf;
235     UByte  *ubyteBuf;
236 } RAWFILE;
237 
rawClose(RAWFILE * tf,Boln fastMode)238 static void rawClose (RAWFILE *tf, Boln fastMode)
239 {
240     if (!fastMode) {
241         if (tf->pixbuf) ckfree ((char *)tf->pixbuf);
242     }
243     if (tf->floatBuf)  ckfree ((char *)tf->floatBuf);
244     if (tf->ushortBuf) ckfree ((char *)tf->ushortBuf);
245     if (tf->ubyteBuf)  ckfree ((char *)tf->ubyteBuf);
246     return;
247 }
248 
249 #define OUT Tcl_WriteChars (outChan, str, -1)
printImgInfo(RAWHEADER * th,FMTOPT * opts,const char * filename,const char * msg)250 static void printImgInfo (RAWHEADER *th, FMTOPT *opts,
251                           const char *filename, const char *msg)
252 {
253     Tcl_Channel outChan;
254     char str[256];
255 
256     outChan = Tcl_GetStdChannel (TCL_STDOUT);
257     if (!outChan) {
258         return;
259     }
260     sprintf (str, "%s %s\n", msg, filename);                                                       OUT;
261     sprintf (str, "\tSize in pixel    : %d x %d\n", th->width, th->height);                        OUT;
262     sprintf (str, "\tNo. of channels  : %d\n",      th->nChans);                                   OUT;
263     sprintf (str, "\tPixel type       : %s\n",      (th->pixelType == TYPE_FLOAT?  strFloat:
264                                                     (th->pixelType == TYPE_USHORT? strUShort:
265                                                     (th->pixelType == TYPE_UBYTE?  strUByte:
266                                                                                    strUnknown)))); OUT;
267     sprintf (str, "\tVertical encoding: %s\n",      th->scanOrder == TOP_DOWN?
268                                                     strTopDown: strBottomUp);                      OUT;
269     sprintf (str, "\tHost byte order  : %s\n",      tkimg_IsIntel ()?  strIntel: strMotorola);     OUT;
270     sprintf (str, "\tFile byte order  : %s\n",      th->byteOrder == INTEL?
271                                                     strIntel: strMotorola);                        OUT;
272     sprintf (str, "\tMapping mode     : %s\n",      (opts->mapMode == IMG_MAP_NONE?   IMG_MAP_NONE_STR:
273                                                     (opts->mapMode == IMG_MAP_MINMAX? IMG_MAP_MINMAX_STR:
274                                                     (opts->mapMode == IMG_MAP_AGC?    IMG_MAP_AGC_STR:
275                                                                                   strUnknown))));  OUT;
276     if (opts->mapMode != IMG_MAP_NONE) {
277         sprintf (str, "\tGamma correction : %f\n",       opts->gamma);                             OUT;
278         if (opts->mapMode == IMG_MAP_MINMAX) {
279             sprintf (str, "\tMinimum map value: %f\n",   opts->minVal);                            OUT;
280             sprintf (str, "\tMaximum map value: %f\n",   opts->maxVal);                            OUT;
281         }
282         if (opts->mapMode == IMG_MAP_AGC) {
283             sprintf (str, "\tSaturation       : %f\n",   opts->saturation);                        OUT;
284             sprintf (str, "\tCutOff           : %f%%\n", opts->cutOff);                            OUT;
285         }
286     }
287     Tcl_Flush (outChan);
288 }
289 #undef OUT
290 
readHeaderLine(Tcl_Interp * interp,tkimg_MFile * handle,char * buf)291 static Boln readHeaderLine (Tcl_Interp *interp, tkimg_MFile *handle, char *buf)
292 {
293     char c, *bufPtr, *bufEndPtr;
294     Boln failure;
295 
296     bufPtr    = buf;
297     bufEndPtr = buf + HEADLEN;
298     failure   = TRUE;
299 
300     while (tkimg_Read2(handle, &c, 1) == 1 && bufPtr < bufEndPtr) {
301         if (c == '\n') {
302             *bufPtr = '\0';
303             failure = FALSE;
304             break;
305         }
306         *bufPtr = c;
307         bufPtr++;
308     }
309     if (failure) {
310         Tcl_AppendResult (interp, "RAW handler: Error reading header line (",
311                           buf, ")\n", NULL);
312         return FALSE;
313     }
314     return TRUE;
315 }
316 
readHeader(Tcl_Interp * interp,tkimg_MFile * handle,RAWHEADER * th)317 static Boln readHeader (Tcl_Interp *interp, tkimg_MFile *handle, RAWHEADER *th)
318 {
319     char buf[HEADLEN];
320     char tmpStr[HEADLEN];
321 
322     if (!readHeaderLine (interp, handle, buf) ||
323         (1 != sscanf (buf, strMagic, th->id))) {
324         Tcl_AppendResult (interp, "Unable to parse header field Magic\n", NULL);
325         return FALSE;
326     }
327     if (strcmp (th->id, "RAW") != 0) {
328         Tcl_AppendResult (interp, "Invalid value for header field Magic:",
329                                   "Must be \"RAW\"\n", NULL);
330         return FALSE;
331     }
332 
333     if (!readHeaderLine (interp, handle, buf) ||
334         (1 != sscanf (buf, strWidth, &th->width))) {
335         Tcl_AppendResult (interp, "Unable to parse header field Width\n", NULL);
336         return FALSE;
337     }
338     if (th->width < 1) {
339         Tcl_AppendResult (interp, "Invalid value for header field Width:",
340                                   "Must be greater than zero\n", NULL);
341         return FALSE;
342     }
343 
344     if (!readHeaderLine (interp, handle, buf) ||
345         (1 != sscanf (buf, strHeight, &th->height))) {
346         Tcl_AppendResult (interp, "Unable to parse header field Height\n", NULL);
347         return FALSE;
348     }
349     if (th->height < 1) {
350         Tcl_AppendResult (interp, "Invalid value for header field Height:",
351                                   "Must be greater than zero\n", NULL);
352         return FALSE;
353     }
354 
355     if (!readHeaderLine (interp, handle, buf) ||
356         (1 != sscanf (buf, strNumChan, &th->nChans))) {
357         Tcl_AppendResult (interp, "Unable to parse header field NumChan\n", NULL);
358         return FALSE;
359     }
360     if (th->nChans != 1 && th->nChans != 3) {
361         Tcl_AppendResult (interp, "Invalid value for header field NumChan:",
362                                   "Must be 1 or 3\n", NULL);
363         return FALSE;
364     }
365 
366     if (!readHeaderLine (interp, handle, buf) ||
367         (1 != sscanf (buf, strByteOrder, tmpStr))) {
368         Tcl_AppendResult (interp, "Unable to parse header field ByteOrder\n", NULL);
369         return FALSE;
370     }
371 
372     if (strcmp (tmpStr, strIntel) == 0) {
373         th->byteOrder = INTEL;
374     } else if (strcmp (tmpStr, strMotorola) == 0) {
375         th->byteOrder = MOTOROLA;
376     } else {
377         Tcl_AppendResult (interp, "Invalid value for header field ByteOrder:",
378                                   "Must be ", strIntel, " or ", strMotorola,
379                                   "\n", NULL);
380         return FALSE;
381     }
382 
383     if (!readHeaderLine (interp, handle, buf) ||
384         (1 != sscanf (buf, strScanOrder, tmpStr))) {
385         Tcl_AppendResult (interp, "Unable to parse header field ScanOrder\n", NULL);
386         return FALSE;
387     }
388     if (strcmp (tmpStr, strTopDown) == 0) {
389         th->scanOrder = TOP_DOWN;
390     } else if (strcmp (tmpStr, strBottomUp) == 0) {
391         th->scanOrder = BOTTOM_UP;
392     } else {
393         Tcl_AppendResult (interp, "Invalid value for header field ScanOrder:",
394                                   "Must be ", strTopDown, " or ", strBottomUp,
395                                   "\n", NULL);
396         return FALSE;
397     }
398 
399     if (!readHeaderLine (interp, handle, buf) ||
400         (1 != sscanf (buf, strPixelType, tmpStr))) {
401         Tcl_AppendResult (interp, "Unable to parse header field PixelType\n", NULL);
402         return FALSE;
403     }
404     if (strcmp (tmpStr, strFloat) == 0) {
405         th->pixelType = TYPE_FLOAT;
406     } else if (strcmp (tmpStr, strUShort) == 0) {
407         th->pixelType = TYPE_USHORT;
408     } else if (strcmp (tmpStr, strUByte) == 0) {
409         th->pixelType = TYPE_UBYTE;
410     } else {
411         Tcl_AppendResult (interp, "Invalid value for header field PixelType:",
412                                   "Must be ", strFloat, ",", strUShort, " or ", strUByte,
413                                   "\n", NULL);
414         return FALSE;
415     }
416 
417     return TRUE;
418 }
419 
writeHeader(tkimg_MFile * handle,RAWHEADER * th)420 static Boln writeHeader (tkimg_MFile *handle, RAWHEADER *th)
421 {
422     char buf[1024];
423 
424     sprintf (buf, strMagic, "RAW");
425     tkimg_Write2(handle, buf, strlen (buf));
426     sprintf (buf, strWidth, th->width);
427     tkimg_Write2(handle, buf, strlen (buf));
428     sprintf (buf, strHeight, th->height);
429     tkimg_Write2(handle, buf, strlen (buf));
430     sprintf (buf, strNumChan, th->nChans);
431     tkimg_Write2(handle, buf, strlen (buf));
432     sprintf (buf, strByteOrder, tkimg_IsIntel()? strIntel: strMotorola);
433     tkimg_Write2(handle, buf, strlen (buf));
434     sprintf (buf, strScanOrder, th->scanOrder == TOP_DOWN?
435                                 strTopDown: strBottomUp);
436     tkimg_Write2(handle, buf, strlen (buf));
437     sprintf (buf, strPixelType, (th->pixelType == TYPE_FLOAT?  strFloat:
438                                 (th->pixelType == TYPE_USHORT? strUShort:
439                                 (th->pixelType == TYPE_UBYTE?  strUByte:
440                                                                strUnknown))));
441     tkimg_Write2(handle, buf, strlen (buf));
442     return TRUE;
443 }
444 
initHeader(RAWHEADER * th)445 static void initHeader (RAWHEADER *th)
446 {
447     th->id[0]     = 'R';
448     th->id[1]     = 'A';
449     th->id[2]     = 'W';
450     th->nChans    = 1;
451     th->width     = 128;
452     th->height    = 128;
453     th->scanOrder = TOP_DOWN;
454     th->byteOrder = INTEL;
455     th->pixelType = TYPE_UBYTE;
456     return;
457 }
458 
459 /*
460  * Here is the start of the standard functions needed for every image format.
461  */
462 
463 /*
464  * Prototypes for local procedures defined in this file:
465  */
466 
467 static int ParseFormatOpts(Tcl_Interp *interp, Tcl_Obj *format,
468         FMTOPT *opts);
469 static int CommonMatch(Tcl_Interp *interp, tkimg_MFile *handle,
470         Tcl_Obj *format, int *widthPtr, int *heightPtr,
471         RAWHEADER *rawHeaderPtr);
472 static int CommonRead(Tcl_Interp *interp, tkimg_MFile *handle,
473         const char *filename, Tcl_Obj *format,
474         Tk_PhotoHandle imageHandle, int destX, int destY,
475         int width, int height, int srcX, int srcY);
476 static int CommonWrite(Tcl_Interp *interp,
477         const char *filename, Tcl_Obj *format,
478         tkimg_MFile *handle, Tk_PhotoImageBlock *blockPtr);
479 
ParseFormatOpts(Tcl_Interp * interp,Tcl_Obj * format,FMTOPT * opts)480 static int ParseFormatOpts(
481     Tcl_Interp *interp,
482     Tcl_Obj *format,
483     FMTOPT *opts
484 ) {
485     static const char *const rawOptions[] = {
486          "-verbose", "-width", "-height", "-nchan", "-byteorder",
487          "-scanorder", "-pixeltype", "-min", "-max", "-gamma",
488          "-useheader", "-map", "-uuencode", "-saturation", "-cutoff",
489          "-nomap", "-printagc", NULL
490     };
491     int objc, i, index;
492     char *optionStr;
493     Tcl_Obj **objv;
494     int boolVal;
495     int intVal;
496     double doubleVal;
497 
498     /* Initialize options with default values. */
499     opts->verbose    = 0;
500     opts->width      = 128;
501     opts->height     = 128;
502     opts->nchan      = 1;
503     opts->byteOrder  = tkimg_IsIntel()? INTEL: MOTOROLA;
504     opts->scanOrder  = TOP_DOWN;
505     opts->pixelType  = TYPE_UBYTE;
506     opts->minVal     = -1.0;
507     opts->maxVal     = -1.0;
508     opts->gamma      = 1.0;
509     opts->useHeader  = 1;
510     opts->mapMode    = IMG_MAP_MINMAX;
511     opts->uuencode   = 1;
512     opts->saturation = -1.0;
513     opts->cutOff     = 3.0;
514     opts->printAgc   = 0;
515 
516     if (tkimg_ListObjGetElements (interp, format, &objc, &objv) != TCL_OK)
517         return TCL_ERROR;
518     if (objc) {
519         for (i=1; i<objc; i++) {
520             if (Tcl_GetIndexFromObj (interp, objv[i], (const char *CONST86 *)rawOptions,
521                     "format option", 0, &index) != TCL_OK) {
522                 return TCL_ERROR;
523             }
524             i++;
525             if (i >= objc) {
526                 Tcl_AppendResult (interp, "No value for option \"",
527                         Tcl_GetStringFromObj (objv[--i], (int *) NULL),
528                         "\"", (char *) NULL);
529                 return TCL_ERROR;
530             }
531             optionStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
532             switch(index) {
533                 case 0:
534                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
535                         Tcl_AppendResult (interp, "Invalid verbose mode \"", optionStr,
536                                           "\": should be 1 or 0, on or off, true or false",
537                                           (char *) NULL);
538                         return TCL_ERROR;
539                     }
540                     opts->verbose = boolVal;
541                     break;
542                 case 1:
543                     if (Tcl_GetInt(interp, optionStr, &intVal) == TCL_ERROR || intVal <= 0) {
544                         Tcl_AppendResult (interp, "Invalid image width \"", optionStr,
545                                           "\": Must be greater than zero.", (char *) NULL);
546                         return TCL_ERROR;
547                     }
548                     opts->width = intVal;
549                     break;
550                 case 2:
551                     if (Tcl_GetInt(interp, optionStr, &intVal) == TCL_ERROR || intVal <= 0) {
552                         Tcl_AppendResult (interp, "Invalid image height \"", optionStr,
553                                           "\": Must be greater than zero.", (char *) NULL);
554                         return TCL_ERROR;
555                     }
556                     opts->height = intVal;
557                     break;
558                 case 3:
559                     if (Tcl_GetInt(interp, optionStr, &intVal) == TCL_ERROR ||
560                         intVal <= 0 || intVal > 4) {
561                         Tcl_AppendResult (interp, "Invalid number of channels \"", optionStr,
562                                           "\": Must be either 1, 2, 3 or 4.", (char *) NULL);
563                         return TCL_ERROR;
564                     }
565                     opts->nchan = intVal;
566                     break;
567                 case 4:
568                     if (!strncmp (optionStr, strIntel, strlen (strIntel))) {
569                         opts->byteOrder = INTEL;
570                     } else if (!strncmp (optionStr, strMotorola, strlen (strMotorola))) {
571                         opts->byteOrder = MOTOROLA;
572                     } else {
573                         Tcl_AppendResult (interp, "Invalid byteorder mode \"", optionStr,
574                                           "\": Must be ", strIntel, " or ", strMotorola, ".",
575                                           (char *) NULL);
576                         return TCL_ERROR;
577                     }
578                     break;
579                 case 5:
580                     if (!strncmp (optionStr, strTopDown, strlen (strTopDown))) {
581                         opts->scanOrder = TOP_DOWN;
582                     } else if (!strncmp (optionStr, strBottomUp, strlen (strBottomUp))) {
583                         opts->scanOrder = BOTTOM_UP;
584                     } else {
585                         Tcl_AppendResult (interp, "Invalid scanline order \"", optionStr,
586                                           "\": should be TopDown or BottomUp",
587                                           (char *) NULL);
588                         return TCL_ERROR;
589                     }
590                     break;
591                 case 6:
592                     if (!strncmp (optionStr, strFloat, strlen (strFloat))) {
593                         opts->pixelType = TYPE_FLOAT;
594                     } else if (!strncmp (optionStr, strUShort, strlen (strUShort))) {
595                         opts->pixelType = TYPE_USHORT;
596                     } else if (!strncmp (optionStr, strUByte, strlen (strUByte))) {
597                         opts->pixelType = TYPE_UBYTE;
598                     } else {
599                         Tcl_AppendResult (interp, "Invalid pixel type \"", optionStr,
600                                           "\": should be float, short or byte",
601                                           (char *) NULL);
602                         return TCL_ERROR;
603                     }
604                     break;
605                 case 7:
606                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
607                         Tcl_AppendResult (interp, "Invalid minimum map value \"", optionStr,
608                                           "\": Must be greater than or equal to zero.", (char *) NULL);
609                         return TCL_ERROR;
610                     }
611                     if (doubleVal >= 0.0) {
612                         opts->minVal = doubleVal;
613                     }
614                     break;
615                 case 8:
616                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
617                         Tcl_AppendResult (interp, "Invalid maximum map value \"", optionStr,
618                                           "\": Must be greater than or equal to zero.", (char *) NULL);
619                         return TCL_ERROR;
620                     }
621                     if (doubleVal >= 0.0) {
622                         opts->maxVal = doubleVal;
623                     }
624                     break;
625                 case 9:
626                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
627                         Tcl_AppendResult (interp, "Invalid gamma value \"", optionStr,
628                                           "\": Must be greater than or equal to zero.", (char *) NULL);
629                         return TCL_ERROR;
630                     }
631                     if (doubleVal >= 0.0) {
632                         opts->gamma = doubleVal;
633                     }
634                     break;
635                 case 10:
636                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
637                         Tcl_AppendResult (interp, "Invalid useheader mode \"", optionStr,
638                                           "\": should be 1 or 0, on or off, true or false",
639                                           (char *) NULL);
640                         return TCL_ERROR;
641                     }
642                     opts->useHeader = boolVal;
643                     break;
644                 case 11:
645                     if (!strncmp (optionStr, IMG_MAP_NONE_STR, strlen (IMG_MAP_NONE_STR))) {
646                         opts->mapMode = IMG_MAP_NONE;
647                     } else if (!strncmp (optionStr, IMG_MAP_MINMAX_STR, strlen (IMG_MAP_MINMAX_STR))) {
648                         opts->mapMode = IMG_MAP_MINMAX;
649                     } else if (!strncmp (optionStr, IMG_MAP_AGC_STR, strlen (IMG_MAP_AGC_STR))) {
650                         opts->mapMode = IMG_MAP_AGC;
651                     } else {
652                         Tcl_AppendResult (interp, "Invalid mapping mode \"", optionStr,
653                                           "\": should be none, minmax or agc",
654                                           (char *) NULL);
655                         return TCL_ERROR;
656                     }
657                     break;
658                 case 12:
659                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
660                         Tcl_AppendResult (interp, "Invalid uuencode mode \"", optionStr,
661                                           "\": should be 1 or 0, on or off, true or false",
662                                           (char *) NULL);
663                         return TCL_ERROR;
664                     }
665                     opts->uuencode = boolVal;
666                     break;
667                 case 13:
668                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
669                         Tcl_AppendResult (interp, "Invalid saturation value \"", optionStr,
670                                           "\": Must be greater than or equal to zero.", (char *) NULL);
671                         return TCL_ERROR;
672                     }
673                     if (doubleVal >= 0.0) {
674                         opts->saturation = doubleVal;
675                     }
676                     break;
677                 case 14:
678                     if (Tcl_GetDouble(interp, optionStr, &doubleVal) == TCL_ERROR) {
679                         Tcl_AppendResult (interp, "Invalid cutoff value \"", optionStr,
680                                           "\": Must be greater than or equal to zero.", (char *) NULL);
681                         return TCL_ERROR;
682                     }
683                     if (doubleVal >= 0.0) {
684                         opts->cutOff = doubleVal;
685                     }
686                     break;
687                 case 15:
688                     /* Option "-nomap" for backward compatibility. */
689                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
690                         Tcl_AppendResult (interp, "Invalid nomap mode \"", optionStr,
691                                           "\": should be 1 or 0, on or off, true or false",
692                                           (char *) NULL);
693                         return TCL_ERROR;
694                     }
695                     if (boolVal) {
696                         opts->mapMode = IMG_MAP_NONE;
697                     }
698                     break;
699                 case 16:
700                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
701                         Tcl_AppendResult (interp, "Invalid printagc mode \"", optionStr,
702                                           "\": should be 1 or 0, on or off, true or false",
703                                           (char *) NULL);
704                         return TCL_ERROR;
705                     }
706                     opts->printAgc = boolVal;
707                     break;
708             }
709         }
710     }
711 
712     /* Convert minimum and maximum range values. */
713     if (opts->minVal >= 0.0 && opts->maxVal >= 0.0 && opts->minVal >= opts->maxVal) {
714         Tcl_AppendResult (interp, "Invalid range values: Maximum must be grater than minimum.", (char *) NULL);
715         return TCL_ERROR;
716     }
717     return TCL_OK;
718 }
719 
ChnMatch(Tcl_Channel chan,const char * filename,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)720 static int ChnMatch(
721     Tcl_Channel chan,
722     const char *filename,
723     Tcl_Obj *format,
724     int *widthPtr,
725     int *heightPtr,
726     Tcl_Interp *interp
727 ) {
728     tkimg_MFile handle;
729 
730     handle.data = (char *) chan;
731     handle.state = IMG_CHAN;
732 
733     return CommonMatch (interp, &handle, format, widthPtr, heightPtr, NULL);
734 }
735 
ObjMatch(Tcl_Obj * data,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)736 static int ObjMatch(
737     Tcl_Obj *data,
738     Tcl_Obj *format,
739     int *widthPtr,
740     int *heightPtr,
741     Tcl_Interp *interp
742 ) {
743     tkimg_MFile handle;
744     FMTOPT opts;
745 
746     if (ParseFormatOpts (interp, format, &opts) == TCL_ERROR) {
747         return FALSE;
748     }
749     if (!opts.uuencode) {
750         size_t length;
751         handle.data = (char *) tkimg_GetByteArrayFromObj2(data, &length);
752         handle.length = length;
753         handle.state = IMG_STRING;
754     } else {
755         tkimg_ReadInit(data, 'M', &handle);
756     }
757     return CommonMatch (interp, &handle, format, widthPtr, heightPtr, NULL);
758 }
759 
CommonMatch(Tcl_Interp * interp,tkimg_MFile * handle,Tcl_Obj * format,int * widthPtr,int * heightPtr,RAWHEADER * rawHeaderPtr)760 static int CommonMatch(
761     Tcl_Interp *interp,
762     tkimg_MFile *handle,
763     Tcl_Obj *format,
764     int *widthPtr,
765     int *heightPtr,
766     RAWHEADER *rawHeaderPtr
767 ) {
768     RAWHEADER th;
769     FMTOPT opts;
770 
771     initHeader (&th);
772 
773     if (ParseFormatOpts (interp, format, &opts) == TCL_ERROR) {
774         return FALSE;
775     }
776     if (opts.useHeader) {
777         if (!readHeader (interp, handle, &th)) {
778             return FALSE;
779         }
780     } else {
781         th.width  = opts.width;
782         th.height = opts.height;
783         th.nChans = opts.nchan;
784         th.pixelType = opts.pixelType;
785         th.scanOrder = opts.scanOrder;
786         th.byteOrder = opts.byteOrder;
787     }
788     *widthPtr  = th.width;
789     *heightPtr = th.height;
790     if (rawHeaderPtr) {
791         *rawHeaderPtr = th;
792     }
793     return TRUE;
794 }
795 
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)796 static int ChnRead(
797     Tcl_Interp *interp,         /* Interpreter to use for reporting errors. */
798     Tcl_Channel chan,           /* The image channel, open for reading. */
799     const char *filename,       /* The name of the image file. */
800     Tcl_Obj *format,            /* User-specified format object, or NULL. */
801     Tk_PhotoHandle imageHandle, /* The photo image to write into. */
802     int destX, int destY,       /* Coordinates of top-left pixel in
803                                  * photo image to be written to. */
804     int width, int height,      /* Dimensions of block of photo image to
805                                  * be written to. */
806     int srcX, int srcY          /* Coordinates of top-left pixel to be used
807                                  * in image being read. */
808 ) {
809     tkimg_MFile handle;
810 
811     handle.data = (char *) chan;
812     handle.state = IMG_CHAN;
813 
814     return CommonRead (interp, &handle, filename, format, imageHandle,
815                        destX, destY, width, height, srcX, srcY);
816 }
817 
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)818 static int ObjRead(
819     Tcl_Interp *interp,
820     Tcl_Obj *data,
821     Tcl_Obj *format,
822     Tk_PhotoHandle imageHandle,
823     int destX, int destY,
824     int width, int height,
825     int srcX, int srcY
826 ) {
827     tkimg_MFile handle;
828     FMTOPT opts;
829 
830     if (ParseFormatOpts (interp, format, &opts) == TCL_ERROR) {
831         return TCL_ERROR;
832     }
833     if (!opts.uuencode) {
834         size_t length;
835         handle.data = (char *) tkimg_GetByteArrayFromObj2(data, &length);
836         handle.length = length;
837         handle.state = IMG_STRING;
838     } else {
839         tkimg_ReadInit(data, 'M', &handle);
840     }
841 
842     return CommonRead (interp, &handle, "InlineData", format, imageHandle,
843                        destX, destY, width, height, srcX, srcY);
844 }
845 
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)846 static int CommonRead(
847     Tcl_Interp *interp,         /* Interpreter to use for reporting errors. */
848     tkimg_MFile *handle,        /* The image file, open for reading. */
849     const char *filename,       /* The name of the image file. */
850     Tcl_Obj *format,            /* User-specified format object, or NULL. */
851     Tk_PhotoHandle imageHandle, /* The photo image to write into. */
852     int destX, int destY,       /* Coordinates of top-left pixel in
853                                  * photo image to be written to. */
854     int width, int height,      /* Dimensions of block of photo image to
855                                  * be written to. */
856     int srcX, int srcY          /* Coordinates of top-left pixel to be used
857                                  * in image being read. */
858 ) {
859     Tk_PhotoImageBlock block;
860     Int x, y, c;
861     Int fileWidth = 0, fileHeight = 0;
862     Float minVals[IMG_MAX_CHANNELS], maxVals[IMG_MAX_CHANNELS];
863     int stopY, outY, outWidth, outHeight;
864     RAWFILE tf;
865     FMTOPT opts;
866     Boln swapBytes;
867     Boln fastMode;
868     Int  byteOrder;
869     Int  scanOrder;
870     Int  pixelType;
871     Int  matte = 0;
872     UByte  *pixbufPtr;
873     Float  *floatBufPtr;
874     UShort *ushortBufPtr;
875     UByte  *ubyteBufPtr;
876     Float  gtable[IMG_GAMMA_TABLE_SIZE];
877     int result = TCL_OK;
878 
879     memset (&tf, 0, sizeof (RAWFILE));
880     initHeader (&tf.th);
881 
882     if (!CommonMatch (interp, handle, format, &fileWidth, &fileHeight, &tf.th)) {
883         return TCL_ERROR;
884     }
885 
886     if (ParseFormatOpts (interp, format, &opts) == TCL_ERROR) {
887         return TCL_ERROR;
888     }
889 
890     if (opts.verbose) {
891         printImgInfo (&tf.th, &opts, filename, "Reading image:");
892     }
893 
894     if ((srcX + width) > fileWidth) {
895         outWidth = fileWidth - srcX;
896     } else {
897         outWidth = width;
898     }
899     if ((srcY + height) > fileHeight) {
900         outHeight = fileHeight - srcY;
901     } else {
902         outHeight = height;
903     }
904     if ((outWidth <= 0) || (outHeight <= 0)
905         || (srcX >= fileWidth) || (srcY >= fileHeight)) {
906         return TCL_OK;
907     }
908 
909     byteOrder = opts.useHeader? tf.th.byteOrder: opts.byteOrder;
910     scanOrder = opts.useHeader? tf.th.scanOrder: opts.scanOrder;
911     pixelType = opts.useHeader? tf.th.pixelType: opts.pixelType;
912     swapBytes = (( tkimg_IsIntel () && (byteOrder != INTEL)) ||
913                  (!tkimg_IsIntel () && (byteOrder == INTEL)));
914     fastMode  = (opts.mapMode == IMG_MAP_NONE &&
915                  pixelType == TYPE_UBYTE && scanOrder == TOP_DOWN &&
916                  fileWidth == width && fileHeight == height);
917 
918     if (!fastMode) {
919         tkimg_CreateGammaTable (opts.gamma, gtable);
920     }
921 
922     switch (pixelType) {
923         case TYPE_FLOAT: {
924             tf.floatBuf = (Float *)ckalloc (fileWidth*fileHeight*tf.th.nChans*sizeof (Float));
925             tkimg_ReadFloatFile (handle, tf.floatBuf, fileWidth, fileHeight, tf.th.nChans,
926                                  swapBytes, opts.verbose, opts.mapMode != IMG_MAP_NONE,
927                                  minVals, maxVals, opts.saturation);
928             break;
929         }
930         case TYPE_USHORT: {
931             tf.ushortBuf = (UShort *)ckalloc (fileWidth*fileHeight*tf.th.nChans*sizeof (UShort));
932             tkimg_ReadUShortFile (handle, tf.ushortBuf, fileWidth, fileHeight, tf.th.nChans,
933                                   swapBytes, opts.verbose, opts.mapMode != IMG_MAP_NONE, minVals, maxVals);
934             break;
935         }
936         case TYPE_UBYTE: {
937             tf.ubyteBuf = (UByte *)ckalloc (fileWidth*fileHeight*tf.th.nChans*sizeof (UByte));
938             tkimg_ReadUByteFile (handle, tf.ubyteBuf, fileWidth, fileHeight, tf.th.nChans,
939                                  opts.verbose, opts.mapMode != IMG_MAP_NONE, minVals, maxVals);
940             break;
941         }
942     }
943     switch (opts.mapMode) {
944         case IMG_MAP_NONE: {
945             for (c=0; c<tf.th.nChans; c++) {
946                 minVals[c] = 0.0;
947                 maxVals[c] = 255.0;
948             }
949             break;
950         }
951         case IMG_MAP_MINMAX: {
952             if (opts.minVal >= 0.0) {
953                 for (c=0; c<tf.th.nChans; c++) {
954                     minVals[c] = opts.minVal;
955                 }
956             }
957             if (opts.maxVal >= 0.0) {
958                 for (c=0; c<tf.th.nChans; c++) {
959                     maxVals[c] = opts.maxVal;
960                 }
961             }
962             break;
963         }
964         case IMG_MAP_AGC: {
965             /* Nothing to do. Saturation is considered on tkimg_ReadFloatFile. */
966             break;
967         }
968     }
969 
970     switch (pixelType) {
971         case TYPE_FLOAT: {
972             tkimg_RemapFloatValues (
973                 tf.floatBuf, fileWidth, fileHeight, tf.th.nChans,
974                 minVals, maxVals, opts.mapMode == IMG_MAP_AGC? opts.cutOff: -1.0,
975                 opts.printAgc
976             );
977             break;
978         }
979         case TYPE_USHORT: {
980             tkimg_RemapUShortValues (
981                 tf.ushortBuf, fileWidth, fileHeight, tf.th.nChans,
982                 minVals, maxVals
983             );
984             break;
985         }
986     }
987 
988     if (tkimg_PhotoExpand (interp, imageHandle, destX + outWidth, destY + outHeight) == TCL_ERROR) {
989         rawClose (&tf, fastMode);
990         return TCL_ERROR;
991     }
992 
993     if (fastMode) {
994         tf.pixbuf = tf.ubyteBuf;
995     } else {
996         tf.pixbuf = (UByte *) ckalloc (fileWidth * tf.th.nChans);
997     }
998 
999     block.pixelSize = tf.th.nChans;
1000     block.pitch = fileWidth * tf.th.nChans;
1001     block.width = outWidth;
1002     block.height = fastMode? outHeight: 1;
1003     block.offset[0] = 0;
1004     block.offset[1] = (tf.th.nChans > 1? 1: 0);
1005     block.offset[2] = (tf.th.nChans > 1? 2: 0);
1006     block.offset[3] = (tf.th.nChans == 4 && matte? 3: 0);
1007     block.pixelPtr = tf.pixbuf + srcX * tf.th.nChans;
1008 
1009     stopY = srcY + outHeight;
1010     outY = destY;
1011 
1012     if (fastMode) {
1013         if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY,
1014                             width, height,
1015                             block.offset[3]?
1016                             TK_PHOTO_COMPOSITE_SET:
1017                             TK_PHOTO_COMPOSITE_OVERLAY) == TCL_ERROR) {
1018             result = TCL_ERROR;
1019         }
1020     } else {
1021         for (y=0; y<stopY; y++) {
1022             pixbufPtr = tf.pixbuf;
1023             switch (pixelType) {
1024                 case TYPE_FLOAT: {
1025                     if (scanOrder == BOTTOM_UP) {
1026                         floatBufPtr = tf.floatBuf + (fileHeight -1 - y) * fileWidth * tf.th.nChans;
1027                     } else {
1028                         floatBufPtr = tf.floatBuf + y * fileWidth * tf.th.nChans;
1029                     }
1030                     tkimg_FloatToUByte (fileWidth * tf.th.nChans, floatBufPtr,
1031                                         opts.gamma != 1.0? gtable: NULL, pixbufPtr);
1032                     floatBufPtr += fileWidth * tf.th.nChans;
1033                     break;
1034                 }
1035                 case TYPE_USHORT: {
1036                     if (scanOrder == BOTTOM_UP) {
1037                         ushortBufPtr = tf.ushortBuf + (fileHeight -1 - y) * fileWidth * tf.th.nChans;
1038                     } else {
1039                         ushortBufPtr = tf.ushortBuf + y * fileWidth * tf.th.nChans;
1040                     }
1041                     tkimg_UShortToUByte (fileWidth * tf.th.nChans, ushortBufPtr,
1042                                          opts.gamma != 1.0? gtable: NULL, pixbufPtr);
1043                     ushortBufPtr += fileWidth * tf.th.nChans;
1044                     break;
1045                 }
1046                 case TYPE_UBYTE: {
1047                     if (scanOrder == BOTTOM_UP) {
1048                         ubyteBufPtr = tf.ubyteBuf + (fileHeight -1 - y) * fileWidth * tf.th.nChans;
1049                     } else {
1050                         ubyteBufPtr = tf.ubyteBuf + y * fileWidth * tf.th.nChans;
1051                     }
1052                     for (x=0; x<fileWidth * tf.th.nChans; x++) {
1053                         pixbufPtr[x] = ubyteBufPtr[x];
1054                     }
1055                     ubyteBufPtr += fileWidth * tf.th.nChans;
1056                     break;
1057                 }
1058             }
1059             if (y >= srcY) {
1060                 if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY,
1061                                     width, 1,
1062                                     block.offset[3]?
1063                                     TK_PHOTO_COMPOSITE_SET:
1064                                     TK_PHOTO_COMPOSITE_OVERLAY) == TCL_ERROR) {
1065                     result = TCL_ERROR;
1066                     break;
1067                 }
1068                 outY++;
1069             }
1070         }
1071     }
1072     rawClose (&tf, fastMode);
1073     return result;
1074 }
1075 
ChnWrite(Tcl_Interp * interp,const char * filename,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)1076 static int ChnWrite(
1077     Tcl_Interp *interp,
1078     const char *filename,
1079     Tcl_Obj *format,
1080     Tk_PhotoImageBlock *blockPtr
1081 ) {
1082     Tcl_Channel chan;
1083     tkimg_MFile handle;
1084     int result;
1085 
1086     chan = tkimg_OpenFileChannel (interp, filename, 0644);
1087     if (!chan) {
1088         return TCL_ERROR;
1089     }
1090 
1091     handle.data = (char *) chan;
1092     handle.state = IMG_CHAN;
1093 
1094     result = CommonWrite (interp, filename, format, &handle, blockPtr);
1095     if (Tcl_Close(interp, chan) == TCL_ERROR) {
1096         return TCL_ERROR;
1097     }
1098     return result;
1099 }
1100 
StringWrite(Tcl_Interp * interp,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)1101 static int StringWrite(
1102     Tcl_Interp *interp,
1103     Tcl_Obj *format,
1104     Tk_PhotoImageBlock *blockPtr
1105 ) {
1106     tkimg_MFile handle;
1107     int result;
1108     Tcl_DString data;
1109 
1110     Tcl_DStringInit(&data);
1111     tkimg_WriteInit (&data, &handle);
1112     result = CommonWrite (interp, "InlineData", format, &handle, blockPtr);
1113     tkimg_Putc(IMG_DONE, &handle);
1114 
1115     if (result == TCL_OK) {
1116         Tcl_DStringResult(interp, &data);
1117     } else {
1118         Tcl_DStringFree(&data);
1119     }
1120     return result;
1121 }
1122 
CommonWrite(Tcl_Interp * interp,const char * filename,Tcl_Obj * format,tkimg_MFile * handle,Tk_PhotoImageBlock * blockPtr)1123 static int CommonWrite(
1124     Tcl_Interp *interp,
1125     const char *filename,
1126     Tcl_Obj *format,
1127     tkimg_MFile *handle,
1128     Tk_PhotoImageBlock *blockPtr
1129 ) {
1130     Int     x, y;
1131     Int     redOffset, greenOffset, blueOffset, alphaOffset;
1132     UByte   *pixelPtr, *rowPixPtr;
1133     RAWFILE tf;
1134     FMTOPT opts;
1135     UByte *ubyteBufPtr;
1136     Int bytesPerLine;
1137 
1138     memset (&tf, 0, sizeof (RAWFILE));
1139     if (ParseFormatOpts (interp, format, &opts) == TCL_ERROR) {
1140         return TCL_ERROR;
1141     }
1142 
1143     redOffset   = 0;
1144     greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
1145     blueOffset  = blockPtr->offset[2] - blockPtr->offset[0];
1146     alphaOffset = blockPtr->offset[0];
1147 
1148     if (alphaOffset < blockPtr->offset[2]) {
1149         alphaOffset = blockPtr->offset[2];
1150     }
1151     if (++alphaOffset < blockPtr->pixelSize) {
1152         alphaOffset -= blockPtr->offset[0];
1153     } else {
1154         alphaOffset = 0;
1155     }
1156 
1157     initHeader (&tf.th);
1158     tf.th.width = blockPtr->width;
1159     tf.th.height = blockPtr->height;
1160     tf.th.nChans = opts.nchan;
1161     tf.th.scanOrder = opts.scanOrder;
1162     tf.th.pixelType = TYPE_UBYTE;
1163 
1164     writeHeader (handle, &tf.th);
1165     bytesPerLine = blockPtr->width * tf.th.nChans * sizeof (UByte);
1166     tf.ubyteBuf = (UByte *)ckalloc (bytesPerLine);
1167 
1168     rowPixPtr = blockPtr->pixelPtr + blockPtr->offset[0];
1169     for (y = 0; y < blockPtr->height; y++) {
1170         ubyteBufPtr = tf.ubyteBuf;
1171         pixelPtr = rowPixPtr;
1172         if (tf.th.nChans == 1) {
1173             for (x=0; x<blockPtr->width; x++) {
1174                 *ubyteBufPtr = pixelPtr[redOffset];
1175                 ubyteBufPtr++;
1176                 pixelPtr += blockPtr->pixelSize;
1177             }
1178         } else {
1179             for (x=0; x<blockPtr->width; x++) {
1180                 *(ubyteBufPtr++) = pixelPtr[redOffset];
1181                 *(ubyteBufPtr++) = pixelPtr[greenOffset];
1182                 *(ubyteBufPtr++) = pixelPtr[blueOffset];
1183                 if (tf.th.nChans == 4) {
1184                     /* Have a matte channel and write it. */
1185                     *(ubyteBufPtr++) = pixelPtr[alphaOffset];
1186                 }
1187                 pixelPtr += blockPtr->pixelSize;
1188             }
1189         }
1190         if (tkimg_Write2(handle, (char *)tf.ubyteBuf, bytesPerLine) != bytesPerLine) {
1191             rawClose (&tf, FALSE);
1192             return TCL_ERROR;
1193         }
1194         rowPixPtr += blockPtr->pitch;
1195     }
1196     if (opts.verbose)
1197         printImgInfo (&tf.th, &opts, filename, "Saving image:");
1198     rawClose (&tf, FALSE);
1199     return TCL_OK;
1200 }
1201 
1202