1/* STARTHEADER
2 *
3 * File :       ico.c
4 *
5 * Author :     Paul Obermeier (paul@poSoft.de)
6 *
7 * Date :       Mon Aug 12 20:30:46 CEST 2002
8 *
9 * Copyright :  (C) 2002 Paul Obermeier
10 *
11 * Description :
12 *
13 * A photo image handler for Windows Icon file format.
14 *
15 * The following icon types are supported:
16 *
17 *  1-bit pixels: Black and White.
18 *  4-bit pixels: Grayscale or indexed.
19 *  8-bit pixels: Grayscale or indexed.
20 * 24-bit pixels: True-color (GBR,  each channel 8 bit).
21 * 32-bit pixels: True-color (GBRA, each channel 8 bit).
22 *
23 * List of currently supported features:
24 *
25 * Type   |     Read      |     Write     |
26 *        | -file | -data | -file | -data |
27 * ----------------------------------------
28 *  1-bit | Yes   | Yes   | No    | No    |
29 *  4-bit | Yes   | Yes   | No    | No    |
30 *  8-bit | Yes   | Yes   | Yes   | Yes   |
31 * 24-bit | Yes   | Yes   | Yes   | Yes   |
32 * 32-bit | Yes   | Yes   | No    | No    |
33 *
34 *
35 * The following format options are available:
36 *
37 * Read  ICO image: "ico -verbose <bool> -index <uint>"
38 * Write ICO image: "ico -verbose <bool>"
39 *
40 * -verbose <bool>: If set to true, additional information about the file
41 *                  format is printed to stdout. Default is "false".
42 * -index <uint>:   Read the icon with specified index. Default is 0.
43 *
44 * Notes:
45 *
46 *
47 * ENDHEADER
48 *
49 * $Id: ico.c 258 2010-05-04 08:59:42Z nijtmans $
50 *
51 */
52
53/*
54 * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
55 */
56
57#include "init.c"
58
59
60/* #define DEBUG_LOCAL */
61
62/* Some defines and typedefs. */
63#define TRUE  1
64#define FALSE 0
65typedef unsigned char Boln;	/* Boolean value: TRUE or FALSE */
66typedef unsigned char UByte;	/* Unsigned  8 bit integer */
67typedef char  Byte;		/* Signed    8 bit integer */
68typedef unsigned short UShort;	/* Unsigned 16 bit integer */
69typedef short Short;		/* Signed   16 bit integer */
70typedef unsigned int UInt;	/* Unsigned 32 bit integer */
71typedef int Int;		/* Signed   32 bit integer */
72
73#define BI_RGB 0
74
75typedef struct {
76   UByte  width;
77   UByte  height;
78   UShort nColors;
79   UByte  reserved;
80   UShort nPlanes;
81   UShort bitCount;
82   UInt   sizeInBytes;
83   UInt   fileOffset;
84} ICOENTRY;
85
86/* ICO file header structure */
87typedef struct {
88   UShort   nIcons;
89   ICOENTRY *entries;
90} ICOHEADER;
91
92typedef struct {
93   UInt   size;
94   UInt   width;
95   UInt   height;
96   UShort nPlanes;
97   UShort nBitsPerPixel;
98   UInt   compression;
99   UInt   imageSize;
100   UInt   xPixelsPerM;
101   UInt   yPixelsPerM;
102   UInt   nColorsUsed;
103   UInt   nColorsImportant;
104} INFOHEADER;
105
106typedef struct {
107   UByte red;
108   UByte green;
109   UByte blue;
110   UByte matte;
111} ICOCOLOR;
112
113/* ICO file format options structure for use with ParseFormatOpts */
114typedef struct {
115    UInt  index;
116    Boln  verbose;
117} FMTOPT;
118
119static Boln readUByte (tkimg_MFile *handle, UByte *b)
120{
121    char buf[1];
122    if (1 != tkimg_Read(handle, buf, 1)) {
123        return FALSE;
124    }
125    *b = buf[0] & 0xFF;
126    return TRUE;
127}
128
129/* Read 2 bytes, representing a unsigned 16 bit integer in the form
130   <LowByte, HighByte>, from a file and convert them into the current
131   machine's format. */
132
133static Boln readUShort (tkimg_MFile *handle, UShort *s)
134{
135    char buf[2];
136    UShort tmp;
137
138    if (2 != tkimg_Read(handle, buf, 2)) {
139        return FALSE;
140    }
141    tmp  =  buf[0] & 0xFF;
142    tmp |= (buf[1] & 0xFF) << 8;
143    *s = tmp;
144    return TRUE;
145}
146
147/* Read 4 bytes, representing a unsigned 32 bit integer in the form
148   <LowByte, HighByte>, from a file and convert them into the current
149   machine's format. */
150
151static Boln readUInt (tkimg_MFile *handle, UInt *i)
152{
153    char buf[4];
154    UInt tmp;
155
156    if (4 != tkimg_Read(handle, buf, 4)) {
157        return FALSE;
158    }
159    tmp  =  buf[0] & 0xFF;
160    tmp |= (buf[1] & 0xFF) <<  8;
161    tmp |= (buf[2] & 0xFF) << 16;
162    tmp |= (buf[3] & 0xFF) << 24;
163    *i = tmp;
164    return TRUE;
165}
166
167/* Write a byte, representing an unsigned integer to a file. */
168
169static Boln writeUByte (tkimg_MFile *handle, UByte b)
170{
171    UByte buf[1];
172    buf[0] = b;
173    if (1 != tkimg_Write(handle, (const char *)buf, 1)) {
174        return FALSE;
175    }
176    return TRUE;
177}
178
179/* Convert a unsigned 16 bit integer number into the format
180   <LowByte, HighByte> (an array of 2 bytes) and write the array to a file. */
181
182static Boln writeUShort (tkimg_MFile *handle, UShort s)
183{
184    Byte buf[2];
185    buf[0] = (Byte)s;
186    buf[1] = s >> 8;
187    if (2 != tkimg_Write(handle, buf, 2)) {
188        return FALSE;
189    }
190    return TRUE;
191}
192
193/* Convert a unsigned 32 bit integer number into the format
194   <LowByte, HighByte> (an array of 4 bytes) and write the array to a file. */
195
196static Boln writeUInt (tkimg_MFile *handle, UInt i)
197{
198    Byte buf[4];
199    buf[0] = i;
200    buf[1] = i >> 8;
201    buf[2] = i >> 16;
202    buf[3] = i >> 24;
203    if (4 != tkimg_Write(handle, buf, 4)) {
204        return FALSE;
205    }
206    return TRUE;
207}
208
209#define OUT Tcl_WriteChars (outChan, str, -1)
210static void printImgInfo (ICOHEADER *th, INFOHEADER *ih, FMTOPT *opts,
211                          const char *filename, const char *msg)
212{
213    Tcl_Channel outChan;
214    char str[256];
215    int i = opts->index;
216
217    outChan = Tcl_GetStdChannel (TCL_STDOUT);
218    if (!outChan) {
219        return;
220    }
221    sprintf(str, "%s %s\n", msg, filename);                                 OUT;
222    sprintf(str, "  No. of icons : %d\n", th->nIcons);                      OUT;
223    sprintf(str, "  Icon %d:\n", i);                                        OUT;
224    sprintf(str, "    Width and Height: %dx%d\n", ih->width, ih->height/2); OUT;
225    sprintf(str, "    Number of colors: %d\n", th->entries[i].nColors);     OUT;
226    sprintf(str, "    Number of planes: %d\n", ih->nPlanes);                OUT;
227    sprintf(str, "    Bits per pixel:   %d\n", ih->nBitsPerPixel);          OUT;
228    sprintf(str, "    Size in bytes:    %d\n", th->entries[i].sizeInBytes); OUT;
229    sprintf(str, "    File offset:      %d\n", th->entries[i].fileOffset);  OUT;
230    Tcl_Flush(outChan);
231}
232#undef OUT
233
234static Boln readIcoHeader (tkimg_MFile *handle, ICOHEADER *th)
235{
236    int    i;
237    UByte  nColors;
238    UShort reserved, type, nIcons;
239
240    if (!readUShort (handle, &reserved)) {
241	return FALSE;
242    }
243    if (reserved != 0) {
244	return FALSE;
245    }
246
247    if (!readUShort (handle, &type)) {
248	return FALSE;
249    }
250    if (type != 1) {
251	return FALSE;
252    }
253    if (!readUShort (handle, &nIcons)) {
254	return FALSE;
255    }
256    if (nIcons <= 0) {
257	return FALSE;
258    }
259
260    th->nIcons = nIcons;
261    if (!(th->entries = (ICOENTRY *)ckalloc (sizeof (ICOENTRY) * nIcons))) {
262	return FALSE;
263    }
264
265    for (i=0; i<nIcons; i++) {
266	if (!readUByte  (handle, &th->entries[i].width)  ||
267	    !readUByte  (handle, &th->entries[i].height) ||
268	    !readUByte  (handle, &nColors) ||
269	    !readUByte  (handle, &th->entries[i].reserved) ||
270	    !readUShort (handle, &th->entries[i].nPlanes) ||
271	    !readUShort (handle, &th->entries[i].bitCount) ||
272	    !readUInt   (handle, &th->entries[i].sizeInBytes) ||
273	    !readUInt   (handle, &th->entries[i].fileOffset)) {
274            ckfree ((char *)th->entries);
275	    return FALSE;
276	}
277        th->entries[i].nColors = (nColors == 0? 256: nColors);
278#ifdef DEBUG_LOCAL
279            printf ("Icon %d:\n", i);
280            printf ("  Width     : %d\n", th->entries[i].width);
281            printf ("  Height    : %d\n", th->entries[i].height);
282            printf ("  Colors    : %d\n", th->entries[i].nColors);
283            printf ("  Planes    : %d\n", th->entries[i].nPlanes);
284            printf ("  BitCount  : %d\n", th->entries[i].bitCount);
285            printf ("  Size      : %d\n", th->entries[i].sizeInBytes);
286            printf ("  FileOffset: %d\n", th->entries[i].fileOffset);
287#endif
288    }
289    return TRUE;
290}
291
292static Boln writeIcoHeader (tkimg_MFile *handle, ICOHEADER *th)
293{
294    int    i;
295    UByte  nColors;
296    UShort reserved = 0,
297           type = 1;
298
299    if (!writeUShort (handle, reserved)) {
300	return FALSE;
301    }
302    if (!writeUShort (handle, type)) {
303	return FALSE;
304    }
305    if (!writeUShort (handle, th->nIcons)) {
306	return FALSE;
307    }
308    for (i=0; i<th->nIcons; i++) {
309        nColors = (th->entries[i].nColors == 256? 0: th->entries[i].nColors);
310	if (!writeUByte  (handle, th->entries[i].width)  ||
311	    !writeUByte  (handle, th->entries[i].height) ||
312	    !writeUByte  (handle, nColors) ||
313	    !writeUByte  (handle, th->entries[i].reserved) ||
314	    !writeUShort (handle, th->entries[i].nPlanes) ||
315	    !writeUShort (handle, th->entries[i].bitCount) ||
316	    !writeUInt   (handle, th->entries[i].sizeInBytes) ||
317	    !writeUInt   (handle, th->entries[i].fileOffset)) {
318	    return FALSE;
319	}
320    }
321    return TRUE;
322}
323
324static Boln readInfoHeader (tkimg_MFile *handle, INFOHEADER *ih)
325{
326    if (!readUInt   (handle, &ih->size) ||
327	!readUInt   (handle, &ih->width) ||
328	!readUInt   (handle, &ih->height) ||
329	!readUShort (handle, &ih->nPlanes) ||
330	!readUShort (handle, &ih->nBitsPerPixel) ||
331	!readUInt   (handle, &ih->compression) ||
332	!readUInt   (handle, &ih->imageSize) ||
333	!readUInt   (handle, &ih->xPixelsPerM) ||
334	!readUInt   (handle, &ih->yPixelsPerM) ||
335	!readUInt   (handle, &ih->nColorsUsed) ||
336	!readUInt   (handle, &ih->nColorsImportant)) {
337	return FALSE;
338    }
339#ifdef DEBUG_LOCAL
340	printf("Info header:\n");
341	printf("Size: %d\n", ih->size);
342	printf("Width: %d\n", ih->width);
343	printf("Height: %d\n", ih->height);
344	printf("Planes: %d\n", ih->nPlanes);
345	printf("BitsPerPixel: %d\n", ih->nBitsPerPixel);
346	printf("Compression: %d\n", ih->compression);
347	printf("Image size: %d\n", ih->imageSize);
348	printf("XPixelsPerM: %d\n", ih->xPixelsPerM);
349	printf("YPixelsPerM: %d\n", ih->yPixelsPerM);
350	printf("ColorsUsed: %d\n", ih->nColorsUsed);
351	printf("ColorsImportant: %d\n", ih->nColorsImportant);
352#endif
353    return TRUE;
354}
355
356static Boln writeInfoHeader (tkimg_MFile *handle, INFOHEADER *ih)
357{
358    if (!writeUInt   (handle, ih->size) ||
359	!writeUInt   (handle, ih->width) ||
360	!writeUInt   (handle, ih->height) ||
361	!writeUShort (handle, ih->nPlanes) ||
362	!writeUShort (handle, ih->nBitsPerPixel) ||
363	!writeUInt   (handle, ih->compression) ||
364	!writeUInt   (handle, ih->imageSize) ||
365	!writeUInt   (handle, ih->xPixelsPerM) ||
366	!writeUInt   (handle, ih->yPixelsPerM) ||
367	!writeUInt   (handle, ih->nColorsUsed) ||
368	!writeUInt   (handle, ih->nColorsImportant)) {
369	return FALSE;
370    }
371#ifdef DEBUG_LOCAL
372	printf("Writing Info header:\n");
373	printf("Size        : %d\n", ih->size);
374	printf("Width       : %d\n", ih->width);
375	printf("Height      : %d\n", ih->height);
376	printf("Planes      : %d\n", ih->nPlanes);
377	printf("BitsPerPixel: %d\n", ih->nBitsPerPixel);
378	printf("Compression : %d\n", ih->compression);
379	printf("Image size  : %d\n", ih->imageSize);
380	printf("XPixelsPerM : %d\n", ih->xPixelsPerM);
381	printf("YPixelsPerM : %d\n", ih->yPixelsPerM);
382	printf("ColorsUsed  : %d\n", ih->nColorsUsed);
383	printf("ColorsImport: %d\n", ih->nColorsImportant);
384#endif
385    return TRUE;
386}
387
388static Boln readColorMap (tkimg_MFile *handle, int mapSize, ICOCOLOR *colorMap)
389{
390    int i;
391    ICOCOLOR color;
392
393    for (i=0; i<mapSize; i++) {
394        if (!readUByte (handle, &color.blue) ||
395	    !readUByte (handle, &color.green) ||
396	    !readUByte (handle, &color.red) ||
397	    !readUByte (handle, &color.matte)) {
398	    return FALSE;
399	}
400        colorMap[i] = color;
401    }
402    return TRUE;
403}
404
405static Boln writeColorMap (tkimg_MFile *handle, int mapSize, ICOCOLOR *colorMap)
406{
407    int i;
408
409    for (i=0; i<mapSize; i++) {
410        if (!writeUByte (handle, colorMap[i].blue) ||
411	    !writeUByte (handle, colorMap[i].green) ||
412	    !writeUByte (handle, colorMap[i].red) ||
413	    !writeUByte (handle, colorMap[i].matte)) {
414	    return FALSE;
415	}
416    }
417    return TRUE;
418}
419
420/*
421 * Prototypes for local procedures defined in this file.
422 */
423
424static int ParseFormatOpts(Tcl_Interp *interp, Tcl_Obj *format,
425	FMTOPT *opts);
426static int CommonMatch(tkimg_MFile *handle, int *widthPtr,
427	int *heightPtr, ICOHEADER *icoHeaderPtr);
428static int CommonRead(Tcl_Interp *interp, tkimg_MFile *handle,
429	const char *filename, Tcl_Obj *format,
430	Tk_PhotoHandle imageHandle, int destX, int destY,
431	int width, int height, int srcX, int srcY);
432static int CommonWrite(Tcl_Interp *interp, tkimg_MFile *handle,
433	Tk_PhotoImageBlock *blockPtr);
434
435static int ParseFormatOpts (interp, format, opts)
436    Tcl_Interp *interp;
437    Tcl_Obj *format;
438    FMTOPT *opts;
439{
440    static const char *const icoOptions[] = {
441         "-verbose", "-index"
442    };
443    int objc, length, c, i, index;
444    Tcl_Obj **objv;
445    const char *indexStr, *verboseStr;
446
447    /* Initialize format options with default values. */
448    verboseStr = "0";
449    indexStr   = "0";
450
451    if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK)
452	return TCL_ERROR;
453    if (objc) {
454	for (i=1; i<objc; i++) {
455	    if (Tcl_GetIndexFromObj(interp, objv[i], (CONST84 char *CONST86 *)icoOptions,
456		    "format option", 0, &index) != TCL_OK) {
457		return TCL_ERROR;
458	    }
459	    if (++i >= objc) {
460		Tcl_AppendResult(interp, "No value for option \"",
461			Tcl_GetStringFromObj (objv[--i], (int *) NULL),
462			"\"", (char *) NULL);
463		return TCL_ERROR;
464	    }
465	    switch(index) {
466		case 0:
467		    verboseStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
468		    break;
469		case 1:
470		    indexStr = Tcl_GetStringFromObj(objv[i], (int *) NULL);
471		    break;
472	    }
473	}
474    }
475
476    /* OPA TODO: Check for valid integer strings. */
477    opts->index  = atoi (indexStr);
478
479    c = verboseStr[0]; length = strlen (verboseStr);
480    if (!strncmp (verboseStr, "1", length) || \
481	!strncmp (verboseStr, "true", length) || \
482	!strncmp (verboseStr, "on", length)) {
483	opts->verbose = 1;
484    } else if (!strncmp (verboseStr, "0", length) || \
485	!strncmp (verboseStr, "false", length) || \
486	!strncmp (verboseStr, "off", length)) {
487	opts->verbose = 0;
488    } else {
489	Tcl_AppendResult(interp, "invalid verbose mode \"", verboseStr,
490			  "\": should be 1 or 0, on or off, true or false",
491			  (char *) NULL);
492	return TCL_ERROR;
493    }
494
495    return TCL_OK;
496}
497
498static int ChnMatch(
499    Tcl_Channel chan,
500    const char *fileName,
501    Tcl_Obj *format,
502    int *widthPtr,
503    int *heightPtr,
504    Tcl_Interp *interp
505) {
506    tkimg_MFile handle;
507
508    handle.data = (char *) chan;
509    handle.state = IMG_CHAN;
510
511    return CommonMatch(&handle, widthPtr, heightPtr, NULL);
512}
513
514static int ObjMatch(
515    Tcl_Obj *data,
516    Tcl_Obj *format,
517    int *widthPtr,
518    int *heightPtr,
519    Tcl_Interp *interp
520) {
521    tkimg_MFile handle;
522
523    if (!tkimg_ReadInit(data, '\000', &handle)) {
524	return 0;
525    }
526    return CommonMatch(&handle, widthPtr, heightPtr, NULL);
527}
528
529static int CommonMatch(handle, widthPtr, heightPtr, icoHeaderPtr)
530    tkimg_MFile *handle;
531    int *widthPtr, *heightPtr;
532    ICOHEADER *icoHeaderPtr;
533{
534    ICOHEADER icoHeader, *headerPtr;
535
536    if (!icoHeaderPtr) {
537        headerPtr = &icoHeader;
538    } else {
539	headerPtr = icoHeaderPtr;
540    }
541    if (!readIcoHeader (handle, headerPtr)) {
542	return 0;
543    }
544
545    *widthPtr  = headerPtr->entries[0].width;
546    *heightPtr = headerPtr->entries[0].height;
547
548    if (!icoHeaderPtr) {
549	ckfree ((char *) headerPtr->entries);
550    }
551    return 1;
552}
553
554static int ChnRead(interp, chan, filename, format, imageHandle,
555                    destX, destY, width, height, srcX, srcY)
556    Tcl_Interp *interp;
557    Tcl_Channel chan;
558    const char *filename;
559    Tcl_Obj *format;
560    Tk_PhotoHandle imageHandle;
561    int destX, destY;
562    int width, height;
563    int srcX, srcY;
564{
565    tkimg_MFile handle;
566
567    handle.data = (char *) chan;
568    handle.state = IMG_CHAN;
569
570    return CommonRead (interp, &handle, filename, format,
571                       imageHandle, destX, destY,
572		       width, height, srcX, srcY);
573}
574
575static int ObjRead (interp, data, format, imageHandle,
576	            destX, destY, width, height, srcX, srcY)
577    Tcl_Interp *interp;
578    Tcl_Obj *data;
579    Tcl_Obj *format;
580    Tk_PhotoHandle imageHandle;
581    int destX, destY;
582    int width, height;
583    int srcX, srcY;
584{
585    tkimg_MFile handle;
586
587    tkimg_ReadInit(data, '\000', &handle);
588
589    return CommonRead (interp, &handle, "InlineData", format, imageHandle,
590                       destX, destY, width, height, srcX, srcY);
591}
592
593static int CommonRead (interp, handle, filename, format, imageHandle,
594                       destX, destY, width, height, srcX, srcY)
595    Tcl_Interp *interp;         /* Interpreter to use for reporting errors. */
596    tkimg_MFile *handle;              /* The image file, open for reading. */
597    const char *filename;       /* The name of the image file. */
598    Tcl_Obj *format;            /* User-specified format object, or NULL. */
599    Tk_PhotoHandle imageHandle; /* The photo image to write into. */
600    int destX, destY;           /* Coordinates of top-left pixel in
601				 * photo image to be written to. */
602    int width, height;          /* Dimensions of block of photo image to
603			         * be written to. */
604    int srcX, srcY;             /* Coordinates of top-left pixel to be used
605			         * in image being read. */
606{
607	Tk_PhotoImageBlock block;
608    int x, y;
609    int fileWidth, fileHeight;
610    int icoHeaderWidth, icoHeaderHeight;
611    int outWidth, outHeight, outY;
612    int bytesPerLine;
613    int nBytesToSkip;
614    int errorFlag = TCL_OK;
615    unsigned char *line = NULL, *expline = NULL;
616    char msgStr[1024];
617    ICOHEADER  icoHeader;
618    INFOHEADER infoHeader;
619    ICOCOLOR   colorMap[256];
620    FMTOPT opts;
621
622    if (ParseFormatOpts(interp, format, &opts) != TCL_OK) {
623        return TCL_ERROR;
624    }
625
626    if (!CommonMatch(handle, &fileWidth, &fileHeight, &icoHeader)) {
627	Tcl_AppendResult(interp, "Error reading header", (char *)NULL);
628        errorFlag = TCL_ERROR;
629        goto error;
630    }
631
632    if (opts.index < 0 || opts.index >= icoHeader.nIcons) {
633        sprintf(msgStr, "Invalid icon index: %d", opts.index);
634	Tcl_AppendResult(interp, msgStr, (char *)NULL);
635        errorFlag = TCL_ERROR;
636        goto error;
637    }
638
639    /* Instead of seeking, which does not work on strings,
640       we calculate the number of bytes from the current position
641       till the start of the INFOHEADER and read these bytes with tkimg_Read. */
642    nBytesToSkip = icoHeader.entries[opts.index].fileOffset -6 -
643                   16 * icoHeader.nIcons;
644    if (nBytesToSkip > 0) {
645        char *dummy = ckalloc (nBytesToSkip);
646	tkimg_Read(handle, dummy, nBytesToSkip);
647        ckfree ((char *) dummy);
648    }
649
650    /* Read Info header and color map */
651    if (!readInfoHeader (handle, &infoHeader)) {
652	Tcl_AppendResult(interp, "Error reading info header", (char *)NULL);
653        errorFlag = TCL_ERROR;
654        goto error;
655    }
656
657    if (infoHeader.compression != BI_RGB) {
658        sprintf(msgStr,"Unsupported compression type (%d)", infoHeader.compression);
659	Tcl_AppendResult(interp, msgStr, (char *)NULL);
660        errorFlag = TCL_ERROR;
661        goto error;
662    }
663
664    if (infoHeader.nBitsPerPixel != 24 && infoHeader.nBitsPerPixel != 32) {
665	if (!readColorMap (handle, icoHeader.entries[opts.index].nColors,
666			   colorMap)) {
667	    Tcl_AppendResult(interp, "Error reading color map", (char *)NULL);
668	    errorFlag = TCL_ERROR;
669	    goto error;
670	}
671    }
672
673    fileWidth  = infoHeader.width;
674    fileHeight = infoHeader.height / 2;
675    icoHeaderWidth  = icoHeader.entries[opts.index].width;
676    icoHeaderHeight = icoHeader.entries[opts.index].height;
677    if (icoHeaderWidth == 0) {
678        icoHeaderWidth = 256;
679    }
680    if (icoHeaderHeight == 0) {
681        icoHeaderHeight = 256;
682    }
683    if (fileWidth  != icoHeaderWidth || fileHeight != icoHeaderHeight) {
684        sprintf(msgStr,"ICO sizes don't match (%dx%d) vs. (%dx%d)",
685                fileWidth, fileHeight,
686                icoHeaderWidth, icoHeaderHeight);
687        Tcl_AppendResult(interp, msgStr, (char *)NULL);
688        errorFlag = TCL_ERROR;
689        goto error;
690    }
691    outWidth   = fileWidth;
692    outHeight  = fileHeight;
693    if (fileWidth != width || fileHeight != height) {
694        if (srcX != 0 || srcY != 0 || destX != 0 || destY != 0) {
695	    if ((srcX + width) > fileWidth) {
696		outWidth = fileWidth - srcX;
697	    } else {
698		outWidth = width;
699	    }
700	    if ((srcY + height) > fileHeight) {
701		outHeight = fileHeight - srcY;
702	    } else {
703		outHeight = height;
704	    }
705	}
706    }
707    if ((outWidth <= 0) || (outHeight <= 0)
708	|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
709	return TCL_OK;
710    }
711
712    if (opts.verbose) {
713        printImgInfo (&icoHeader, &infoHeader, &opts,
714		      filename, "Reading image:");
715    }
716
717    if (tkimg_PhotoExpand(interp, imageHandle, destX + outWidth, destY + outHeight) == TCL_ERROR) {
718        errorFlag = TCL_ERROR;
719        goto error;
720    }
721
722    bytesPerLine = ((infoHeader.nBitsPerPixel * fileWidth + 31)/32)*4;
723
724    block.pixelSize = 4;
725    block.pitch = fileWidth * 4;
726    block.width = outWidth;
727    block.height = 1;
728    block.offset[0] = 0;
729    block.offset[1] = 1;
730    block.offset[2] = 2;
731    block.offset[3] = 3;
732    block.pixelPtr = (unsigned char *) ckalloc (4 * fileWidth * fileHeight);
733    expline = block.pixelPtr;
734
735    line = (unsigned char *) ckalloc(bytesPerLine);
736    switch (infoHeader.nBitsPerPixel) {
737	case 32:
738	    for (y=0; y<fileHeight; y++) {
739                tkimg_Read(handle, (char *)line, bytesPerLine);
740		for (x = 0; x < fileWidth; x++) {
741		    expline[0] = line[x*4 + 2];
742		    expline[1] = line[x*4 + 1];
743		    expline[2] = line[x*4 + 0];
744		    expline[3] = line[x*4 + 3];
745		    expline += 4;
746		}
747            }
748            break;
749	case 24:
750	    for (y=0; y<fileHeight; y++) {
751                tkimg_Read(handle, (char *)line, bytesPerLine);
752		for (x = 0; x < fileWidth; x++) {
753		    expline[0] = line[x*3 + 2];
754		    expline[1] = line[x*3 + 1];
755		    expline[2] = line[x*3 + 0];
756		    expline += 4;
757		}
758            }
759            break;
760	case 8:
761	    for (y=0; y<fileHeight; y++) {
762		tkimg_Read(handle, (char *)line, bytesPerLine);
763		for (x = 0; x < fileWidth; x++) {
764		    expline[0] = colorMap[line[x]].red;
765		    expline[1] = colorMap[line[x]].green;
766		    expline[2] = colorMap[line[x]].blue;
767		    expline += 4;
768		}
769	    }
770	    break;
771	case 4:
772	    for (y=0; y<fileHeight; y++) {
773		int c;
774		tkimg_Read(handle, (char *)line, bytesPerLine);
775		for (x=0; x<fileWidth; x++) {
776		    if (x&1) {
777			c = line[x/2] & 0x0f;
778		    } else {
779			c = line[x/2] >> 4;
780		    }
781		    expline[0] = colorMap[c].red;
782		    expline[1] = colorMap[c].green;
783		    expline[2] = colorMap[c].blue;
784		    expline += 4;
785		}
786	    }
787	    break;
788	case 1:
789	    for (y=0; y<fileHeight; y++) {
790		int c;
791		tkimg_Read(handle, (char *)line, bytesPerLine);
792		for (x=0; x<fileWidth; x++) {
793		    c = (line[x/8] >> (7-(x%8))) & 1;
794		    expline[0] = colorMap[c].red;
795		    expline[1] = colorMap[c].green;
796		    expline[2] = colorMap[c].blue;
797		    expline += 4;
798		}
799	    }
800	    break;
801	default:
802	    sprintf(msgStr,"%d-bits ICO file not supported",
803                     infoHeader.nBitsPerPixel);
804	    Tcl_AppendResult(interp, msgStr, (char *)NULL);
805	    errorFlag = TCL_ERROR;
806	    goto error;
807    }
808
809    if (infoHeader.nBitsPerPixel != 32) {
810        /* Read XAND bitmap. We don't need to read the alpha bitmap, if
811           alpha is supplied already in the 32-bit case. */
812        bytesPerLine = ((1 * fileWidth + 31)/32)*4;
813
814        expline = block.pixelPtr;
815        for (y=0; y<fileHeight; y++) {
816            int c;
817            tkimg_Read(handle, (char *)line, bytesPerLine);
818            for (x=0; x<fileWidth; x++) {
819                c = (line[x/8] >> (7-(x%8))) & 1;
820                expline[3] = (c? 0: 255);
821                expline += 4;
822            }
823        }
824    }
825
826    /* Store the pointer to allocated buffer for later freeing. */
827    expline = block.pixelPtr;
828    block.pixelPtr += srcX * 4;
829
830    outY = destY + outHeight - 1;
831    for (y=fileHeight-1; y>=0; y--) {
832        if (y >= srcY && y < srcY + outHeight) {
833    	errorFlag = tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, outY,
834    		outWidth, 1, TK_PHOTO_COMPOSITE_SET);
835    	if (errorFlag == TCL_ERROR) {
836    	    break;
837    	}
838    	outY--;
839        }
840        block.pixelPtr += 4 * fileWidth;
841    }
842    block.pixelPtr = expline;
843
844error:
845    if (icoHeader.entries) {
846	ckfree((char *) icoHeader.entries);
847    }
848    if (line) {
849	ckfree((char *) line);
850    }
851    if (expline) {
852	ckfree((char *) block.pixelPtr);
853    }
854    return errorFlag;
855}
856
857static int ChnWrite (interp, filename, format, blockPtr)
858    Tcl_Interp *interp;
859    const char *filename;
860    Tcl_Obj *format;
861    Tk_PhotoImageBlock *blockPtr;
862{
863    Tcl_Channel chan;
864    tkimg_MFile handle;
865    int result;
866
867    chan = tkimg_OpenFileChannel(interp, filename, 0644);
868    if (!chan) {
869	return TCL_ERROR;
870    }
871
872    handle.data = (char *) chan;
873    handle.state = IMG_CHAN;
874
875    result = CommonWrite(interp, &handle, blockPtr);
876    if (Tcl_Close(interp, chan) == TCL_ERROR) {
877	return TCL_ERROR;
878    }
879    return result;
880}
881
882static int StringWrite(
883    Tcl_Interp *interp,
884    Tcl_Obj *format,
885    Tk_PhotoImageBlock *blockPtr
886) {
887    tkimg_MFile handle;
888    int result;
889    Tcl_DString data;
890
891    Tcl_DStringInit(&data);
892    tkimg_WriteInit(&data, &handle);
893    result = CommonWrite(interp, &handle, blockPtr);
894    tkimg_Putc(IMG_DONE, &handle);
895
896    if (result == TCL_OK) {
897	Tcl_DStringResult(interp, &data);
898    } else {
899	Tcl_DStringFree(&data);
900    }
901    return result;
902}
903
904static int CommonWrite (interp, handle, blockPtr)
905    Tcl_Interp *interp;
906    tkimg_MFile *handle;
907    Tk_PhotoImageBlock *blockPtr;
908{
909    int bytesPerLineXOR, bytesPerLineAND, nbytes, ncolors, i, x, y;
910    int redOffset, greenOffset, blueOffset, alphaOffset;
911    int foundColor;
912    UByte *imagePtr, *pixelPtr;
913    UByte buf[4];
914    ICOHEADER  icoHeader;
915    INFOHEADER infoHeader;
916    ICOCOLOR   colorMap[256];
917    ICOCOLOR   pixel;
918
919    if (blockPtr->width > 255 || blockPtr->height > 255) {
920	Tcl_AppendResult(interp, "ICO images must be less than 256 pixels.",
921		          (char *) NULL);
922	return TCL_ERROR;
923    }
924
925    redOffset   = 0;
926    greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
927    blueOffset  = blockPtr->offset[2] - blockPtr->offset[0];
928    alphaOffset = blockPtr->offset[0];
929    if (alphaOffset < blockPtr->offset[2]) {
930	alphaOffset = blockPtr->offset[2];
931    }
932    if (++alphaOffset < blockPtr->pixelSize) {
933	alphaOffset -= blockPtr->offset[0];
934    } else {
935	alphaOffset = 0;
936    }
937    ncolors = 0;
938    if (greenOffset || blueOffset) {
939	for (y = 0; ncolors <= 256 && y < blockPtr->height; y++) {
940	    pixelPtr = blockPtr->pixelPtr + y*blockPtr->pitch + blockPtr->offset[0];
941	    for (x=0; ncolors <= 256 && x<blockPtr->width; x++) {
942		pixel.red   = pixelPtr[redOffset];
943		pixel.green = pixelPtr[greenOffset];
944		pixel.blue  = pixelPtr[blueOffset];
945		if (alphaOffset && (pixelPtr[alphaOffset] == 0)) {
946		    pixel.matte = 0;
947		} else {
948		    pixel.matte = 1;
949		}
950		foundColor = 0;
951		for (i=0; i<ncolors; i++) {
952		    if (pixel.red   == colorMap[i].red &&
953                        pixel.green == colorMap[i].green &&
954                        pixel.blue  == colorMap[i].blue) {
955			foundColor = 1;
956			break;
957		    }
958		}
959		if (!foundColor) {
960		    if (ncolors < 256) {
961			colorMap[ncolors] = pixel;
962		    }
963		    ncolors++;
964		}
965		pixelPtr += blockPtr->pixelSize;
966	    }
967	}
968	if (ncolors <= 256) {
969	    pixel.red = pixel.green = pixel.blue = pixel.matte = 0;
970            while (ncolors < 256) {
971		colorMap[ncolors++] = pixel;
972	    }
973	    nbytes = 1;
974	} else {
975	    nbytes = 3;
976	    ncolors = 0;
977	}
978    } else {
979        nbytes = 1;
980    }
981
982    bytesPerLineXOR = ((blockPtr->width * nbytes + 3) / 4) * 4;
983    bytesPerLineAND = ((blockPtr->width * 1      + 31) / 32 )* 4;
984
985    icoHeader.nIcons = 1;
986    if (!(icoHeader.entries = (ICOENTRY *) ckalloc (sizeof (ICOENTRY)))) {
987	return TCL_ERROR;
988    }
989    icoHeader.entries[0].width       = blockPtr->width;
990    icoHeader.entries[0].height      = blockPtr->height;
991    icoHeader.entries[0].nColors     = (ncolors > 0? ncolors: 0);
992    icoHeader.entries[0].reserved    = 0;
993    icoHeader.entries[0].nPlanes     = 1;
994    icoHeader.entries[0].bitCount    = (ncolors > 0? 8: 24);
995    icoHeader.entries[0].sizeInBytes = sizeof (INFOHEADER) +
996				       ncolors * sizeof (ICOCOLOR) +
997                                       bytesPerLineXOR * blockPtr->height +
998                                       bytesPerLineAND * blockPtr->height;
999    icoHeader.entries[0].fileOffset  = 6 + icoHeader.nIcons * 16;
1000
1001    if (!writeIcoHeader (handle, &icoHeader)) {
1002	return TCL_ERROR;
1003    }
1004
1005    infoHeader.size = sizeof (INFOHEADER);
1006    infoHeader.width = blockPtr->width;
1007    infoHeader.height = blockPtr->height * 2;
1008    infoHeader.nPlanes = 1;
1009    infoHeader.nBitsPerPixel = (ncolors > 0? 8: 24);
1010    infoHeader.compression = 0;
1011    infoHeader.imageSize = 0;
1012    infoHeader.xPixelsPerM = 0;
1013    infoHeader.yPixelsPerM = 0;
1014    infoHeader.nColorsUsed = 0;
1015    infoHeader.nColorsImportant = 0;
1016
1017    if (!writeInfoHeader (handle, &infoHeader)) {
1018	return TCL_ERROR;
1019    }
1020
1021    if (ncolors > 0) {
1022	if (!writeColorMap (handle, ncolors, colorMap)) {
1023	    return TCL_ERROR;
1024        }
1025    }
1026
1027    bytesPerLineXOR -= blockPtr->width * nbytes;
1028
1029    imagePtr = blockPtr->pixelPtr + blockPtr->offset[0] +
1030               blockPtr->height * blockPtr->pitch;
1031    for (y = 0; y < blockPtr->height; y++) {
1032	pixelPtr = imagePtr -= blockPtr->pitch;
1033	for (x=0; x<blockPtr->width; x++) {
1034	    if (ncolors) {
1035		for (i=0; i<ncolors; i++) {
1036                    if (pixelPtr[redOffset]   == colorMap[i].red &&
1037                        pixelPtr[greenOffset] == colorMap[i].green &&
1038                        pixelPtr[blueOffset]  == colorMap[i].blue) {
1039			buf[0] = i;
1040		    }
1041		}
1042	    } else {
1043		buf[0] = pixelPtr[blueOffset];
1044		buf[1] = pixelPtr[greenOffset];
1045		buf[2] = pixelPtr[redOffset];
1046	    }
1047	    tkimg_Write(handle, (char *) buf, nbytes);
1048	    pixelPtr += blockPtr->pixelSize;
1049	}
1050	if (bytesPerLineXOR) {
1051	    tkimg_Write(handle, "\0\0\0", bytesPerLineXOR);
1052	}
1053    }
1054
1055    bytesPerLineAND -= blockPtr->width / 8;
1056
1057    imagePtr = blockPtr->pixelPtr + blockPtr->offset[0] +
1058               blockPtr->height * blockPtr->pitch;
1059    for (y = 0; y < blockPtr->height; y++) {
1060	int c;
1061	pixelPtr = imagePtr -= blockPtr->pitch;
1062	for (x=0; x<blockPtr->width; x++) {
1063	    if (x % 8 == 0) {
1064		buf[0] = 0;
1065	    }
1066	    if (alphaOffset) {
1067		c = pixelPtr[alphaOffset];
1068		if (c == 0) {
1069		    buf[0] |= 1<<(7-x%8);
1070		}
1071	    }
1072	    if (x % 8 == 7) {
1073		tkimg_Write(handle, (char *) buf, 1);
1074	    }
1075	    pixelPtr += blockPtr->pixelSize;
1076	}
1077	if (bytesPerLineAND) {
1078	    tkimg_Write(handle, "\0\0\0", bytesPerLineAND);
1079	}
1080    }
1081    return TCL_OK;
1082}
1083