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