1 /*
2  * imgXBM.c --
3  *
4  *	A photo image file handler for XBM files.
5  *
6  * Written by:
7  *	Jan Nijtmans
8  *	email: nijtmans@users.sourceforge.net
9  *	url:   http://purl.oclc.org/net/nijtmans/
10  *
11  * <paul@poSoft.de> Paul Obermeier
12  * Feb 2001:
13  *      - Bugfix  in CommonWrite: const char *fileName was overwritten.
14  */
15 
16 /*
17  * Generic initialization code, parameterized via CPACKAGE and PACKAGE.
18  */
19 
20 #include "init.c"
21 
22 
23 /* constants used only in this file */
24 
25 #define MAX_BUFFER 4096
26 
27 /*
28  * The following data structure is used to describe the state of
29  * parsing a bitmap file or string.  It is used for communication
30  * between TkGetBitmapData and NextBitmapWord.
31  */
32 
33 #define MAX_WORD_LENGTH 100
34 typedef struct ParseInfo {
35     tkimg_MFile handle;
36     char word[MAX_WORD_LENGTH+1];
37 				/* Current word of bitmap data, NULL
38 				 * terminated. */
39     int wordLength;		/* Number of non-NULL bytes in word. */
40 } ParseInfo;
41 
42 /*
43  * Prototypes for local procedures defined in this file:
44  */
45 
46 static int CommonRead(Tcl_Interp *interp,
47 	ParseInfo *parseInfo,
48 	Tcl_Obj *format, Tk_PhotoHandle imageHandle,
49 	int destX, int destY, int width, int height,
50 	int srcX, int srcY);
51 static int CommonWrite(Tcl_Interp *interp,
52 	const char *fileName, Tcl_DString *dataPtr,
53 	Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr);
54 
55 static int ReadXBMFileHeader(ParseInfo *parseInfo,
56 	int *widthPtr, int *heightPtr);
57 static int NextBitmapWord(ParseInfo *parseInfoPtr);
58 
59 /*
60  *----------------------------------------------------------------------
61  *
62  * ObjMatch --
63  *
64  *	This procedure is invoked by the photo image type to see if
65  *	a datastring contains image data in XBM format.
66  *
67  * Results:
68  *	The return value is >0 if the first characters in data look
69  *	like XBM data, and 0 otherwise.
70  *
71  * Side effects:
72  *	none
73  *
74  *----------------------------------------------------------------------
75  */
ObjMatch(Tcl_Obj * data,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)76 static int ObjMatch(
77     Tcl_Obj *data,		/* The data supplied by the image */
78     Tcl_Obj *format,		/* User-specified format string, or NULL. */
79     int *widthPtr,		/* The dimensions of the image are */
80 	int *heightPtr,			 /* returned here if the file is a valid
81 				 * raw XBM file. */
82     Tcl_Interp *interp
83 ) {
84     ParseInfo parseInfo;
85     size_t length;
86 
87     parseInfo.handle.data = (char *)tkimg_GetStringFromObj2(data, &length);
88     parseInfo.handle.length = length;
89     parseInfo.handle.state = IMG_STRING;
90 
91     return ReadXBMFileHeader(&parseInfo, widthPtr, heightPtr);
92 }
93 
94 
95 /*
96  *----------------------------------------------------------------------
97  *
98  * ChnMatch --
99  *
100  *	This procedure is invoked by the photo image type to see if
101  *	a channel contains image data in XBM format.
102  *
103  * Results:
104  *	The return value is >0 if the first characters in channel "chan"
105  *	look like XBM data, and 0 otherwise.
106  *
107  * Side effects:
108  *	The access position in chan may change.
109  *
110  *----------------------------------------------------------------------
111  */
112 
ChnMatch(Tcl_Channel chan,const char * fileName,Tcl_Obj * format,int * widthPtr,int * heightPtr,Tcl_Interp * interp)113 static int ChnMatch(
114     Tcl_Channel chan,		/* The image channel, open for reading. */
115     const char *fileName,	/* The name of the image file. */
116     Tcl_Obj *format,		/* User-specified format object, or NULL. */
117     int *widthPtr,		/* The dimensions of the image are */
118 	int *heightPtr,			/* returned here if the file is a valid
119 				 * raw XBM file. */
120     Tcl_Interp *interp
121 ) {
122     ParseInfo parseInfo;
123 
124     parseInfo.handle.data = (char *) chan;
125     parseInfo.handle.state = IMG_CHAN;
126 
127     return ReadXBMFileHeader(&parseInfo, widthPtr, heightPtr);
128 }
129 
130 /*
131  *----------------------------------------------------------------------
132  *
133  * CommonRead --
134  *
135  *	This procedure is called by the photo image type to read
136  *	XBM format data from a file or string and write it into a
137  *	given photo image.
138  *
139  * Results:
140  *	A standard TCL completion code.  If TCL_ERROR is returned
141  *	then an error message is left in interp->result.
142  *
143  * Side effects:
144  *	The access position in file f is changed (if read from file)
145  *	and new data is added to the image given by imageHandle.
146  *
147  *----------------------------------------------------------------------
148  */
149 static int
CommonRead(Tcl_Interp * interp,ParseInfo * parseInfo,Tcl_Obj * format,Tk_PhotoHandle imageHandle,int destX,int destY,int width,int height,int srcX,int srcY)150 CommonRead(
151     Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
152     ParseInfo *parseInfo,
153     Tcl_Obj *format,		/* User-specified format string, or NULL. */
154     Tk_PhotoHandle imageHandle,	/* The photo image to write into. */
155     int destX, int destY,	/* Coordinates of top-left pixel in
156 				 * photo image to be written to. */
157     int width, int height,	/* Dimensions of block of photo image to
158 				 * be written to. */
159     int srcX, int srcY		/* Coordinates of top-left pixel to be used
160 				 * in image being read. */
161 ) {
162     Tk_PhotoImageBlock block;
163     int fileWidth, fileHeight;
164     int numBytes, row, col, value, i;
165     unsigned char *data, *pixelPtr;
166     char *end;
167     int result = TCL_OK;
168 
169     ReadXBMFileHeader(parseInfo, &fileWidth, &fileHeight);
170 
171     if ((srcX + width) > fileWidth) {
172 	width = fileWidth - srcX;
173     }
174     if ((srcY + height) > fileHeight) {
175 	height = fileHeight - srcY;
176     }
177     if ((width <= 0) || (height <= 0)
178 	|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
179 	return TCL_OK;
180     }
181 
182     if (tkimg_PhotoExpand(interp, imageHandle, destX + width, destY + height) == TCL_ERROR) {
183 	return TCL_ERROR;
184     }
185 
186     numBytes = ((fileWidth+7)/8)*32;
187     block.width = fileWidth;
188     block.height = 1;
189     block.pixelSize = 4;
190     block.offset[0] = 0;
191     block.offset[1] = 1;
192     block.offset[2] = 2;
193     block.offset[3] = 3;
194 
195     data = (unsigned char *) ckalloc((unsigned) numBytes);
196     block.pixelPtr = data + srcX*4;
197     for (row = 0; row < srcY + height; row++) {
198 	pixelPtr = data;
199         for (col = 0; col<(numBytes/32); col++) {
200 	    if (NextBitmapWord(parseInfo) != TCL_OK) {
201 		ckfree((char *) data);
202 		return TCL_ERROR;
203 	    }
204 	    value = (int) strtol(parseInfo->word, &end, 0);
205 	    if (end == parseInfo->word) {
206 	    	ckfree((char *) data);
207 	    	return TCL_ERROR;
208 	    }
209 	    for (i=0; i<8; i++) {
210 	        *pixelPtr++ = 0;
211 	        *pixelPtr++ = 0;
212 	        *pixelPtr++ = 0;
213 	        *pixelPtr++ = (value & 0x1)? 255:0;
214 	  	value >>= 1;
215 	    }
216 	}
217 	if (row >= srcY) {
218 	    if (tkimg_PhotoPutBlock(interp, imageHandle, &block, destX, destY++, width, 1, TK_PHOTO_COMPOSITE_SET) == TCL_ERROR) {
219 		result = TCL_ERROR;
220 		break;
221 	    }
222 	}
223     }
224     ckfree((char *) data);
225     return result;
226 }
227 
228 /*
229  *----------------------------------------------------------------------
230  *
231  * ChnRead --
232  *
233  *	This procedure is called by the photo image type to read
234  *	XBM format data from a channel and write it into a given
235  *	photo image.
236  *
237  * Results:
238  *	A standard TCL completion code.  If TCL_ERROR is returned
239  *	then an error message is left in interp->result.
240  *
241  * Side effects:
242  *	The access position in channel chan is changed, and new data is
243  *	added to the image given by imageHandle.
244  *
245  *----------------------------------------------------------------------
246  */
247 
248 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)249 ChnRead(
250     Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
251     Tcl_Channel chan,		/* The image channel, open for reading. */
252     const char *fileName,	/* The name of the image file. */
253     Tcl_Obj *format,		/* User-specified format object, or NULL. */
254     Tk_PhotoHandle imageHandle,	/* The photo image to write into. */
255     int destX, int destY,	/* Coordinates of top-left pixel in
256 				 * photo image to be written to. */
257     int width, int height,	/* Dimensions of block of photo image to
258 				 * be written to. */
259     int srcX, int srcY		/* Coordinates of top-left pixel to be used
260 				 * in image being read. */
261 ) {
262     ParseInfo parseInfo;
263 
264     parseInfo.handle.data = (char *) chan;
265     parseInfo.handle.state = IMG_CHAN;
266 
267     return CommonRead(interp, &parseInfo, format, imageHandle,
268 		destX, destY, width, height, srcX, srcY);
269 }
270 
271 /*
272  *----------------------------------------------------------------------
273  *
274  * ObjRead --
275  *
276  *	This procedure is called by the photo image type to read
277  *	XBM format data from a string and write it into a given
278  *	photo image.
279  *
280  * Results:
281  *	A standard TCL completion code.  If TCL_ERROR is returned
282  *	then an error message is left in interp->result.
283  *
284  * Side effects:
285  *	New data is added to the image given by imageHandle.
286  *
287  *----------------------------------------------------------------------
288  */
289 
290 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)291 ObjRead(
292     Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
293     Tcl_Obj *data,
294     Tcl_Obj *format,		/* User-specified format string, or NULL. */
295     Tk_PhotoHandle imageHandle,	/* The photo image to write into. */
296     int destX, int destY,	/* Coordinates of top-left pixel in
297 				 * photo image to be written to. */
298     int width, int height,	/* Dimensions of block of photo image to
299 				 * be written to. */
300     int srcX, int srcY		/* Coordinates of top-left pixel to be used
301 				 * in image being read. */
302 ) {
303     ParseInfo parseInfo;
304     size_t length;
305 
306     parseInfo.handle.data = (char *)tkimg_GetStringFromObj2(data, &length);
307     parseInfo.handle.length = length;
308     parseInfo.handle.state = IMG_STRING;
309 
310     return CommonRead(interp, &parseInfo, format, imageHandle,
311 		destX, destY, width, height, srcX, srcY);
312 }
313 
314 /*
315  *----------------------------------------------------------------------
316  *
317  * ReadXBMFileHeader --
318  *
319  *	This procedure reads the XBM header from the beginning of a
320  *	XBM file and returns information from the header.
321  *
322  * Results:
323  *	The return value is 1 if file "f" appears to start with a valid
324  *      XBM header, and 0 otherwise.  If the header is valid,
325  *	then *widthPtr and *heightPtr are modified to hold the
326  *	dimensions of the image and *numColors holds the number of
327  *	colors and byteSize the number of bytes used for 1 pixel.
328  *
329  * Side effects:
330  *	The access position in f advances.
331  *
332  *----------------------------------------------------------------------
333  */
334 
335 #define UCHAR(c) ((unsigned char) (c))
336 
337 /*
338  *----------------------------------------------------------------------
339  *
340  * NextBitmapWord --
341  *
342  *	This procedure retrieves the next word of information (stuff
343  *	between commas or white space) from a bitmap description.
344  *
345  * Results:
346  *	Returns TCL_OK if all went well.  In this case the next word,
347  *	and its length, will be availble in *parseInfoPtr.  If the end
348  *	of the bitmap description was reached then TCL_ERROR is returned.
349  *
350  * Side effects:
351  *	None.
352  *
353  *----------------------------------------------------------------------
354  */
355 
356 static int
NextBitmapWord(ParseInfo * parseInfoPtr)357 NextBitmapWord(
358     ParseInfo *parseInfoPtr		/* Describes what we're reading
359 					 * and where we are in it. */
360 ) {
361     char *dst, buf;
362     int num;
363 
364     parseInfoPtr->wordLength = 0;
365     dst = parseInfoPtr->word;
366 
367     for (num=tkimg_Read2(&parseInfoPtr->handle,&buf,1); isspace(UCHAR(buf)) || (buf == ',');
368 	    num=tkimg_Read2(&parseInfoPtr->handle,&buf,1)) {
369 	if (num == 0) {
370 	    return TCL_ERROR;
371 	}
372     }
373     for ( ; !isspace(UCHAR(buf)) && (buf != ',') && (num != 0);
374 	    num=tkimg_Read2(&parseInfoPtr->handle,&buf,1)) {
375 	*dst = buf;
376 	dst++;
377 	parseInfoPtr->wordLength++;
378 	if (num == 0 || parseInfoPtr->wordLength > MAX_WORD_LENGTH) {
379 	    return TCL_ERROR;
380 	}
381     }
382 
383     if (parseInfoPtr->wordLength == 0) {
384 	return TCL_ERROR;
385     }
386     parseInfoPtr->word[parseInfoPtr->wordLength] = 0;
387     return TCL_OK;
388 }
389 
390 static int
ReadXBMFileHeader(ParseInfo * pi,int * widthPtr,int * heightPtr)391 ReadXBMFileHeader(
392     ParseInfo *pi,
393     int *widthPtr, int *heightPtr	/* The dimensions of the image are
394 					 * returned here. */
395 ) {
396     int width, height;
397     char *end;
398 
399     /*
400      * Parse the lines that define the dimensions of the bitmap,
401      * plus the first line that defines the bitmap data (it declares
402      * the name of a data variable but doesn't include any actual
403      * data).  These lines look something like the following:
404      *
405      *		#define foo_width 16
406      *		#define foo_height 16
407      *		#define foo_x_hot 3
408      *		#define foo_y_hot 3
409      *		static char foo_bits[] = {
410      *
411      * The x_hot and y_hot lines may or may not be present.  It's
412      * important to check for "char" in the last line, in order to
413      * reject old X10-style bitmaps that used shorts.
414      */
415     width = 0;
416     height = 0;
417     while (1) {
418 	if (NextBitmapWord(pi) != TCL_OK) {
419 	    return 0;
420 	}
421 	if ((pi->wordLength >= 6) && (pi->word[pi->wordLength-6] == '_')
422 		&& (strcmp(pi->word+pi->wordLength-6, "_width") == 0)) {
423 	    if (NextBitmapWord(pi) != TCL_OK) {
424 		return 0;
425 	    }
426 	    width = strtol(pi->word, &end, 0);
427 	    if ((end == pi->word) || (*end != 0)) {
428 		return 0;
429 	    }
430 	} else if ((pi->wordLength >= 7) && (pi->word[pi->wordLength-7] == '_')
431 		&& (strcmp(pi->word+pi->wordLength-7, "_height") == 0)) {
432 	    if (NextBitmapWord(pi) != TCL_OK) {
433 		return 0;
434 	    }
435 	    height = strtol(pi->word, &end, 0);
436 	    if ((end == pi->word) || (*end != 0)) {
437 		return 0;
438 	    }
439 	} else if ((pi->wordLength >= 6) && (pi->word[pi->wordLength-6] == '_')
440 		&& (strcmp(pi->word+pi->wordLength-6, "_x_hot") == 0)) {
441 	    if (NextBitmapWord(pi) != TCL_OK) {
442 		return 0;
443 	    }
444 	    strtol(pi->word, &end, 0);
445 	    if ((end == pi->word) || (*end != 0)) {
446 		return 0;
447 	    }
448 	} else if ((pi->wordLength >= 6) && (pi->word[pi->wordLength-6] == '_')
449 		&& (strcmp(pi->word+pi->wordLength-6, "_y_hot") == 0)) {
450 	    if (NextBitmapWord(pi) != TCL_OK) {
451 		return 0;
452 	    }
453 	    strtol(pi->word, &end, 0);
454 	    if ((end == pi->word) || (*end != 0)) {
455 		return 0;
456 	    }
457 	} else if ((pi->word[0] == 'c') && (strcmp(pi->word, "char") == 0)) {
458 	    while (1) {
459 		if (NextBitmapWord(pi) != TCL_OK) {
460 		    return 0;
461 		}
462 		if ((pi->word[0] == '{') && (pi->word[1] == 0)) {
463 		    goto getData;
464 		}
465 	    }
466 	} else if ((pi->word[0] == '{') && (pi->word[1] == 0)) {
467 
468 	    return 0;
469 	}
470     }
471 
472 getData:
473     if (width == 0 || height == 0) {
474         return 0;
475     }
476     *widthPtr = width;
477     *heightPtr = height;
478     return 1;
479 }
480 
481 
482 /*
483  *----------------------------------------------------------------------
484  *
485  * ChnWrite
486  *
487  *	Writes a XBM image to a file. Just calls CommonWrite
488  *      with appropriate arguments.
489  *
490  * Results:
491  *	Returns the return value of CommonWrite
492  *
493  * Side effects:
494  *	A file is (hopefully) created on success.
495  *
496  *----------------------------------------------------------------------
497  */
498 static int
ChnWrite(Tcl_Interp * interp,const char * fileName,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)499 ChnWrite(
500     Tcl_Interp *interp,
501     const char *fileName,
502     Tcl_Obj *format,
503     Tk_PhotoImageBlock *blockPtr
504 ) {
505     return CommonWrite(interp, fileName, (Tcl_DString *)NULL, format, blockPtr);
506 }
507 
508 
509 /*
510  *----------------------------------------------------------------------
511  *
512  * StringWrite
513  *
514  *	Writes a XBM image to a string. Just calls CommonWrite
515  *      with appropriate arguments.
516  *
517  * Results:
518  *	Returns the return value of CommonWrite
519  *
520  * Side effects:
521  *	The Tcl_DString dataPtr is modified on success.
522  *
523  *----------------------------------------------------------------------
524  */
StringWrite(Tcl_Interp * interp,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)525 static int StringWrite(
526     Tcl_Interp *interp,
527     Tcl_Obj *format,
528     Tk_PhotoImageBlock *blockPtr
529 ) {
530     int result;
531     Tcl_DString data;
532 
533     Tcl_DStringInit(&data);
534     result = CommonWrite(interp, "InlineData", &data, format, blockPtr);
535 
536     if (result == TCL_OK) {
537 	Tcl_DStringResult(interp, &data);
538     } else {
539 	Tcl_DStringFree(&data);
540     }
541     return result;
542 }
543 
544 
545 /*
546  * Yes, I know these macros are dangerous. But it should work fine
547  */
548 #define WRITE(buf) { if (chan) Tcl_Write(chan, buf, -1); else Tcl_DStringAppend(dataPtr, buf, -1);}
549 
550 /*
551  *----------------------------------------------------------------------
552  *
553  * CommonWrite
554  *
555  *	This procedure writes a XBM image to the file filename
556  *      (if filename != NULL) or to dataPtr.
557  *
558  * Results:
559  *	Returns TCL_OK on success, or TCL_ERROR on error.
560  *
561  * Side effects:
562  *	varies (see StringWrite and ChnWrite)
563  *
564  *----------------------------------------------------------------------
565  */
566 static int
CommonWrite(Tcl_Interp * interp,const char * fileName,Tcl_DString * dataPtr,Tcl_Obj * format,Tk_PhotoImageBlock * blockPtr)567 CommonWrite(
568     Tcl_Interp *interp,
569     const char *fileName,
570     Tcl_DString *dataPtr,
571     Tcl_Obj *format,
572     Tk_PhotoImageBlock *blockPtr
573 ) {
574     Tcl_Channel chan = (Tcl_Channel) NULL;
575     char buffer[256];
576     unsigned char *pp;
577     int x, y, value, mask;
578     int sep = ' ';
579     int alphaOffset;
580     char *p = (char *) NULL;
581     char *imgName;
582     static const char header[] =
583 "#define %s_width %d\n\
584 #define %s_height %d\n\
585 static char %s_bits[] = {\n";
586 
587     alphaOffset = blockPtr->offset[0];
588     if (alphaOffset < blockPtr->offset[1]) alphaOffset = blockPtr->offset[1];
589     if (alphaOffset < blockPtr->offset[2]) alphaOffset = blockPtr->offset[2];
590     if (++alphaOffset < blockPtr->pixelSize) {
591 	alphaOffset -= blockPtr->offset[0];
592     } else {
593 	alphaOffset = 0;
594     }
595 
596 
597     /* open the output file (if needed) */
598     if (!dataPtr) {
599       chan = Tcl_OpenFileChannel(interp, fileName, "w", 0644);
600       if (!chan) {
601 	return TCL_ERROR;
602       }
603     }
604 
605     /* compute image name */
606     imgName = (char*)ckalloc(strlen(fileName)+1);
607     memcpy (imgName, fileName, strlen(fileName)+1);
608     p = strrchr(imgName, '/');
609     if (p) {
610 	imgName = p+1;
611     }
612     p = strrchr(imgName, '\\');
613     if (p) {
614 	imgName = p+1;
615     }
616     p = strrchr(imgName, ':');
617     if (p) {
618 	imgName = p+1;
619     }
620     p = strchr(imgName, '.');
621     if (p) {
622 	*p = 0;
623     }
624 
625     sprintf(buffer, header, imgName, blockPtr->width, imgName,
626 	    blockPtr->height, imgName);
627     WRITE(buffer);
628 
629     /* write image itself */
630     pp = blockPtr->pixelPtr + blockPtr->offset[0];
631     sep = ' ';
632     for (y = 0; y < blockPtr->height; y++) {
633 	value = 0;
634 	mask  = 1;
635 	for (x = 0; x < blockPtr->width; x++) {
636 	    if (!alphaOffset || pp[alphaOffset]) {
637 		value |= mask;
638 	    } else {
639 		/* make transparent pixel */
640 	    }
641 	    pp += blockPtr->pixelSize;
642 	    mask <<= 1;
643 	    if (mask >= 256)
644              {
645 	      sprintf(buffer,"%c 0x%02x",sep,value);
646 	      WRITE(buffer);
647               value = 0;
648 	      mask = 1;
649 	      sep = ',';
650              }
651 	}
652 	if (mask != 1) {
653 	      sprintf(buffer,"%c 0x%02x",sep, value);
654 	      WRITE(buffer);
655 	}
656 
657 	if (y == blockPtr->height - 1) {
658 	    WRITE("};\n");
659 	} else {
660 	    WRITE(",\n");
661 	    sep = ' ';
662 	}
663     }
664 
665     /* close the channel */
666     if (chan) {
667 	Tcl_Close(interp, chan);
668     }
669     return TCL_OK;
670 }
671