1/* STARTHEADER
2 *
3 * File :       tga.c
4 *
5 * Author :     Paul Obermeier (paul@poSoft.de)
6 *
7 * Date :       Wed Nov 22 21:45:17 CET 2000
8 *
9 * Copyright :  (C) 2000-2002 Paul Obermeier
10 *
11 * Description :
12 *
13 * A photo image handler for Truevision's TARGA file format.
14 *
15 * The following image types are supported:
16 *
17 * 24-bit pixels: True-color (RGB, each channel 8 bit).
18 * 32-bit pixels: True-color with alpha channel (RGBA, each channel 8 bit).
19 *
20 * List of currently supported features:
21 *
22 * Type   |     Read      |     Write     |
23 *        | -file | -data | -file | -data |
24 * ----------------------------------------
25 * 24-bit | Yes   | Yes   | Yes   | Yes   |
26 * 32-bit | Yes   | Yes   | Yes   | Yes   |
27 *
28 * All images types may be either uncompressed (Targa-Type 2) or
29 * run-length encoded (Targa-Type 10).
30 *
31 *
32 * The following format options are available:
33 *
34 * Read  TGA image: "tga -matte <bool> -verbose <bool>"
35 * Write TGA image: "tga -matte <bool> -verbose <bool> -compression <type>"
36 *
37 * -matte <bool>:       If set to false, a matte (alpha) channel is ignored
38 *                      during reading or writing. Default is true.
39 * -verbose <bool>:     If set to true, additional information about the file
40 *                      format is printed to stdout. Default is false.
41 * -compression <type>: Set the compression mode to either "none" or "rle".
42 * 			Default is "rle".
43 *
44 * Notes:
45 *
46 * - As Targa files do not have a "magic number" somewhere in the file header,
47 *   it is difficult to automatically recognize this format.
48 *   Therefore it should be specified as one of the first entries in the list of
49 *   package require tkimg::*.
50 *
51 * ENDHEADER
52 *
53 * $Id: tga.c 233 2010-04-01 09:28:00Z nijtmans $
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
69typedef unsigned char Boln;	/* Boolean value: TRUE or FALSE */
70typedef unsigned char UByte;	/* Unsigned  8 bit integer */
71typedef char  Byte;		/* Signed    8 bit integer */
72typedef short Short;		/* Signed   16 bit integer */
73typedef int Int;		/* Signed   32 bit integer */
74
75/* Supported TARGA version numbers */
76#define TGA_RGB_UNCOMP	 2
77#define TGA_RGB_COMP	10
78
79/* Macros needed for run-length encoding. */
80#define TGA_MODE_SAME	0
81#define TGA_MODE_DIFF	1
82#define MINRUN   3
83#define MAXRUN 127
84
85/* Macros for acessing header fields */
86#define ENC_LEFT_RIGHT(imgdes) (((imgdes >> 4) & 0x1)? FALSE: TRUE)
87#define ENC_TOP_BOTTOM(imgdes) (((imgdes >> 5) & 0x1)? TRUE: FALSE)
88#define NCHAN(pixsize)         ((pixsize == 24) ? 3: 4)
89#define IS_COMPRESSED(imgtyp)  ((imgtyp == TGA_RGB_COMP)? TRUE: FALSE)
90
91/* The Targa header structure */
92typedef struct {
93    UByte numid;
94    UByte maptyp;
95    UByte imgtyp;
96    Short maporig;
97    Short mapsize;
98    UByte mapbits;
99    Short xorig;
100    Short yorig;
101    Short xsize;
102    Short ysize;
103    UByte pixsize;
104    UByte imgdes;
105} TGAHEADER;
106
107/* Structure to hold information about the Targa file being processed. */
108typedef struct {
109    TGAHEADER th;
110    Int   scanrest,	/* Number of pixels belonging to next scanline */
111	  scanmode;	/* Current compression mode */
112    UByte *red,		/* Pointers to step through scanlines */
113	  *green,
114	  *blue,
115	  *matte;
116    UByte *redScan,	/* Buffer for one scanline: Red   channel */
117	  *greenScan,	/* Buffer for one scanline: Green channel */
118	  *blueScan,	/* Buffer for one scanline: Blue  channel */
119	  *matteScan;	/* Buffer for one scanline: Matte channel */
120    UByte *pixbuf;
121#ifdef DEBUG_LOCAL
122	Int total;
123#endif
124} TGAFILE;
125
126static void tgaClose(TGAFILE *tf)
127{
128    if (tf->redScan)   ckfree((char *)tf->redScan);
129    if (tf->greenScan) ckfree((char *)tf->greenScan);
130    if (tf->blueScan)  ckfree((char *)tf->blueScan);
131    if (tf->matteScan) ckfree((char *)tf->matteScan);
132    if (tf->pixbuf)    ckfree((char *)tf->pixbuf);
133    return;
134}
135
136static Boln readError(Tcl_Interp *interp)
137{
138    Tcl_AppendResult(interp, "Unexpected end of file", (char *) NULL);
139    return FALSE;
140}
141
142/* This function is commented out because it is not used anywhere
143static Boln writeError(Tcl_Interp *interp)
144{
145    Tcl_AppendResult(interp, "Error writing to file", (char *) NULL);
146    return FALSE;
147}
148*/
149
150/* Read 1 byte, representing an unsigned integer number. */
151
152static Boln readUByte (tkimg_MFile *handle, UByte *b)
153{
154    char buf[1];
155    if (1 != tkimg_Read(handle, buf, 1))
156        return FALSE;
157    *b = buf[0];
158    return TRUE;
159}
160
161/* Read 2 bytes, representing a short integer in the form <LowByte, HighByte>,
162   from a file and convert them into the current machine's format. */
163
164static Boln readShort (tkimg_MFile *handle, Short *s)
165{
166    char buf[2];
167    if (2 != tkimg_Read(handle, buf, 2))
168        return FALSE;
169    *s = (buf[0] & 0xFF) | (buf[1] << 8);
170    return TRUE;
171}
172
173/* Write a byte, representing an unsigned integer to a file. */
174
175static Boln writeUByte (tkimg_MFile *handle, UByte b)
176{
177    UByte buf[1];
178    buf[0] = b;
179    if (1 != tkimg_Write(handle, (const char *)buf, 1))
180        return FALSE;
181    return TRUE;
182}
183
184/* Write a byte, representing a signed integer to a file. */
185
186static Boln writeByte(tkimg_MFile *handle, Byte b)
187{
188    Byte buf[1];
189    buf[0] = b;
190    if (1 != tkimg_Write(handle, buf, 1))
191        return FALSE;
192    return TRUE;
193}
194
195/* Convert a short integer number into the format <LowByte, HighByte> (an array
196   of 2 bytes) and write the array to a file. */
197
198static Boln writeShort (tkimg_MFile *handle, Short s)
199{
200    Byte buf[2];
201    buf[0] = s;
202    buf[1] = s >> 8;
203    if (2 != tkimg_Write(handle, buf, 2))
204        return FALSE;
205    return TRUE;
206}
207
208#define OUT Tcl_WriteChars (outChan, str, -1)
209static void printImgInfo (TGAHEADER *th, const char *filename, const char *msg)
210{
211    Tcl_Channel outChan;
212    char str[256];
213
214    outChan = Tcl_GetStdChannel (TCL_STDOUT);
215    if (!outChan) {
216        return;
217    }
218
219    sprintf(str, "%s %s\n", msg, filename);                                   OUT;
220    sprintf(str, "\tSize in pixel      : %d x %d\n", th->xsize, th->ysize);   OUT;
221    sprintf(str, "\tNo. of channels    : %d\n", NCHAN(th->pixsize));          OUT;
222    sprintf(str, "\tCompression        : %s\n",
223		IS_COMPRESSED(th->imgtyp)? "RLE": "None");                     OUT;
224    sprintf(str, "\tVertical encoding  : %s\n",
225		ENC_TOP_BOTTOM(th->imgdes)? "Top -> Bottom": "Bottom -> Top"); OUT;
226    sprintf(str, "\tHorizontal encoding: %s\n",
227		ENC_LEFT_RIGHT(th->imgdes)? "Left -> Right": "Right -> Left"); OUT;
228    Tcl_Flush(outChan);
229}
230#undef OUT
231static Boln readHeader (tkimg_MFile *handle, TGAHEADER *th)
232{
233    Int i;
234    UByte dummy;
235
236    if (!readUByte (handle, &th->numid) ||
237	!readUByte (handle, &th->maptyp) ||
238	!readUByte (handle, &th->imgtyp) ||
239	!readShort (handle, &th->maporig) ||
240	!readShort (handle, &th->mapsize) ||
241	!readUByte (handle, &th->mapbits) ||
242	!readShort (handle, &th->xorig) ||
243	!readShort (handle, &th->yorig) ||
244	!readShort (handle, &th->xsize) ||
245	!readShort (handle, &th->ysize) ||
246	!readUByte (handle, &th->pixsize) ||
247	!readUByte (handle, &th->imgdes))
248	return FALSE;
249
250    /* Try to find out if this file can possibly be a TARGA pixel file. */
251    if (!((th->imgtyp == TGA_RGB_UNCOMP || th->imgtyp == TGA_RGB_COMP) &&
252	  (th->pixsize == 24 || th->pixsize == 32))) {
253	return FALSE;
254    }
255
256    for ( i=0; i<th->numid; i++) {
257	if (!readUByte (handle, &dummy))
258	    return FALSE;
259    }
260
261    if (th->xsize < 1 || th->ysize < 1) {
262	return FALSE;
263    }
264
265    /* Skip colormap data, if present. */
266    if (th->mapsize > 0)
267    {	Int   mapbytes;
268	switch (th->mapbits)
269	{
270	    case 15:
271	    case 16:
272		mapbytes = 2 * th->mapsize;
273		break;
274	    case 24:
275		mapbytes = 3 * th->mapsize;
276		break;
277	    case 32:
278		mapbytes = 4 * th->mapsize;
279		break;
280	    default:
281		return FALSE;
282	}
283	while (mapbytes--)
284	    if (!readUByte (handle, &dummy))
285		return FALSE;
286    }
287    return TRUE;
288}
289
290static Boln writeHeader(tkimg_MFile *handle, TGAHEADER *th)
291{
292    if (!writeUByte (handle, th->numid) ||
293	!writeUByte (handle, th->maptyp) ||
294	!writeUByte (handle, th->imgtyp) ||
295	!writeShort (handle, th->maporig) ||
296	!writeShort (handle, th->mapsize) ||
297	!writeUByte (handle, th->mapbits) ||
298	!writeShort (handle, th->xorig) ||
299	!writeShort (handle, th->yorig) ||
300	!writeShort (handle, th->xsize) ||
301	!writeShort (handle, th->ysize) ||
302	!writeUByte (handle, th->pixsize) ||
303	!writeUByte (handle, th->imgdes))
304	return FALSE;
305    return TRUE;
306}
307
308/* A pixel is represented by 3 or 4 bytes in the order Blue/Green/Red/Alpha.
309   We are converting the order into standard RGBA order.
310   Note that TARGA allows pixel values to be compressed across scanline
311   boundaries.
312*/
313
314/* Read the value of a pixel from "handle" and assume it must be repeated "n"
315   times.
316*/
317
318static Boln readRlePixel (Tcl_Interp *interp, tkimg_MFile *handle, UByte **pixBufPtr,
319                          Int *countPtr, Int stop, Int n, TGAFILE *tf)
320{
321    Int i, count, nchan;
322    UByte localBuf[4];
323
324    nchan = NCHAN(tf->th.pixsize);
325    if (nchan != tkimg_Read(handle, (char *)localBuf, nchan))
326	return readError (interp);
327    count = *countPtr;
328    for (i=0; i<n; i++)
329    {
330#ifdef DEBUG_LOCAL
331	    tf->total++;
332#endif
333	(*pixBufPtr)[0] = localBuf[2];
334	(*pixBufPtr)[1] = localBuf[1];
335	(*pixBufPtr)[2] = localBuf[0];
336	if (nchan == 4)
337	    (*pixBufPtr)[3] = localBuf[3];
338	(*pixBufPtr) += nchan;
339	count++;
340
341	if (count == stop)
342	{   /* Scanline is filled with pixel values.
343	       Determine the number of pixels to keep for next scanline. */
344	    tf->scanrest = n - i - 1;
345	    *countPtr = count;
346	    return TRUE;
347	}
348    }
349    *countPtr = count;
350    return TRUE;
351}
352
353/* The channels of scan line number "y" are read. */
354
355static Boln tgaReadScan (Tcl_Interp *interp, tkimg_MFile *handle,
356                         TGAFILE *tf, Int y)
357{
358    Int   nchan;
359    Int   count, stop;
360    UByte localBuf[4];
361    UByte *pixBufPtr;
362
363    count = 0;
364    stop  = tf->th.xsize;
365    nchan = NCHAN(tf->th.pixsize);
366    pixBufPtr = tf->pixbuf;
367
368#ifdef DEBUG_LOCAL
369	tf->total = 0;
370#endif
371
372    if (IS_COMPRESSED (tf->th.imgtyp)) {
373	Byte cbuf[1];
374	Int  pix, numpix;
375	/* While there are pixels left from the previous scanline,
376	   either fill the current scanline with the pixel value
377	   still stored in "pixbuf" (TGA_MODE_SAME) or read in the
378	   appropriate number of pixel values (TGA_MODE_DIFF). */
379	while (tf->scanrest) {
380	    if (tf->scanmode == TGA_MODE_DIFF) {
381		if (nchan != tkimg_Read(handle, (char *)localBuf, nchan))
382		    return readError (interp);
383	    }
384#ifdef DEBUG_LOCAL
385		tf->total++;
386#endif
387	    *pixBufPtr++ = localBuf[2];
388	    *pixBufPtr++ = localBuf[1];
389	    *pixBufPtr++ = localBuf[0];
390	    if (nchan == 4)
391		*pixBufPtr++ = localBuf[3];
392	    count++;
393
394	    tf->scanrest--;
395	    /* If the image is small, the compression might go over several
396	       scanlines. */
397	    if (count == stop)
398		return TRUE;
399	}
400
401	/* Read the byte telling us the compression mode and the compression
402	   count. Then read the pixel values till a scanline is filled. */
403	do {
404	    if (1 != tkimg_Read(handle, cbuf, 1))
405		return readError (interp);
406	    numpix = (cbuf[0] & 0x7F) + 1;
407
408	    if ((cbuf[0] & 0x80) != 0x80) {
409		tf->scanmode = TGA_MODE_DIFF;
410		for (pix=0; pix<numpix; pix++) {
411		    if (!readRlePixel (interp, handle, &pixBufPtr,
412				       &count, stop, 1, tf))
413			return FALSE;
414		    if (count == stop) {
415			tf->scanrest = numpix - pix - 1;
416			break;
417		    }
418		}
419	    } else {
420		tf->scanmode = TGA_MODE_SAME;
421		if (!readRlePixel (interp, handle, &pixBufPtr,
422				   &count, stop, numpix, tf))
423		    return FALSE;
424	    }
425	} while (count < stop);
426
427#ifdef DEBUG_LOCAL
428	    printf("\tScanline %d: Pixels: %d Rest: %d\n",
429		    y, tf->total, tf->scanrest);
430#endif
431    } else {
432	/* Read uncompressed pixel data. */
433	Int   i, bytesPerLine;
434	UByte curPix;
435
436	bytesPerLine = nchan * tf->th.xsize;
437	if (bytesPerLine != tkimg_Read(handle, (char *)tf->pixbuf, bytesPerLine))
438	    return readError (interp);
439
440	for (i=0; i<stop; i++) {
441	    curPix = pixBufPtr[2];
442	    pixBufPtr[2] = pixBufPtr[0];
443	    pixBufPtr[0] = curPix;
444	    pixBufPtr += nchan;
445	}
446    }
447    return TRUE;
448}
449
450static Boln writePixel(tkimg_MFile *handle, UByte b, UByte g,
451			UByte r, UByte m, Int nchan)
452{
453    UByte buf[4];
454    buf[0] = b;
455    buf[1] = g;
456    buf[2] = r;
457    buf[3] = m;
458    if (nchan != tkimg_Write(handle, (const char *)buf, nchan))
459	return FALSE;
460    return TRUE;
461}
462
463static Boln tgaWriteScan(Tcl_Interp *interp, tkimg_MFile *handle,
464			  TGAFILE *tf, Int y)
465{
466    UByte *stop, *red_end, *green_end, *blue_end, *matte_end;
467    Int nchan;
468
469    tf->red = tf->redScan;
470    tf->green = tf->greenScan;
471    tf->blue = tf->blueScan;
472    tf->matte = tf->matteScan;
473    stop = tf->red + tf->th.xsize;
474    nchan = NCHAN(tf->th.pixsize);
475
476    /* Write the scanline data to the file. */
477    if (! IS_COMPRESSED(tf->th.imgtyp)) {
478	while (tf->red < stop)
479	{
480	    if (!writePixel(handle, *tf->blue, *tf->green, *tf->red, *tf->matte, nchan))
481		return FALSE;
482	    tf->blue++;
483	    tf->green++;
484	    tf->red++;
485	    tf->matte++;
486	}
487    }
488    else	/* Run-length Compression */
489    {
490	red_end = tf->red + 1;
491	green_end = tf->green + 1;
492	blue_end = tf->blue + 1;
493	matte_end = tf->matte + 1;
494	while (tf->red < stop)
495	{
496	    while (red_end < stop &&
497		   *tf->red == *red_end &&
498		   *tf->green == *green_end &&
499		   *tf->blue == *blue_end &&
500		   red_end - tf->red - 1 < MAXRUN)
501	    {
502		if (nchan == 4)
503		{
504		    if (*tf->matte != *matte_end)
505			break;
506		}
507		red_end++;
508		green_end++;
509		blue_end++;
510		matte_end++;
511	    }
512	    if (red_end - tf->red >= MINRUN)
513	    {	/* Found a run of compressable data */
514		if (!writeByte(handle, (Byte)(((red_end - tf->red)-1)|0x80)) ||
515		    !writePixel(handle, *tf->blue, *tf->green, *tf->red, *tf->matte, nchan))
516		    return FALSE;
517		tf->red = red_end;
518		tf->green = green_end;
519		tf->blue = blue_end;
520		tf->matte = matte_end;
521	    }
522	    else
523	    {	/* Found a run of uncompressable data */
524		while (red_end < stop &&
525		       ((red_end + 1 >= stop ||
526			*red_end != *(red_end + 1)) ||
527			(red_end + 2 >= stop ||
528			*(red_end + 1) != *(red_end + 2))) &&
529		       ((green_end + 1 >= stop ||
530			*green_end != *(green_end + 1)) ||
531			(green_end + 2 >= stop ||
532			*(green_end + 1) != *(green_end + 2))) &&
533		       ((blue_end + 1 >= stop ||
534			*blue_end != *(blue_end + 1)) ||
535			(blue_end + 2 >= stop ||
536			*(blue_end + 1) != *(blue_end + 2))) &&
537			red_end - tf->red < MAXRUN)
538		{
539		    if (nchan == 4)
540		    {
541		        if (! ((matte_end + 1 >= stop ||
542			       *matte_end != *(matte_end + 1)) ||
543			       (matte_end + 2 >= stop ||
544			       *(matte_end + 1) != *(matte_end + 2))))
545			    break;
546		    }
547		    red_end++;
548		    green_end++;
549		    blue_end++;
550		    matte_end++;
551		}
552		if (!writeByte(handle, (Byte)((red_end - tf->red) - 1)))
553		    return FALSE;
554		while (tf->red < red_end)
555		{
556		    if (!writePixel(handle, *tf->blue, *tf->green, *tf->red, *tf->matte, nchan))
557			return FALSE;
558		    tf->red++;
559		    tf->green++;
560		    tf->blue++;
561		    tf->matte++;
562		}
563	    }
564	    red_end++;
565	    green_end++;
566	    blue_end++;
567	    matte_end++;
568	}
569    }
570    return TRUE;
571}
572
573/*
574 * Here is the start of the standard functions needed for every image format.
575 */
576
577/*
578 * Prototypes for local procedures defined in this file:
579 */
580
581static int ParseFormatOpts(Tcl_Interp *interp, Tcl_Obj *format,
582	int *comp, int *verb, int *matte);
583static int CommonMatch(tkimg_MFile *handle, int *widthPtr,
584	int *heightPtr, TGAHEADER *tgaHeaderPtr);
585static int CommonRead(Tcl_Interp *interp, tkimg_MFile *handle,
586	const char *filename, Tcl_Obj *format,
587	Tk_PhotoHandle imageHandle, int destX, int destY,
588	int width, int height, int srcX, int srcY);
589static int CommonWrite(Tcl_Interp *interp,
590	const char *filename, Tcl_Obj *format,
591	tkimg_MFile *handle, Tk_PhotoImageBlock *blockPtr);
592
593static int ParseFormatOpts(interp, format, comp, verb, matte)
594    Tcl_Interp *interp;
595    Tcl_Obj *format;
596    int *comp;
597    int *verb;
598    int *matte;
599{
600    static const char *const tgaOptions[] = {"-compression", "-verbose", "-matte"};
601    int objc, length, c, i, index;
602    Tcl_Obj **objv;
603    const char *compression, *verbose, *transp;
604
605    *comp = TGA_RGB_COMP;
606    *verb = 0;
607    *matte = 1;
608    if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK)
609	return TCL_ERROR;
610    if (objc) {
611	compression = "rle";
612	verbose     = "0";
613	transp      = "1";
614	for (i=1; i<objc; i++) {
615	    if (Tcl_GetIndexFromObj(interp, objv[i], (CONST84 char *CONST86 *)tgaOptions,
616		    "format option", 0, &index) != TCL_OK) {
617		return TCL_ERROR;
618	    }
619	    if (++i >= objc) {
620		Tcl_AppendResult(interp, "No value for option \"",
621			Tcl_GetStringFromObj (objv[--i], (int *) NULL),
622			"\"", (char *) NULL);
623		return TCL_ERROR;
624	    }
625	    switch(index) {
626		case 0:
627		    compression = Tcl_GetStringFromObj(objv[i], (int *) NULL);
628		    break;
629		case 1:
630		    verbose = Tcl_GetStringFromObj(objv[i], (int *) NULL);
631		    break;
632		case 2:
633		    transp = Tcl_GetStringFromObj(objv[i], (int *) NULL);
634		    break;
635	    }
636	}
637
638	c = compression[0]; length = strlen (compression);
639	if ((c == 'n') && (!strncmp (compression, "none", length))) {
640	    *comp = TGA_RGB_UNCOMP;
641	} else if ((c == 'r') && (!strncmp (compression, "rle",length))) {
642	    *comp = TGA_RGB_COMP;
643	} else {
644	    Tcl_AppendResult(interp, "invalid compression mode \"",
645		    compression, "\": should be rle or none", (char *) NULL);
646	    return TCL_ERROR;
647	}
648
649	c = verbose[0]; length = strlen (verbose);
650	if (!strncmp (verbose, "1", length) || \
651	    !strncmp (verbose, "true", length) || \
652	    !strncmp (verbose, "on", length)) {
653	    *verb = 1;
654	} else if (!strncmp (verbose, "0", length) || \
655	    !strncmp (verbose, "false", length) || \
656	    !strncmp (verbose, "off", length)) {
657	    *verb = 0;
658	} else {
659	    Tcl_AppendResult(interp, "invalid verbose mode \"", verbose,
660                              "\": should be 1 or 0, on or off, true or false",
661			      (char *) NULL);
662	    return TCL_ERROR;
663	}
664
665        c = transp[0]; length = strlen (transp);
666        if (!strncmp (transp, "1", length) || \
667            !strncmp (transp, "true", length) || \
668            !strncmp (transp, "on", length)) {
669            *matte = 1;
670        } else if (!strncmp (transp, "0", length) || \
671            !strncmp (transp, "false", length) || \
672            !strncmp (transp, "off", length)) {
673            *matte = 0;
674        } else {
675            Tcl_AppendResult(interp, "invalid alpha (matte) mode \"", verbose,
676                              "\": should be 1 or 0, on or off, true or false",
677                              (char *) NULL);
678            return TCL_ERROR;
679        }
680    }
681    return TCL_OK;
682}
683
684static int ChnMatch(
685    Tcl_Channel chan,
686    const char *filename,
687    Tcl_Obj *format,
688    int *widthPtr,
689    int *heightPtr,
690    Tcl_Interp *interp
691) {
692    tkimg_MFile handle;
693
694    handle.data = (char *) chan;
695    handle.state = IMG_CHAN;
696
697    return CommonMatch(&handle, widthPtr, heightPtr, NULL);
698}
699
700static int ObjMatch(
701    Tcl_Obj *data,
702    Tcl_Obj *format,
703    int *widthPtr,
704    int *heightPtr,
705    Tcl_Interp *interp
706) {
707    tkimg_MFile handle;
708
709    if (!tkimg_ReadInit (data, 0, &handle)) {
710        tkimg_ReadInit (data, '*', &handle);
711    }
712    return CommonMatch(&handle, widthPtr, heightPtr, NULL);
713}
714
715static int CommonMatch(handle, widthPtr, heightPtr, tgaHeaderPtr)
716    tkimg_MFile *handle;
717    int *widthPtr;
718    int *heightPtr;
719    TGAHEADER *tgaHeaderPtr;
720{
721    TGAHEADER th;
722
723    if (!readHeader (handle, &th))
724	return 0;
725
726    *widthPtr  = th.xsize;
727    *heightPtr = th.ysize;
728    if (tgaHeaderPtr)
729	*tgaHeaderPtr = th;
730    return 1;
731}
732
733static int ChnRead(interp, chan, filename, format, imageHandle,
734                    destX, destY, width, height, srcX, srcY)
735    Tcl_Interp *interp;         /* Interpreter to use for reporting errors. */
736    Tcl_Channel chan;           /* The image channel, open for reading. */
737    const char *filename;       /* The name of the image file. */
738    Tcl_Obj *format;            /* User-specified format object, or NULL. */
739    Tk_PhotoHandle imageHandle; /* The photo image to write into. */
740    int destX, destY;           /* Coordinates of top-left pixel in
741				 * photo image to be written to. */
742    int width, height;          /* Dimensions of block of photo image to
743				 * be written to. */
744    int srcX, srcY;             /* Coordinates of top-left pixel to be used
745			         * in image being read. */
746{
747    tkimg_MFile handle;
748
749    handle.data = (char *) chan;
750    handle.state = IMG_CHAN;
751
752    return CommonRead(interp, &handle, filename, format, imageHandle,
753                       destX, destY, width, height, srcX, srcY);
754}
755
756static int ObjRead(interp, data, format, imageHandle,
757	            destX, destY, width, height, srcX, srcY)
758    Tcl_Interp *interp;
759    Tcl_Obj *data;
760    Tcl_Obj *format;
761    Tk_PhotoHandle imageHandle;
762    int destX, destY;
763    int width, height;
764    int srcX, srcY;
765{
766    tkimg_MFile handle;
767
768    if (!tkimg_ReadInit(data, 0, &handle)) {
769        tkimg_ReadInit(data, '*', &handle);
770    }
771    return CommonRead(interp, &handle, "InlineData", format, imageHandle,
772                       destX, destY, width, height, srcX, srcY);
773}
774
775static int CommonRead(interp, handle, filename, format, imageHandle,
776                       destX, destY, width, height, srcX, srcY)
777    Tcl_Interp *interp;         /* Interpreter to use for reporting errors. */
778    tkimg_MFile *handle;        /* The image file, open for reading. */
779    const char *filename;       /* The name of the image file. */
780    Tcl_Obj *format;            /* User-specified format object, or NULL. */
781    Tk_PhotoHandle imageHandle; /* The photo image to write into. */
782    int destX, destY;           /* Coordinates of top-left pixel in
783				 * photo image to be written to. */
784    int width, height;          /* Dimensions of block of photo image to
785			         * be written to. */
786    int srcX, srcY;             /* Coordinates of top-left pixel to be used
787			         * in image being read. */
788{
789	Tk_PhotoImageBlock block;
790    Int y, nchan;
791    int fileWidth, fileHeight;
792    int stopY, outY, outWidth, outHeight;
793    TGAFILE tf;
794    int compr, verbose, matte;
795    char errMsg[200];
796    int result = TCL_OK;
797
798    memset (&tf, 0, sizeof (TGAFILE));
799    if (ParseFormatOpts (interp, format, &compr, &verbose, &matte) != TCL_OK) {
800	return TCL_ERROR;
801    }
802
803    if (!CommonMatch(handle, &fileWidth, &fileHeight, &tf.th))
804	return TCL_ERROR;
805    if (verbose)
806	printImgInfo (&tf.th, filename, "Reading image:");
807
808    if ((srcX + width) > fileWidth) {
809	outWidth = fileWidth - srcX;
810    } else {
811	outWidth = width;
812    }
813    if ((srcY + height) > fileHeight) {
814	outHeight = fileHeight - srcY;
815    } else {
816	outHeight = height;
817    }
818    if ((outWidth <= 0) || (outHeight <= 0)
819	|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
820	return TCL_OK;
821    }
822
823    if (tkimg_PhotoExpand(interp, imageHandle, destX + outWidth, destY + outHeight) == TCL_ERROR) {
824	return TCL_ERROR;
825    }
826
827    if (IS_COMPRESSED(tf.th.imgtyp)) {
828	tkimg_ReadBuffer(1);
829    }
830
831    tf.scanmode = TGA_MODE_DIFF;
832    nchan = NCHAN(tf.th.pixsize);
833
834    tf.pixbuf = (UByte *) ckalloc (fileWidth * nchan);
835    if (!tf.pixbuf) {
836	sprintf(errMsg, "Can't allocate memory of size %d", fileWidth * nchan);
837	Tcl_AppendResult(interp, errMsg, (char *)NULL);
838	tkimg_ReadBuffer (0);
839	return TCL_ERROR;
840    }
841
842    block.pixelSize = nchan;
843    block.pitch = fileWidth * nchan;
844    block.width = outWidth;
845    block.height = 1;
846    block.offset[0] = 0;
847    block.offset[1] = 1;
848    block.offset[2] = 2;
849    if (nchan < 4) {
850	matte = 0;
851    }
852    block.offset[3] = matte? 3: 0;
853    block.pixelPtr = tf.pixbuf + srcX * nchan;
854
855    stopY = srcY + outHeight;
856
857    if (ENC_TOP_BOTTOM (tf.th.imgdes)) {
858	outY = destY;
859	for (y=0; y<stopY; y++) {
860	    tgaReadScan(interp, handle, &tf, y);
861	    if (y >= srcY) {
862		if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, width, 1, matte? TK_PHOTO_COMPOSITE_OVERLAY: TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
863		    result = TCL_ERROR;
864		    break;
865		}
866		outY++;
867	    }
868	}
869    } else {
870	outY = destY + outHeight - 1;
871	for (y=fileHeight-1; y>=0; y--) {
872	    tgaReadScan(interp, handle, &tf, y);
873	    if (y >= srcY && y < stopY) {
874		if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
875		    result = TCL_ERROR;
876		    break;
877		}
878		outY--;
879	    }
880	}
881    }
882    tgaClose(&tf);
883    tkimg_ReadBuffer(0);
884    return result;
885}
886
887static int ChnWrite(interp, filename, format, blockPtr)
888    Tcl_Interp *interp;
889    const char *filename;
890    Tcl_Obj *format;
891    Tk_PhotoImageBlock *blockPtr;
892{
893    Tcl_Channel chan;
894    tkimg_MFile handle;
895    int result;
896
897    chan = tkimg_OpenFileChannel(interp, filename, 0644);
898    if (!chan) {
899	return TCL_ERROR;
900    }
901
902    handle.data = (char *) chan;
903    handle.state = IMG_CHAN;
904
905    result = CommonWrite(interp, filename, format, &handle, blockPtr);
906    if (Tcl_Close(interp, chan) == TCL_ERROR) {
907	return TCL_ERROR;
908    }
909    return result;
910}
911
912static int StringWrite(
913    Tcl_Interp *interp,
914    Tcl_Obj *format,
915    Tk_PhotoImageBlock *blockPtr
916) {
917    tkimg_MFile handle;
918    int result;
919    Tcl_DString data;
920
921    Tcl_DStringInit(&data);
922    tkimg_WriteInit(&data, &handle);
923    result = CommonWrite(interp, "InlineData", format, &handle, blockPtr);
924    tkimg_Putc(IMG_DONE, &handle);
925
926    if (result == TCL_OK) {
927	Tcl_DStringResult(interp, &data);
928    } else {
929	Tcl_DStringFree(&data);
930    }
931    return result;
932}
933
934static int CommonWrite (interp, filename, format, handle, blockPtr)
935    Tcl_Interp *interp;
936    const char *filename;
937    Tcl_Obj *format;
938    tkimg_MFile *handle;
939    Tk_PhotoImageBlock *blockPtr;
940{
941    Int     x, y, nchan;
942    Int     redOffset, greenOffset, blueOffset, alphaOffset;
943    UByte   *pixelPtr, *rowPixPtr;
944    TGAFILE tf;
945    int compr, verbose, matte; /* Format options */
946    char errMsg[200];
947
948    memset(&tf, 0, sizeof(TGAFILE));
949    if (ParseFormatOpts(interp, format, &compr, &verbose, &matte) != TCL_OK) {
950	return TCL_ERROR;
951    }
952
953    redOffset   = 0;
954    greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
955    blueOffset  = blockPtr->offset[2] - blockPtr->offset[0];
956    alphaOffset = blockPtr->offset[0];
957
958    if (alphaOffset < blockPtr->offset[2]) {
959        alphaOffset = blockPtr->offset[2];
960    }
961    if (++alphaOffset < blockPtr->pixelSize) {
962        alphaOffset -= blockPtr->offset[0];
963    } else {
964        alphaOffset = 0;
965    }
966
967    nchan = ((matte && alphaOffset)? 4: 3);
968
969    tf.redScan   = (UByte *) ckalloc(blockPtr->width);
970    tf.greenScan = (UByte *) ckalloc(blockPtr->width);
971    tf.blueScan  = (UByte *) ckalloc(blockPtr->width);
972    tf.matteScan = (UByte *) ckalloc(blockPtr->width);
973    if (!tf.redScan || !tf.greenScan || !tf.blueScan || !tf.matteScan) {
974	sprintf(errMsg, "Can't allocate memory of size %d", blockPtr->width);
975	Tcl_AppendResult(interp, errMsg, (char *)NULL);
976	return TCL_ERROR;
977    }
978
979    /* Fill the targa header struct and write the header to the channel. */
980    tf.th.pixsize = nchan * 8;
981    tf.th.xsize = blockPtr->width;
982    tf.th.ysize = blockPtr->height;
983    tf.th.imgdes = (1 << 5);    	/* Top->Bottom, Left->Right encoding */
984    tf.th.imgtyp = compr;		/* Uncompressed or RLE-compressed */
985
986    if (!writeHeader(handle, &tf.th)) {
987	return TCL_ERROR;
988    }
989
990    rowPixPtr = blockPtr->pixelPtr + blockPtr->offset[0];
991    for (y = 0; y < blockPtr->height; y++) {
992	tf.red = tf.redScan;
993	tf.green = tf.greenScan;
994	tf.blue = tf.blueScan;
995	tf.matte = tf.matteScan;
996	pixelPtr = rowPixPtr;
997	for (x = 0; x < blockPtr->width; x++) {
998	    *(tf.red++)   = pixelPtr[redOffset];
999	    *(tf.green++) = pixelPtr[greenOffset];
1000	    *(tf.blue++)  = pixelPtr[blueOffset];
1001	    if (nchan == 4) {
1002		/* Have a matte channel and write it. */
1003		*(tf.matte++) = pixelPtr[alphaOffset];
1004	    }
1005	    pixelPtr += blockPtr->pixelSize;
1006	}
1007	if (!tgaWriteScan(interp, handle, &tf, y)) {
1008	    tgaClose (&tf);
1009	    return TCL_ERROR;
1010	}
1011	rowPixPtr += blockPtr->pitch;
1012    }
1013    if (verbose)
1014        printImgInfo(&tf.th, filename, "Saving image:");
1015    tgaClose (&tf);
1016    return TCL_OK;
1017}
1018