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