1 /* STARTHEADER
2  *
3  * File :       pcx.c
4  *
5  * Author :     Paul Obermeier (paul@poSoft.de)
6  *
7  * Date :       2001 / 02 / 20
8  *
9  * Copyright :  (C) 2001-2019 Paul Obermeier
10  *
11  * Description :
12  *
13  * A photo image handler for PaintBrush's PCX file format.
14  *
15  * The following image types are supported:
16  *
17  *  1-bit pixels: Black and White.
18  *  8-bit pixels: Grayscale or indexed.
19  * 24-bit pixels: True-color (RGB, each channel 8 bit).
20  *
21  * List of currently supported features:
22  *
23  * Type   |     Read      |     Write     |
24  *        | -file | -data | -file | -data |
25  * ----------------------------------------
26  *  1-bit | Yes   | Yes   | No    | No    |
27  *  8-bit | Yes   | Yes   | No    | No    |
28  * 24-bit | Yes   | Yes   | Yes   | Yes   |
29  *
30  * All images types may be either uncompressed or run-length encoded.
31  *
32  *
33  * The following format options are available:
34  *
35  * Read  PCX image: "pcx -verbose <bool>"
36  * Write PCX image: "pcx -verbose <bool> -compression <type>"
37  *
38  * -verbose <bool>:     If set to true, additional information about the file
39  *                      format is printed to stdout. Default is "false".
40  * -compression <type>: Set the compression mode to either "none" or "rle".
41  *                      Default is "rle".
42  *
43  * Notes:
44  *
45  * - Part of this code was taken from the "pcx" GIMP plugin:
46  *
47  *  >> pcx.c GIMP plug-in for loading & saving PCX files
48  *  >>
49  *  >> This code is based in parts on code by Francisco Bustamante, but the
50  *  >> largest portion of the code has been rewritten and is now maintained
51  *  >> occasionally by Nick Lamb njl195@zepler.org.uk
52  *
53  * ENDHEADER
54  *
55  */
56 
57 /*
58  * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
59  */
60 
61 #include "init.c"
62 
63 
64 /* #define DEBUG_LOCAL */
65 
66 /* Some defines and typedefs. */
67 #define TRUE  1
68 #define FALSE 0
69 typedef unsigned char Boln;     /* Boolean value: TRUE or FALSE */
70 typedef unsigned char UByte;    /* Unsigned  8 bit integer */
71 typedef char  Byte;             /* Signed    8 bit integer */
72 typedef unsigned short UShort;  /* Unsigned 16 bit integer */
73 typedef short Short;            /* Signed   16 bit integer */
74 typedef int Int;                /* Signed   32 bit integer */
75 
76 typedef struct {
77     UByte manufacturer;
78     UByte version;
79     UByte compression;
80     UByte bpp;
81     Short x1;
82     Short y1;
83     Short x2;
84     Short y2;
85     Short hdpi;
86     Short vdpi;
87     UByte colormap[48];
88     UByte reserved;
89     UByte planes;
90     Short bytesperline;
91     Short color;
92     UByte filler[58];
93 } PCXHEADER;
94 
95 /* Format options structure for use with ParseFormatOpts */
96 typedef struct {
97     Int   compression;
98     Boln  verbose;
99 } FMTOPT;
100 
101 #define htoqs(x) qtohs(x)
qtohs(UShort x)102 static UShort qtohs (UShort x)
103 {
104     /* The PCX image format expects data to be in Intel (Little-endian) format. */
105     if (!tkimg_IsIntel ()) {
106         return ((UShort)((((UShort)(x) & 0x00ff) << 8) | \
107                          (((UShort)(x) & 0xff00) >> 8)));
108     } else {
109         return x;
110     }
111 }
112 
113 /* Read 1 byte, representing an unsigned integer number. */
114 
115 #ifdef DEBUG_LOCAL
readUByte(tkimg_MFile * handle,UByte * b)116 static Boln readUByte (tkimg_MFile *handle, UByte *b)
117 {
118     char buf[1];
119     if (1 != tkimg_Read2(handle, (char *) buf, 1)) {
120         return FALSE;
121     }
122     *b = buf[0];
123     return TRUE;
124 }
125 #else
126     /* Use this macro for better performance, esp. when reading RLE files. */
127 #   define readUByte(h,b) (1 == tkimg_Read2((h),(char *)(b),1))
128 #endif
129 
130 /* Write 1 byte, representing an unsigned integer to a file. */
131 
writeUByte(tkimg_MFile * handle,UByte b)132 static Boln writeUByte (tkimg_MFile *handle, UByte b)
133 {
134     UByte buf[1];
135     buf[0] = b;
136     if (1 != tkimg_Write2(handle, (const char *)buf, 1)) {
137         return FALSE;
138     }
139     return TRUE;
140 }
141 
read_pcx_header(tkimg_MFile * ifp,PCXHEADER * pcxhdr)142 static Boln read_pcx_header (tkimg_MFile *ifp, PCXHEADER *pcxhdr)
143 {
144     if (tkimg_Read2(ifp, (char *)pcxhdr, 128) != 128) {
145         return FALSE;
146     }
147 
148     if (pcxhdr->manufacturer != 10) {
149         return FALSE;
150     }
151     if (pcxhdr->bpp != 1 && pcxhdr->bpp != 8) {
152         return FALSE;
153     }
154     if (pcxhdr->planes != 1 && pcxhdr->planes != 3 && pcxhdr->planes != 4) {
155         return FALSE;
156     }
157     return TRUE;
158 }
159 
160 #define OUT Tcl_WriteChars (outChan, str, -1)
printImgInfo(PCXHEADER * ph,const char * filename,const char * msg)161 static void printImgInfo (PCXHEADER *ph, const char *filename, const char *msg)
162 {
163     Tcl_Channel outChan;
164     char str[256];
165     Int width, height;
166 
167     outChan = Tcl_GetStdChannel (TCL_STDOUT);
168     if (!outChan) {
169         return;
170     }
171     width  = qtohs (ph->x2) - qtohs (ph->x1) + 1;
172     height = qtohs (ph->y2) - qtohs (ph->y1) + 1;
173 
174     sprintf(str, "%s %s\n", msg, filename);                                 OUT;
175     sprintf(str, "\tSize in pixel   : %d x %d\n", width, height);           OUT;
176     sprintf(str, "\tNo. of channels : %d\n", ph->planes);                   OUT;
177     sprintf(str, "\tBits per pixel  : %d\n", ph->bpp);                      OUT;
178     sprintf(str, "\tBytes per line  : %d\n", ph->bytesperline);             OUT;
179     sprintf(str, "\tRLE compression : %s\n", ph->compression? "yes": "no"); OUT;
180     Tcl_Flush(outChan);
181 }
182 #undef OUT
183 
readline(tkimg_MFile * handle,UByte * buffer,Int bytes,Int compr)184 static Boln readline (tkimg_MFile *handle, UByte *buffer, Int bytes, Int compr)
185 {
186     static UByte count = 0, value = 0;
187 
188     if (compr) {
189         while (bytes--) {
190             if (count == 0) {
191                 if (!readUByte (handle, &value)) {
192                     return TRUE;
193                 }
194                 if (value < 0xc0) {
195                     count = 1;
196                 } else {
197                     count = value - 0xc0;
198                     if (!readUByte (handle, &value)) {
199                         return TRUE;
200                     }
201                 }
202             }
203             count--;
204             *(buffer++) = value;
205         }
206     } else {
207         if (bytes != tkimg_Read2(handle, (char *)buffer, bytes)) {
208             return FALSE;
209         }
210     }
211     return TRUE;
212 }
213 
writeline(tkimg_MFile * handle,UByte * buffer,Int bytes)214 static Boln writeline (tkimg_MFile *handle, UByte *buffer, Int bytes)
215 {
216     UByte value, count;
217     UByte *finish = buffer + bytes;
218 
219     while (buffer < finish) {
220         value = *(buffer++);
221         count = 1;
222 
223         while (buffer < finish && count < 63 && *buffer == value) {
224             count++;
225             buffer++;
226         }
227 
228         if (value < 0xc0 && count == 1) {
229             if (!writeUByte (handle, value)) {
230                 return FALSE;
231             }
232         } else {
233             if (!writeUByte (handle, 0xc0 + count)) {
234                 return FALSE;
235             }
236             if (!writeUByte (handle, value)) {
237                 return FALSE;
238             }
239         }
240     }
241     return TRUE;
242 }
243 
load_8(Tcl_Interp * interp,tkimg_MFile * ifp,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY,int fileWidth,int fileHeight,int bytesPerLine,int compr)244 static Boln load_8 (Tcl_Interp *interp, tkimg_MFile *ifp,
245                     Tk_PhotoHandle imageHandle, int destX, int destY,
246                     int width, int height, int srcX, int srcY,
247                     int fileWidth, int fileHeight, int bytesPerLine, int compr)
248 {
249     Int x, y;
250     Int stopY, outY;
251     Tk_PhotoImageBlock block;
252     UByte *line, *buffer, *indBuf, *indBufPtr;
253     UByte cmap[768], sepChar;
254     Boln haveColormap = FALSE;
255     Boln result = TRUE;
256     char errMsg[200];
257 
258     line   = (UByte *) ckalloc (bytesPerLine);
259     buffer = (UByte *) ckalloc (fileWidth * 3);
260     indBuf = (UByte *) ckalloc (fileWidth * fileHeight);
261     indBufPtr = indBuf;
262 
263     block.pixelSize = 3;
264     block.pitch = fileWidth * 3;
265     block.width = width;
266     block.height = 1;
267     block.offset[0] = 0;
268     block.offset[1] = 1;
269     block.offset[2] = 2;
270     block.offset[3] = 0;
271 
272     block.pixelPtr = buffer + srcX * 3;
273 
274     stopY = srcY + height;
275     outY  = destY;
276 
277     /* Read in the whole image data as indices. */
278     for (y=0; y<stopY; y++) {
279         if (!readline (ifp, line, bytesPerLine, compr)) {
280             ckfree ((char *) line);
281             ckfree ((char *) buffer);
282             ckfree ((char *) indBuf);
283             sprintf(errMsg, "Unexpected end-of-file while scanline %d", y);
284             Tcl_AppendResult(interp, errMsg, (char *)NULL);
285             return FALSE;
286         }
287         memcpy (indBufPtr, line, fileWidth);
288         indBufPtr += fileWidth;
289     }
290     /* Read the colormap: 256 entries a 3 values for RGB */
291     if (tkimg_Read2(ifp, (char *)&sepChar, 1) == 1) {
292         if (sepChar == 12) {
293             /* A colormap is available, if sepChar equals 0x0C */
294             if (tkimg_Read2(ifp, (char *)&cmap, 768) != 768) {
295                 ckfree ((char *) line);
296                 ckfree ((char *) buffer);
297                 ckfree ((char *) indBuf);
298                 Tcl_AppendResult (interp, "Unexpected end-of-file while reading colormap",
299                                  (char *) NULL);
300                 return FALSE;
301             }
302             haveColormap = TRUE;
303         }
304     }
305 
306     for (y=srcY; y<stopY; y++) {
307         if (haveColormap) {
308             /* An indexed colormap image */
309             for (x=0; x<fileWidth; x++) {
310                 buffer[x * 3 + 0] = cmap[indBuf[y*fileWidth + x]*3 + 0 ];
311                 buffer[x * 3 + 1] = cmap[indBuf[y*fileWidth + x]*3 + 1 ];
312                 buffer[x * 3 + 2] = cmap[indBuf[y*fileWidth + x]*3 + 2 ];
313             }
314         } else {
315             /* A grey-scale image */
316             for (x=0; x<fileWidth; x++) {
317                 buffer[x * 3 + 0] = indBuf[y*fileWidth + x];
318                 buffer[x * 3 + 1] = indBuf[y*fileWidth + x];
319                 buffer[x * 3 + 2] = indBuf[y*fileWidth + x];
320             }
321         }
322         if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, width, 1,
323             TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
324             result = FALSE;
325             break;
326         }
327         outY++;
328     }
329     ckfree ((char *) line);
330     ckfree ((char *) buffer);
331     ckfree ((char *) indBuf);
332     return result;
333 }
334 
load_24(Tcl_Interp * interp,tkimg_MFile * ifp,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY,int fileWidth,int fileHeight,int bytesPerLine,int compr)335 static Boln load_24 (Tcl_Interp *interp, tkimg_MFile *ifp,
336                      Tk_PhotoHandle imageHandle, int destX, int destY,
337                      int width, int height, int srcX, int srcY,
338                      int fileWidth, int fileHeight, int bytesPerLine, int compr)
339 {
340     Int x, y, c;
341     Int stopY, outY;
342     Tk_PhotoImageBlock block;
343     UByte *line, *buffer;
344     Boln result = TRUE;
345 
346     line   = (UByte *) ckalloc (bytesPerLine);
347     buffer = (UByte *) ckalloc (fileWidth * 3);
348 
349     block.pixelSize = 3;
350     block.pitch = fileWidth * 3;
351     block.width = width;
352     block.height = 1;
353     block.offset[0] = 0;
354     block.offset[1] = 1;
355     block.offset[2] = 2;
356     block.offset[3] = 0;
357 
358     block.pixelPtr = buffer + srcX * 3;
359 
360     stopY = srcY + height;
361     outY  = destY;
362 
363     for (y=0; y<stopY; y++) {
364         for (c=0; c<3; c++) {
365             if (!readline (ifp, line, bytesPerLine, compr)) {
366                 ckfree ((char *) line);
367                 ckfree ((char *) buffer);
368                 return FALSE;
369             }
370             for (x=0; x<fileWidth; x++) {
371                 buffer[x * 3 + c] = line[x];
372             }
373         }
374         if (y >= srcY) {
375             if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, width, 1,
376                 TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
377                 result = FALSE;
378                 break;
379             }
380             outY++;
381         }
382     }
383     ckfree ((char *) line);
384     ckfree ((char *) buffer);
385     return result;
386 }
387 
load_1(Tcl_Interp * interp,tkimg_MFile * ifp,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY,int fileWidth,int fileHeight,int bytesPerLine,int compr)388 static Boln load_1 (Tcl_Interp *interp, tkimg_MFile *ifp,
389                     Tk_PhotoHandle imageHandle, int destX, int destY,
390                     int width, int height, int srcX, int srcY,
391                     int fileWidth, int fileHeight, int bytesPerLine, int compr)
392 {
393     Int x, y;
394     Int stopY, outY;
395     Tk_PhotoImageBlock block;
396     UByte *line, *buffer;
397     Boln result = TRUE;
398 
399     line   = (UByte *) ckalloc (fileWidth);
400     buffer = (UByte *) ckalloc (fileWidth * 1);
401 
402     block.pixelSize = 1;
403     block.pitch = fileWidth * 1;
404     block.width = width;
405     block.height = 1;
406     block.offset[0] = 0;
407     block.offset[1] = 0;
408     block.offset[2] = 0;
409     block.offset[3] = 0;
410 
411     block.pixelPtr = buffer + srcX * 1;
412 
413     stopY = srcY + height;
414     outY  = destY;
415 
416     for (y=0; y<stopY; y++) {
417         if (!readline (ifp, line, bytesPerLine, compr)) {
418             ckfree ((char *) line);
419             ckfree ((char *) buffer);
420             return FALSE;
421         }
422         for (x=0; x<fileWidth; x++) {
423             if (line[x/8] & (128 >> (x%8))) {
424                 buffer[x] = 255;
425             } else {
426                 buffer[x] = 0;
427             }
428         }
429         if (y >= srcY) {
430             if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, width, 1,
431                 TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
432                 result = FALSE;
433                 break;
434             }
435             outY++;
436         }
437     }
438     ckfree ((char *) line);
439     ckfree ((char *) buffer);
440     return result;
441 }
442 
443 /*
444  * Prototypes for local procedures defined in this file:
445  */
446 
447 static int ParseFormatOpts(Tcl_Interp *interp, Tcl_Obj *format, FMTOPT *opts);
448 static int CommonMatch(tkimg_MFile *handle, int *widthPtr,
449                  int *heightPtr, PCXHEADER *pcxHeaderPtr);
450 static int CommonRead(Tcl_Interp *interp, tkimg_MFile *handle,
451                  const char *filename, Tcl_Obj *format,
452                  Tk_PhotoHandle imageHandle, int destX, int destY,
453                  int width, int height, int srcX, int srcY);
454 static int CommonWrite(Tcl_Interp *interp,
455                  const char *filename, Tcl_Obj *format,
456                  tkimg_MFile *handle, Tk_PhotoImageBlock *blockPtr);
457 
ParseFormatOpts(Tcl_Interp * interp,Tcl_Obj * format,FMTOPT * opts)458 static int ParseFormatOpts(
459     Tcl_Interp *interp,
460     Tcl_Obj *format,
461     FMTOPT *opts
462 ) {
463     static const char *const pcxOptions[] = {
464         "-compression", "-verbose", NULL
465     };
466     int objc, i, index;
467     char *optionStr;
468     Tcl_Obj **objv;
469     int boolVal;
470 
471     /* Initialize options with default values. */
472     opts->compression = 1;
473     opts->verbose     = 0;
474 
475     if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK) {
476         return TCL_ERROR;
477     }
478     if (objc) {
479         for (i=1; i<objc; i++) {
480             if (Tcl_GetIndexFromObj(interp, objv[i], (const char *CONST86 *)pcxOptions,
481                     "format option", 0, &index) != TCL_OK) {
482                 return TCL_ERROR;
483             }
484             if (++i >= objc) {
485                 Tcl_AppendResult(interp, "No value for option \"",
486                         Tcl_GetStringFromObj (objv[--i], (int *) NULL),
487                         "\"", (char *) NULL);
488                 return TCL_ERROR;
489             }
490             optionStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
491             switch(index) {
492                 case 0:
493                     if (!strncmp (optionStr, "none", strlen ("none"))) {
494                         opts->compression = 0;
495                     } else if (!strncmp (optionStr, "rle", strlen ("rle"))) {
496                         opts->compression = 1;
497                     } else {
498                         Tcl_AppendResult (interp, "Invalid compression mode \"", optionStr,
499                                           "\": Must be \"none\" or \"rle\"", (char *) NULL);
500                         return TCL_ERROR;
501                     }
502                     break;
503                 case 1:
504                     if (Tcl_GetBoolean(interp, optionStr, &boolVal) == TCL_ERROR) {
505                         Tcl_AppendResult (interp, "Invalid verbose mode \"", optionStr,
506                                           "\": should be 1 or 0, on or off, true or false",
507                                           (char *) NULL);
508                         return TCL_ERROR;
509                     }
510                     opts->verbose = boolVal;
511                     break;
512             }
513         }
514     }
515     return TCL_OK;
516 }
517 
ChnMatch(Tcl_Channel chan,const char * filename,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)518 static int ChnMatch(
519     Tcl_Channel chan,
520     const char *filename,
521     Tcl_Obj *format,
522     int *widthPtr,
523     int *heightPtr,
524     Tcl_Interp *interp
525 ) {
526     tkimg_MFile handle;
527 
528     handle.data = (char *) chan;
529     handle.state = IMG_CHAN;
530 
531     return CommonMatch(&handle, widthPtr, heightPtr, NULL);
532 }
533 
ObjMatch(Tcl_Obj * data,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)534 static int ObjMatch(
535     Tcl_Obj *data,
536     Tcl_Obj *format,
537     int *widthPtr,
538     int *heightPtr,
539     Tcl_Interp *interp
540 ) {
541     tkimg_MFile handle;
542 
543     if (!tkimg_ReadInit(data, 10, &handle)) {
544         return 0;
545     }
546     return CommonMatch(&handle, widthPtr, heightPtr, NULL);
547 }
548 
CommonMatch(tkimg_MFile * handle,int * widthPtr,int * heightPtr,PCXHEADER * pcxHeaderPtr)549 static int CommonMatch(
550     tkimg_MFile *handle,
551     int   *widthPtr,
552     int   *heightPtr,
553     PCXHEADER *pcxHeaderPtr
554 ) {
555     PCXHEADER ph;
556     Int offset_x, offset_y;
557 
558     if (!read_pcx_header (handle, &ph)) {
559         return 0;
560     }
561 
562     offset_x = qtohs (ph.x1);
563     offset_y = qtohs (ph.y1);
564 
565     if (offset_x < 0 || offset_y < 0) {
566         return 0;
567     }
568 
569     *widthPtr  = qtohs (ph.x2) - offset_x + 1;
570     *heightPtr = qtohs (ph.y2) - offset_y + 1;
571 
572     if (*widthPtr < 1 || *heightPtr < 1) {
573         return 0;
574     }
575 
576     if (pcxHeaderPtr) {
577         *pcxHeaderPtr = ph;
578     }
579     return 1;
580 }
581 
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)582 static int ChnRead(
583     Tcl_Interp *interp,         /* Interpreter to use for reporting errors. */
584     Tcl_Channel chan,           /* The image channel, open for reading. */
585     const char *filename,       /* The name of the image file. */
586     Tcl_Obj *format,            /* User-specified format object, or NULL. */
587     Tk_PhotoHandle imageHandle, /* The photo image to write into. */
588     int destX, int destY,       /* Coordinates of top-left pixel in
589                                  * photo image to be written to. */
590     int width, int height,      /* Dimensions of block of photo image to
591                                  * be written to. */
592     int srcX, int srcY          /* Coordinates of top-left pixel to be used
593                                  * in image being read. */
594 ) {
595     tkimg_MFile handle;
596 
597     handle.data = (char *) chan;
598     handle.state = IMG_CHAN;
599 
600     return CommonRead (interp, &handle, filename, format,
601                        imageHandle, destX, destY,
602                        width, height, srcX, srcY);
603 }
604 
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)605 static int ObjRead(
606     Tcl_Interp *interp,
607     Tcl_Obj *data,
608     Tcl_Obj *format,
609     Tk_PhotoHandle imageHandle,
610     int destX, int destY,
611     int width, int height,
612     int srcX, int srcY
613 ) {
614     tkimg_MFile handle;
615 
616     tkimg_ReadInit (data, 10, &handle);
617     return CommonRead (interp, &handle, "InlineData", format, imageHandle,
618                        destX, destY, width, height, srcX, srcY);
619 }
620 
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)621 static int CommonRead(
622     Tcl_Interp *interp,         /* Interpreter to use for reporting errors. */
623     tkimg_MFile *handle,        /* The image file, open for reading. */
624     const char *filename,       /* The name of the image file. */
625     Tcl_Obj *format,            /* User-specified format object, or NULL. */
626     Tk_PhotoHandle imageHandle, /* The photo image to write into. */
627     int destX, int destY,       /* Coordinates of top-left pixel in
628                                  * photo image to be written to. */
629     int width, int height,      /* Dimensions of block of photo image to
630                                  * be written to. */
631     int srcX, int srcY          /* Coordinates of top-left pixel to be used
632                                  * in image being read. */
633 ) {
634     int fileWidth, fileHeight;
635     int outWidth, outHeight;
636     int retCode = TCL_OK;
637     PCXHEADER ph;
638     FMTOPT opts;
639     char errMsg[200];
640 
641     if (ParseFormatOpts(interp, format, &opts) != TCL_OK) {
642         return TCL_ERROR;
643     }
644 
645     CommonMatch(handle, &fileWidth, &fileHeight, &ph);
646     if (opts.verbose) {
647         printImgInfo (&ph, filename, "Reading image:");
648     }
649 
650     if ((srcX + width) > fileWidth) {
651         outWidth = fileWidth - srcX;
652     } else {
653         outWidth = width;
654     }
655     if ((srcY + height) > fileHeight) {
656         outHeight = fileHeight - srcY;
657     } else {
658         outHeight = height;
659     }
660     if ((outWidth <= 0) || (outHeight <= 0)
661         || (srcX >= fileWidth) || (srcY >= fileHeight)) {
662         return TCL_OK;
663     }
664 
665     if (tkimg_PhotoExpand(interp, imageHandle, destX + outWidth, destY + outHeight) == TCL_ERROR) {
666         return TCL_ERROR;
667     }
668 
669     if (ph.compression) {
670         tkimg_ReadBuffer (1);
671     }
672 
673     if (ph.planes == 1 && ph.bpp == 1) {
674         if (!load_1 (interp, handle, imageHandle, destX, destY,
675                      outWidth, outHeight, srcX, srcY, fileWidth, fileHeight,
676                      qtohs (ph.bytesperline), ph.compression)) {
677             retCode = TCL_ERROR;
678         }
679     } else if (ph.planes == 4 && ph.bpp == 1) {
680         Tcl_AppendResult(interp, "Format (4 channels, 1 bit per channel) ",
681                           "is not supported yet.", (char *)NULL);
682         retCode = TCL_ERROR;
683     } else if (ph.planes == 1 && ph.bpp == 8) {
684         if (!load_8 (interp, handle, imageHandle, destX, destY,
685                      outWidth, outHeight, srcX, srcY, fileWidth, fileHeight,
686                      qtohs (ph.bytesperline), ph.compression)) {
687             retCode = TCL_ERROR;
688         }
689     } else if (ph.planes == 3 && ph.bpp == 8) {
690         if (!load_24 (interp, handle, imageHandle, destX, destY,
691                       outWidth, outHeight, srcX, srcY, fileWidth, fileHeight,
692                       qtohs (ph.bytesperline), ph.compression)) {
693             retCode = TCL_ERROR;
694         }
695     } else {
696         sprintf(errMsg, "Image has invalid channel/bpp combination: (%d, %d)",
697                           ph.planes, ph.bpp);
698         Tcl_AppendResult(interp, errMsg, (char *)NULL);
699         retCode = TCL_ERROR;
700     }
701     tkimg_ReadBuffer (0);
702     return retCode;
703 }
704 
ChnWrite(interp,filename,format,blockPtr)705 static int ChnWrite (interp, filename, format, blockPtr)
706     Tcl_Interp *interp;
707     const char *filename;
708     Tcl_Obj *format;
709     Tk_PhotoImageBlock *blockPtr;
710 {
711     Tcl_Channel chan;
712     tkimg_MFile handle;
713     int result;
714 
715     chan = tkimg_OpenFileChannel (interp, filename, 0644);
716     if (!chan) {
717         return TCL_ERROR;
718     }
719 
720     handle.data = (char *) chan;
721     handle.state = IMG_CHAN;
722 
723     result = CommonWrite (interp, filename, format, &handle, blockPtr);
724     if (Tcl_Close(interp, chan) == TCL_ERROR) {
725         return TCL_ERROR;
726     }
727     return result;
728 }
729 
StringWrite(Tcl_Interp * interp,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)730 static int StringWrite(
731     Tcl_Interp *interp,
732     Tcl_Obj *format,
733     Tk_PhotoImageBlock *blockPtr
734 ) {
735     tkimg_MFile handle;
736     int result;
737     Tcl_DString data;
738 
739     Tcl_DStringInit(&data);
740     tkimg_WriteInit(&data, &handle);
741     result = CommonWrite (interp, "InlineData", format, &handle, blockPtr);
742     tkimg_Putc(IMG_DONE, &handle);
743 
744     if (result == TCL_OK) {
745         Tcl_DStringResult(interp, &data);
746     } else {
747         Tcl_DStringFree(&data);
748     }
749     return result;
750 }
751 
CommonWrite(Tcl_Interp * interp,const char * filename,Tcl_Obj * format,tkimg_MFile * handle,Tk_PhotoImageBlock * blockPtr)752 static int CommonWrite(
753     Tcl_Interp *interp,
754     const char *filename,
755     Tcl_Obj *format,
756     tkimg_MFile *handle,
757     Tk_PhotoImageBlock *blockPtr
758 ) {
759     int     x, y, nchan, nBytes;
760     int     redOffset, greenOffset, blueOffset, alphaOffset;
761     UByte   *pixelPtr, *pixRowPtr;
762     PCXHEADER ph;
763     UByte *row;
764     FMTOPT opts;
765     char errMsg[200];
766 
767     if (ParseFormatOpts(interp, format, &opts) != TCL_OK) {
768         return TCL_ERROR;
769     }
770 
771     redOffset   = 0;
772     greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
773     blueOffset  = blockPtr->offset[2] - blockPtr->offset[0];
774     alphaOffset = blockPtr->offset[0];
775 
776     if (alphaOffset < blockPtr->offset[2]) {
777         alphaOffset = blockPtr->offset[2];
778     }
779     if (++alphaOffset < blockPtr->pixelSize) {
780         alphaOffset -= blockPtr->offset[0];
781     } else {
782         alphaOffset = 0;
783     }
784 
785     nchan  = 3;
786     nBytes = blockPtr->width * nchan;
787 
788     /* Fill the PCX header struct and write the header to the channel. */
789     memset (&ph, 0, sizeof (PCXHEADER));
790     ph.manufacturer = 0x0a;
791     ph.version = 5;
792     ph.compression = opts.compression;
793     ph.bpp = 8;
794     ph.planes = 3;
795     ph.color = htoqs (1);
796     ph.bytesperline = htoqs (blockPtr->width);
797     ph.x1 = htoqs (0);
798     ph.y1 = htoqs (0);
799     ph.x2 = htoqs (blockPtr->width  - 1);
800     ph.y2 = htoqs (blockPtr->height - 1);
801 
802     ph.hdpi = htoqs (300);
803     ph.vdpi = htoqs (300);
804     ph.reserved = 0;
805 
806     if (tkimg_Write2(handle, (const char *)&ph, 128) != 128) {
807         Tcl_AppendResult(interp, "Can't write PCX header.", (char *)NULL);
808         return TCL_ERROR;
809     }
810 
811     row = (UByte *) ckalloc (nBytes);
812     /* Now write out the image data. */
813     pixRowPtr = blockPtr->pixelPtr + blockPtr->offset[0];
814     if (!opts.compression) {
815         for (y=0; y<blockPtr->height; y++) {
816             pixelPtr = pixRowPtr;
817             for (x=0; x<blockPtr->width; x++) {
818                 row[x + 0*blockPtr->width] = pixelPtr[redOffset];
819                 row[x + 1*blockPtr->width] = pixelPtr[greenOffset];
820                 row[x + 2*blockPtr->width] = pixelPtr[blueOffset];
821                 pixelPtr += blockPtr->pixelSize;
822             }
823             if (nBytes != tkimg_Write2(handle, (const char *)row, nBytes)) {
824                 sprintf(errMsg, "Can't write %d bytes to image file.", nBytes);
825                 Tcl_AppendResult(interp, errMsg, (char *)NULL);
826                 ckfree ((char *)row);
827                 return TCL_ERROR;
828             }
829             pixRowPtr += blockPtr->pitch;
830         }
831     } else {                    /* RLE compression */
832         for (y = 0; y < blockPtr->height; y++) {
833             pixelPtr = pixRowPtr;
834             for (x = 0; x < blockPtr->width; x++) {
835                 row[x + 0*blockPtr->width] = pixelPtr[redOffset];
836                 row[x + 1*blockPtr->width] = pixelPtr[greenOffset];
837                 row[x + 2*blockPtr->width] = pixelPtr[blueOffset];
838                 pixelPtr += blockPtr->pixelSize;
839             }
840             if (!writeline (handle, row, nBytes)) {
841                 sprintf(errMsg, "Can't write %d bytes to image file.", nBytes);
842                 Tcl_AppendResult(interp, errMsg, (char *)NULL);
843                 ckfree ((char *)row);
844                 return TCL_ERROR;
845             }
846             pixRowPtr += blockPtr->pitch;
847         }
848     }
849     if (opts.verbose) {
850         printImgInfo (&ph, filename, "Saving image:");
851     }
852     ckfree ((char *)row);
853     return TCL_OK;
854 }
855