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