1 /*
2  * bmp.c --
3  *
4  *  BMP photo image type, Tcl/Tk package
5  *
6  * Copyright (c) 1997-2003 Jan Nijtmans    <nijtmans@users.sourceforge.net>
7  * Copyright (c) 2002      Andreas Kupries <andreas_kupries@users.sourceforge.net>
8  *
9  * The following format options are available:
10  *
11  * Write BMP image: "ico -resolution <list>"
12  *
13  * -resolution <list>: Set the resolution property of the output file.
14  *                     The default is 74 dpi (no option)
15  *                     Possible forms for list:
16  *                     {xRes unit} : set x and y resolution to same value
17  *                     {xRes yRes} : set x to 74dpi and y to 74*YRes/XRes
18  *                     {xRes yRes unit} : set x and y resolution to given values
19  *                     xRes, yRes are floats >= 0. 0 is the "no value" value.
20  *                     unit is one of "c" cm, "m" mm, "i" inch or "p" inch/72.
21  */
22 
23 /*
24  * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
25  */
26 
27 #include <stdio.h>
28 #include <string.h>
29 #include <math.h>
30 #include "init.c"
31 
32 /* Compression types */
33 #define BI_RGB          0
34 #define BI_RLE8         1
35 #define BI_RLE4         2
36 #define BI_BITFIELDS    3
37 
38 /* Structure for reading bit masks for compression type BI_BITFIELDS */
39 typedef struct {
40   unsigned int mask;
41   unsigned int shiftin;
42   unsigned int shiftout;
43 } BitmapChannel;
44 
45 /*
46  * Now the implementation
47  */
48 /*
49  * Prototypes for local procedures defined in this file:
50  */
51 
52 static int CommonMatch(tkimg_MFile *handle, int *widthPtr,
53         int *heightPtr, unsigned char **colorMap, int *numBits,
54         int *numCols, int *comp, unsigned int *mask);
55 
56 static int CommonRead(Tcl_Interp *interp, tkimg_MFile *handle,
57         Tk_PhotoHandle imageHandle, int destX, int destY, int width,
58         int height, int srcX, int srcY);
59 
60 static int CommonWrite(Tcl_Interp *interp, Tcl_Obj *format,
61         tkimg_MFile *handle,
62         Tk_PhotoImageBlock *blockPtr);
63 
64 static void putint(tkimg_MFile *handle, int i);
65 
66 /*
67  * Entrypoints for the photo image type.
68  */
69 
70 static int
ChnMatch(Tcl_Channel chan,const char * fileName,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)71 ChnMatch(
72     Tcl_Channel chan,
73     const char *fileName,
74     Tcl_Obj *format,
75     int *widthPtr,
76     int *heightPtr,
77     Tcl_Interp *interp
78 ) {
79     tkimg_MFile handle;
80 
81     handle.data = (char *) chan;
82     handle.state = IMG_CHAN;
83 
84     return CommonMatch(&handle, widthPtr, heightPtr,
85                         NULL, NULL, NULL, NULL, NULL);
86 }
87 
88 static int
ObjMatch(Tcl_Obj * data,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)89 ObjMatch(
90     Tcl_Obj *data,
91     Tcl_Obj *format,
92     int *widthPtr,
93     int *heightPtr,
94     Tcl_Interp *interp
95 ) {
96     tkimg_MFile handle;
97 
98     if (!tkimg_ReadInit(data, 'B', &handle)) {
99         return 0;
100     }
101     return CommonMatch(&handle, widthPtr, heightPtr,
102                         NULL, NULL, NULL, NULL, NULL);
103 }
104 
105 static int
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)106 ChnRead(
107     Tcl_Interp *interp,
108     Tcl_Channel chan,
109     const char *fileName,
110     Tcl_Obj *format,
111     Tk_PhotoHandle imageHandle,
112     int destX, int destY,
113     int width, int height,
114     int srcX, int srcY
115 ) {
116     tkimg_MFile handle;
117 
118     handle.data = (char *) chan;
119     handle.state = IMG_CHAN;
120 
121     return CommonRead(interp, &handle, imageHandle, destX, destY,
122             width, height, srcX, srcY);
123 }
124 
125 static int
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)126 ObjRead(
127     Tcl_Interp *interp,
128     Tcl_Obj *data,
129     Tcl_Obj *format,
130     Tk_PhotoHandle imageHandle,
131     int destX, int destY,
132     int width, int height,
133     int srcX, int srcY
134 ) {
135     tkimg_MFile handle;
136 
137     tkimg_ReadInit(data,'B',&handle);
138     return CommonRead(interp, &handle, imageHandle, destX, destY,
139             width, height, srcX, srcY);
140 }
141 
142 static int
ChnWrite(Tcl_Interp * interp,const char * filename,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)143 ChnWrite(
144     Tcl_Interp *interp,
145     const char *filename,
146     Tcl_Obj *format,
147     Tk_PhotoImageBlock *blockPtr
148 ) {
149     Tcl_Channel chan;
150     tkimg_MFile handle;
151     int result;
152 
153     chan = tkimg_OpenFileChannel(interp, filename, 0644);
154     if (!chan) {
155         return TCL_ERROR;
156     }
157 
158     handle.data = (char *) chan;
159     handle.state = IMG_CHAN;
160 
161     result = CommonWrite(interp, format, &handle, blockPtr);
162     if (Tcl_Close(interp, chan) == TCL_ERROR) {
163         return TCL_ERROR;
164     }
165     return result;
166 }
167 
StringWrite(Tcl_Interp * interp,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)168 static int StringWrite(
169     Tcl_Interp *interp,
170     Tcl_Obj *format,
171     Tk_PhotoImageBlock *blockPtr
172 ) {
173     tkimg_MFile handle;
174     int result;
175     Tcl_DString data;
176 
177     Tcl_DStringInit(&data);
178     tkimg_WriteInit(&data, &handle);
179     result = CommonWrite(interp, format, &handle, blockPtr);
180     tkimg_Putc(IMG_DONE, &handle);
181 
182     if (result == TCL_OK) {
183         Tcl_DStringResult(interp, &data);
184     } else {
185         Tcl_DStringFree(&data);
186     }
187     return result;
188 }
189 
190 static unsigned int
getUInt32(unsigned char * buf)191 getUInt32(unsigned char *buf)
192 {
193     return (buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24);
194 }
195 
196 static unsigned int
getUInt16(unsigned char * buf)197 getUInt16(unsigned char *buf)
198 {
199     return (buf[0] | buf[1] << 8);
200 }
201 
202 static void
GetChannelMasks(unsigned int * intMask,BitmapChannel * masks)203 GetChannelMasks(
204     unsigned int *intMask,
205     BitmapChannel *masks
206 ) {
207     unsigned int mask;
208     int i, nbits, offset, bit;
209 
210     for (i=0; i<3; i++) {
211         mask = getUInt32((unsigned char *) &intMask[i]);
212         masks[i].mask = mask;
213         nbits = 0;
214         offset = -1;
215         for (bit=0; bit<32; bit++) {
216             if (mask & 1) {
217                 nbits++;
218                 if (offset == -1) {
219                   offset = bit;
220                 }
221             }
222             mask = mask >> 1;
223         }
224         masks[i].shiftin = offset;
225         masks[i].shiftout = 8 - nbits;
226     }
227 }
228 
229 /*
230  * Helper functions for the entry points. Work horses.
231  */
232 
233 static int
CommonMatch(tkimg_MFile * handle,int * widthPtr,int * heightPtr,unsigned char ** colorMap,int * numBits,int * numCols,int * comp,unsigned int * mask)234 CommonMatch(
235     tkimg_MFile *handle,
236     int *widthPtr, int *heightPtr,
237     unsigned char **colorMap,
238     int *numBits, int *numCols, int *comp,
239     unsigned int *mask
240 ) {
241     unsigned char buf[28];
242     int c,i, compression, nBits, clrUsed, offBits;
243 
244     if ((tkimg_Read2(handle, (char *) buf, 2) != 2)
245             || (strncmp("BM", (char *) buf, 2) != 0)
246             || (tkimg_Read2(handle, (char *) buf, 24) != 24)
247             || buf[13] || buf[14] || buf[15]) {
248         return 0;
249     }
250 
251     offBits = (buf[11]<<24) + (buf[10]<<16) + (buf[9]<<8) + buf[8];
252     c = buf[12];
253     if ((c == 40) || (c == 64)) {
254         *widthPtr = (buf[19]<<24) + (buf[18]<<16) + (buf[17]<<8) + buf[16];
255         *heightPtr = (buf[23]<<24) + (buf[22]<<16) + (buf[21]<<8) + buf[20];
256         if (tkimg_Read2(handle, (char *) buf, 24) != 24) {
257             return 0;
258         }
259         nBits = buf[2];
260         compression = buf[4];
261         clrUsed = (buf[21]<<8) + buf[20];
262         offBits -= c+14;
263     } else if (c == 12) {
264         *widthPtr = (buf[17]<<8) + buf[16];
265         *heightPtr = (buf[19]<<8) + buf[18];
266         nBits = buf[22];
267         compression = BI_RGB;
268         clrUsed = 0;
269     } else {
270         return 0;
271     }
272     if (*widthPtr <= 0 || *heightPtr <= 0)
273         return 0;
274 
275     if (colorMap) {
276         if (c > 36) {
277             if (tkimg_Read2(handle, (char *) buf, c - 36) != c - 36 )
278                 return 0;
279         }
280         if (compression == BI_BITFIELDS) {
281             /* Read the channel masks. */
282             if (tkimg_Read2(handle, (char *) buf, 3*4) != 3*4 )
283                 return 0;
284             if (mask) {
285                 mask[0] = getUInt32((unsigned char *) &buf[0]);
286                 mask[1] = getUInt32((unsigned char *) &buf[4]);
287                 mask[2] = getUInt32((unsigned char *) &buf[8]);
288             }
289             offBits -= 3*4;
290         }
291         if (!clrUsed && nBits < 24) {
292             clrUsed = 1 << nBits;
293         }
294         if (nBits<16) {
295             unsigned char colbuf[4], *ptr;
296             offBits -= (3+(c!=12)) * clrUsed;
297             *colorMap = ptr = (unsigned char *) ckalloc(3*clrUsed);
298             for (i = 0; i < clrUsed; i++) {
299                 if (tkimg_Read2(handle, (char *) colbuf, 3+(c!=12)) != 3+(c!=12))
300                     return 0;
301                 *ptr++ = colbuf[0]; *ptr++ = colbuf[1]; *ptr++ = colbuf[2];
302                 /*printf("color %d: %d %d %d\n", i, colbuf[2], colbuf[1], colbuf[0]);*/
303             }
304         }
305         while (offBits>28) {
306             offBits -= 28;
307             if (tkimg_Read2(handle, (char *) buf, 28) != 28)
308                 return 0;
309         }
310         if (offBits > 0) {
311             if (tkimg_Read2(handle, (char *) buf, offBits) != offBits)
312                 return 0;
313         }
314         if (numCols) {
315             *numCols = clrUsed;
316         }
317     }
318     if (numBits) {
319         *numBits = nBits;
320     }
321     if (comp) {
322         *comp = compression;
323     }
324     return 1;
325 }
326 
327 static int
CommonRead(Tcl_Interp * interp,tkimg_MFile * handle,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY)328 CommonRead(
329     Tcl_Interp *interp,
330     tkimg_MFile *handle,
331     Tk_PhotoHandle imageHandle,
332     int destX, int destY,
333     int width, int height,
334     int srcX, int srcY
335 ) {
336     Tk_PhotoImageBlock block;
337     int numBits, bytesPerLine, numCols, comp, x, y;
338     int fileWidth, fileHeight;
339     unsigned char *colorMap = NULL;
340     unsigned int intMask[3];
341     BitmapChannel masks[3];
342     char buf[10];
343     unsigned char *line = NULL, *expline = NULL;
344     unsigned short rgb;
345 
346     CommonMatch(handle, &fileWidth, &fileHeight, &colorMap, &numBits,
347                  &numCols, &comp, intMask);
348 
349     if (numBits == 16) {
350         if (comp == BI_BITFIELDS) {
351             GetChannelMasks(intMask, masks);
352         } else {
353             masks[0].mask     = 0x7c00;
354             masks[0].shiftin  = 10;
355             masks[0].shiftout = 3;
356             masks[1].mask     = 0x03e0;
357             masks[1].shiftin  = 5;
358             masks[1].shiftout = 3;
359             masks[2].mask     = 0x001f;
360             masks[2].shiftin  = 0;
361             masks[2].shiftout = 3;
362         }
363     }
364 
365     /* printf("reading %d-bit BMP %dx%d\n", numBits, width, height); */
366     if (comp == BI_RLE8 || comp == BI_RLE4) {
367         tkimg_ReadBuffer(1);
368     }
369 
370     if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) {
371         goto error;
372     }
373 
374     bytesPerLine = ((numBits * fileWidth + 31)/32)*4;
375 
376     /* printf("bytesPerLine = %d numBits=%d (%dx%d)\n",
377      *        bytesPerLine, numBits, width, height);
378      */
379     block.pixelSize = 3;
380     block.pitch = width * 3;
381     block.width = width;
382     block.height = 1;
383     block.offset[0] = 2;
384     block.offset[1] = 1;
385     block.offset[2] = 0;
386     block.offset[3] = block.offset[0];
387 
388     if (comp == BI_RGB || comp == BI_BITFIELDS) {       /* No compression */
389         line = (unsigned char *) ckalloc(bytesPerLine);
390         for(y=srcY+height; y<fileHeight; y++) {
391             tkimg_Read2(handle, (char *)line, bytesPerLine);
392         }
393         switch (numBits) {
394             case 32:
395                 block.pixelPtr = expline = (unsigned char *) ckalloc(3*width);
396                 for( y = height-1; y>=0; y--) {
397                     tkimg_Read2(handle, (char *)line, bytesPerLine);
398                     for (x = srcX; x < (srcX+width); x++) {
399                         *expline++ = line[x*4 + 0];
400                         *expline++ = line[x*4 + 1];
401                         *expline++ = line[x*4 + 2];
402                     }
403                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block,
404                             destX, destY+y, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
405                         goto error;
406                     }
407                     expline = block.pixelPtr;
408                 }
409                 break;
410             case 24:
411                 block.pixelPtr = line + srcX*3;
412                 for( y = height-1; y>=0; y--) {
413                     tkimg_Read2(handle, (char *)line, bytesPerLine);
414                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block,
415                             destX, destY+y, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
416                         goto error;
417                     }
418                 }
419                 break;
420             case 16:
421                 block.pixelPtr = expline = (unsigned char *) ckalloc(3*width);
422                 for( y = height-1; y>=0; y--) {
423                     tkimg_Read2(handle, (char *)line, bytesPerLine);
424                     for (x = srcX; x < (srcX+width); x++) {
425                         rgb = getUInt16 (&line[x*2]);
426                         *expline++ = ((rgb & masks[2].mask) >> masks[2].shiftin) << masks[2].shiftout;
427                         *expline++ = ((rgb & masks[1].mask) >> masks[1].shiftin) << masks[1].shiftout;
428                         *expline++ = ((rgb & masks[0].mask) >> masks[0].shiftin) << masks[0].shiftout;
429                     }
430                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block,
431                             destX, destY+y, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
432                         goto error;
433                     }
434                     expline = block.pixelPtr;
435                 }
436                 break;
437             case 8:
438                 block.pixelPtr = expline = (unsigned char *) ckalloc(3*width);
439                 for( y = height-1; y>=0; y--) {
440                     tkimg_Read2(handle, (char *)line, bytesPerLine);
441                     for (x = srcX; x < (srcX+width); x++) {
442                         memcpy(expline, colorMap+(3*line[x]),3);
443                         expline += 3;
444                     }
445                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block,
446                             destX, destY+y, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
447                         goto error;
448                     }
449                     expline = block.pixelPtr;
450                 }
451                 break;
452             case 4:
453                 block.pixelPtr = expline = (unsigned char *) ckalloc(3*width);
454                 for( y = height-1; y>=0; y--) {
455                     int c;
456                     tkimg_Read2(handle, (char *)line, bytesPerLine);
457                     for (x = srcX; x < (srcX+width); x++) {
458                         if (x&1) {
459                             c = line[x/2] & 0x0f;
460                         } else {
461                             c = line[x/2] >> 4;
462                         }
463                         memcpy(expline, colorMap+(3*c),3);
464                         expline += 3;
465                     }
466                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block,
467                             destX, destY+y, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
468                         goto error;
469                     }
470                     expline = block.pixelPtr;
471                 }
472                 break;
473             case 1:
474                 block.pixelPtr = expline = (unsigned char *) ckalloc(3*width);
475                 for( y = height-1; y>=0; y--) {
476                     int c;
477                     tkimg_Read2(handle, (char *)line, bytesPerLine);
478                     for (x = srcX; x < (srcX+width); x++) {
479                         c = (line[x/8] >> (7-(x%8))) & 1;
480                         memcpy(expline, colorMap+(3*c),3);
481                         expline += 3;
482                     }
483                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block,
484                             destX, destY+y, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
485                         goto error;
486                     }
487                     expline = block.pixelPtr;
488                 }
489                 break;
490             default:
491                 sprintf(buf, "%d", numBits);
492                 Tcl_AppendResult(interp, buf,
493                         "-bits BMP file not (yet) supported", (char *) NULL);
494                 goto error;
495         }
496     } else {            /* RLE Compression */
497         int i, j, c;
498         unsigned char howMuch;
499         unsigned char rleBuf[2];
500         unsigned char rleDelta[2];
501         unsigned char val;
502 
503         x = srcX;
504         y = fileHeight - 1;
505         block.pixelPtr = expline = (unsigned char *) ckalloc (3*fileWidth);
506 
507         if (numBits != 4 && numBits != 8) {
508             sprintf (buf, "%d", numBits);
509             Tcl_AppendResult(interp, "RLE compression not supported for ",
510                               buf, "-bit pixel values", (char *) NULL);
511             goto error;
512         }
513 
514         while (1) {
515             if (2 != tkimg_Read2(handle, (char *)rleBuf, 2)) {
516                 Tcl_AppendResult(interp, "Unexpected EOF", (char *) NULL);
517                 goto error;
518             }
519             /* printf("In: (%d %X) --> \n", rleBuf[0], rleBuf[1]); */
520             if (rleBuf[0] != 0) {
521                 howMuch = rleBuf[0];
522                 switch (numBits) {
523                     case 8: {
524                         for (i=0; i<howMuch; i++, x++) {
525                             memcpy (expline, colorMap + 3*rleBuf[1], 3);
526                             expline += 3;
527                         }
528                         break;
529                     }
530                     case 4: {
531                         for (i=0; i<howMuch; i++, x++) {
532                             if (x&1) {
533                                 c = rleBuf[1] & 0x0f;
534                             } else {
535                                 c = rleBuf[1] >> 4;
536                             }
537                             memcpy(expline, colorMap + 3*c, 3);
538                             if (x>=srcX+width-1) {
539                                 break;
540                             }
541                             expline += 3;
542                         }
543                         break;
544                     }
545                 }
546             } else {
547                 if (rleBuf[1]>2) {
548                     /* uncompressed record */
549                     howMuch = rleBuf[1];
550                     /* printf ("Uncompressed: %d\n", howMuch); fflush (stdout); */
551                     switch (numBits) {
552                         case 8: {
553                             for (i=0; i<howMuch; i++, x++) {
554                                 if (1 != tkimg_Read2(handle, (char *)&val, 1)) {
555                                     Tcl_AppendResult(interp, "Unexpected EOF", (char *)NULL);
556                                     goto error;
557                                 }
558                                 memcpy(expline, colorMap + 3*val, 3);
559                                 expline += 3;
560                             }
561                             break;
562                         }
563                         case 4: {
564                             for (i=0; i<howMuch; i+=2) {
565                                 if (1 != tkimg_Read2(handle, (char *)&val, 1)) {
566                                     Tcl_AppendResult(interp, "Unexpected EOF", (char *)NULL);
567                                     goto error;
568                                 }
569                                 for (j=0; j<2; j++, x++) {
570                                     if (x&1) {
571                                         c = val & 0x0f;
572                                     } else {
573                                         c = val >> 4;
574                                     }
575                                     memcpy(expline, colorMap + 3*c, 3);
576                                     if (x>=srcX+width-1) {
577                                         break;
578                                     }
579                                     expline += 3;
580                                 }
581                             }
582                             break;
583                         }
584                     }
585 
586                     if ((howMuch % 2) && (numBits==4)) {
587                         howMuch++;
588                     }
589 
590                     if ((howMuch / (8 / numBits)) % 2) {
591                         if (1 != tkimg_Read2(handle, (char *)&val, 1)) {
592                             Tcl_AppendResult(interp, "Unexpected EOF", (char *)NULL);
593                             goto error;
594                         }
595                     }
596                 } else if (rleBuf[1]==0) {
597                     /* End of line */
598                     /* printf("New line: y=%d x=%d\n", y, x); fflush(stdout); */
599                     if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, destY+y,
600                             width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
601                         goto error;
602                     }
603                     y--;
604                     x = srcX;
605                     expline = block.pixelPtr;
606                 } else if (rleBuf[1]==1) {
607                     /* End of bitmap */
608                     break;
609                 } else if (rleBuf[1]==2) {
610                     /* Deltarecord */
611                     /* printf("Deltarecord\n"); fflush(stdout); */
612                     if (2 != tkimg_Read2(handle, (char *) rleDelta, 2)) {
613                         Tcl_AppendResult(interp, "Unexpected EOF", (char *) NULL);
614                         goto error;
615                     }
616                     x += rleDelta[0];
617                     y += rleDelta[1];
618                 }
619             }
620         }
621     }
622     tkimg_ReadBuffer(0);
623 
624     if (colorMap) {
625         ckfree((char *) colorMap);
626     }
627     if (line) {
628         ckfree((char *) line);
629     }
630     if (expline) {
631         ckfree((char *) block.pixelPtr);
632     }
633     return TCL_OK ;
634 
635 error:
636     tkimg_ReadBuffer(0);
637     if (colorMap) {
638         ckfree((char *) colorMap);
639     }
640     if (line) {
641         ckfree((char *) line);
642     }
643     if (expline) {
644         ckfree((char *) block.pixelPtr);
645     }
646     return TCL_ERROR;
647 }
648 
649 static int
CommonWrite(Tcl_Interp * interp,Tcl_Obj * format,tkimg_MFile * handle,Tk_PhotoImageBlock * blockPtr)650 CommonWrite(
651     Tcl_Interp *interp,
652     Tcl_Obj *format,
653     tkimg_MFile *handle,
654     Tk_PhotoImageBlock *blockPtr
655 ) {
656     int bperline, nbytes, ncolors, i, x, y, greenOffset, blueOffset, alphaOffset;
657     unsigned char *imagePtr, *pixelPtr;
658     unsigned char buf[4];
659     int colors[256];
660     int resX=75*39, resY=75*39;
661     int objc = 0;
662     Tcl_Obj **objv = NULL;
663 
664     /* Decode resolution parameter -resolution List */
665     if (tkimg_ListObjGetElements(interp, format, &objc, &objv) != TCL_OK) {
666         return TCL_ERROR;
667     }
668     /* List parameter given ? */
669     if (objc > 1) {
670         Tcl_Obj *objList;
671         int nBytes;
672         double fResX = -1, fResY = -1, fFactor = 0;
673         char unit = '\0';
674         char *c = Tcl_GetStringFromObj(objv[1], &nBytes);
675         if ((objc > 3) || ((objc == 3) && ((c[0] != '-') ||
676                 (c[1] != 'r') || strncmp(c, "-resolution", strlen(c))))) {
677             Tcl_AppendResult(interp, "invalid format: \"",
678                     tkimg_GetStringFromObj2(format, NULL), "\"", (char *) NULL);
679             return TCL_ERROR;
680         }
681         objList = objv[objc-1];
682         if (tkimg_ListObjGetElements(interp, objList, &objc, &objv) != TCL_OK) {
683             return TCL_ERROR;
684         }
685         if (!objc || objc > 3) {
686             Tcl_AppendResult(interp, "Wrong resolution parameters: \"",
687                     tkimg_GetStringFromObj2(objList, NULL), "\"", (char *) NULL);
688             return TCL_ERROR;
689         }
690         if (Tcl_GetDoubleFromObj(interp, objv[0], &fResX) != TCL_OK) {
691             return TCL_ERROR;
692         }
693         if ( fResX < 0 || fResX > 1e20 ) {
694             Tcl_AppendResult(interp, "Wrong resolution: \"",
695                 tkimg_GetStringFromObj2(objv[0], NULL), "\"", (char *) NULL);
696             return TCL_ERROR;
697         }
698         /* more than xRes value given ? */
699         if (objc > 1) {
700             /* check last for unit */
701             c = Tcl_GetStringFromObj(objv[objc-1], &nBytes);
702             if ( nBytes == 1 && ( c[0] == 'c' || c[0] == 'i' || c[0] == 'm' || c[0] == 'p') ) {
703                 unit = c[0];
704                 objc--;
705             }
706             if (objc > 2) {
707                 Tcl_AppendResult(interp, "Wrong unit: \"",
708                     tkimg_GetStringFromObj2(objv[objc-1], NULL), "\"", (char *) NULL);
709                 return TCL_ERROR;
710             }
711             if (objc > 1) {
712                 if (Tcl_GetDoubleFromObj(interp, objv[1], &fResY) != TCL_OK) {
713                     return TCL_ERROR;
714                 }
715                 if ( fResY < 0 || fResY > 1e20 ) {
716                     Tcl_AppendResult(interp, "Wrong resolution: \"",
717                         tkimg_GetStringFromObj2(objv[0], NULL), "\"", (char *) NULL);
718                     return TCL_ERROR;
719                 }
720             }
721         }
722         /* Process unit and eventually get factor value */
723         switch (unit) {
724         case 'c': /* centimeter */
725             fFactor = 100.0;
726             break;
727         case 'i': /* inches */
728             fFactor = 1.0/0.0254;
729             break;
730         case 'm': /* millimeter */
731             fFactor = 1000.0;
732             break;
733         case 'p': /* printer points (1/72 inch) */
734             fFactor = 72.0/0.0254;
735             break;
736         default: /* no unit given - 75*39 as X resolution */
737             if (fResX == 0) {
738                 resX = 0;
739                 resY = 0;
740             } else {
741                 if (fResY != -1) {
742                     resY = (int) ( fResY * 75.0*39.0 / fResX + 0.5);
743                 }
744             }
745             break;
746         }
747         if (fFactor != 0) {
748             resX = (int) ( fResX * fFactor + 0.5);
749             if (fResY == -1) {
750                 resY = resX;
751             } else {
752                 resY = (int) ( fResY * fFactor + 0.5);
753             }
754         }
755     }
756 
757     greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
758     blueOffset  = blockPtr->offset[2] - blockPtr->offset[0];
759     alphaOffset = blockPtr->offset[0];
760     if (alphaOffset < blockPtr->offset[2]) {
761         alphaOffset = blockPtr->offset[2];
762     }
763     if (++alphaOffset < blockPtr->pixelSize) {
764         alphaOffset -= blockPtr->offset[0];
765     } else {
766         alphaOffset = 0;
767     }
768     ncolors = 0;
769     for (y = 0; ncolors <= 256 && y < blockPtr->height; y++) {
770         pixelPtr = blockPtr->pixelPtr + y*blockPtr->pitch + blockPtr->offset[0];
771         for (x=0; ncolors <= 256 && x<blockPtr->width; x++) {
772             int pixel;
773             if (alphaOffset && (pixelPtr[alphaOffset] == 0))
774                 pixel = 0xd9d9d9;
775             else
776                 pixel = (pixelPtr[0]<<16) | (pixelPtr[greenOffset]<<8) | pixelPtr[blueOffset];
777             for (i = 0; i < ncolors && pixel != colors[i]; i++);
778             if (i == ncolors) {
779                 if (ncolors < 256) {
780                     colors[ncolors] = pixel;
781                 }
782                 ncolors++;
783             }
784             pixelPtr += blockPtr->pixelSize;
785         }
786     }
787     if (ncolors <= 256 && (blockPtr->width * blockPtr->height >= 512)) {
788         while (ncolors < 256) {
789             colors[ncolors++] = 0;
790         }
791         nbytes = 1;
792     } else {
793         nbytes = 3;
794         ncolors = 0;
795     }
796 
797     bperline = ((blockPtr->width  * nbytes + 3) / 4) * 4;
798 
799     tkimg_Write2(handle,"BM", 2);
800     putint(handle, 54 + (ncolors*4) + bperline * blockPtr->height);
801     putint(handle, 0);
802     putint(handle, 54 + (ncolors*4));
803     putint(handle, 40);
804     putint(handle, blockPtr->width);
805     putint(handle, blockPtr->height);
806     putint(handle, 1 + (nbytes<<19));
807     putint(handle, 0);
808     putint(handle, bperline * blockPtr->height);
809     putint(handle, resX);
810     putint(handle, resY);
811     putint(handle, ncolors);
812     putint(handle, ncolors);
813 
814     for (i = 0; i < ncolors ; i++) {
815         putint(handle, colors[i]);
816     }
817 
818     bperline -= blockPtr->width * nbytes;
819 
820     imagePtr =  blockPtr->pixelPtr + blockPtr->offset[0]
821             + blockPtr->height * blockPtr->pitch;
822     for (y = 0; y < blockPtr->height; y++) {
823         pixelPtr = imagePtr -= blockPtr->pitch;
824         for (x=0; x<blockPtr->width; x++) {
825             if (ncolors) {
826                 int pixel;
827                 if (alphaOffset && (pixelPtr[alphaOffset] == 0))
828                     pixel = 0xd9d9d9;
829                 else
830                     pixel = (pixelPtr[0]<<16)|(pixelPtr[greenOffset]<<8)|pixelPtr[blueOffset];
831                 for (i = 0; i < ncolors && pixel != colors[i]; i += 1);
832                 buf[0] = i;
833             } else if (alphaOffset && (pixelPtr[alphaOffset] == 0)) {
834                 buf[0] = buf[1] = buf[2] = 0xd9;
835             } else {
836                 buf[0] = pixelPtr[blueOffset];
837                 buf[1] = pixelPtr[greenOffset];
838                 buf[2] = pixelPtr[0];
839             }
840             tkimg_Write2(handle, (char *) buf, nbytes);
841             pixelPtr += blockPtr->pixelSize;
842         }
843         if (bperline) {
844             tkimg_Write2(handle, "\0\0\0", bperline);
845         }
846     }
847     return(TCL_OK);
848 }
849 
850 static void
putint(tkimg_MFile * handle,int i)851 putint(
852     tkimg_MFile *handle,
853     int i
854 ) {
855     unsigned char buf[4];
856     buf[0] = i;
857     buf[1] = i>>8;
858     buf[2] = i>>16;
859     buf[3] = i>>24;
860     tkimg_Write2(handle, (char *) buf, 4);
861 }
862