/*
* imgXBM.c --
*
* A photo image file handler for XBM files.
*
* Written by:
* Jan Nijtmans
* CMG Oost-Nederland B.V.
* email: j.nijtmans@chello.nl (private)
* jan.nijtmans@cmg.nl (work)
* url: http://purl.oclc.org/net/nijtmans/
*
*/
#include "tk.h"
#include "tkVMacro.h"
#include "imgInt.h"
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
/* constants used only in this file */
#define MAX_BUFFER 4096
/*
* The following data structure is used to describe the state of
* parsing a bitmap file or string. It is used for communication
* between TkGetBitmapData and NextBitmapWord.
*/
#define MAX_WORD_LENGTH 100
typedef struct ParseInfo {
MFile handle;
char word[MAX_WORD_LENGTH+2];
/* Current word of bitmap data, NULL
* terminated. */
int wordLength; /* Number of non-NULL bytes in word. */
} ParseInfo;
/*
* The format record for the XBM file format:
*/
static int ChnMatchXBM _ANSI_ARGS_((Tcl_Channel chan,
Tcl_Obj *fileName, Tcl_Obj *format, int *widthPtr,
int *heightPtr, Tcl_Interp *interp));
static int ObjMatchXBM _ANSI_ARGS_((Tcl_Obj *data,
Tcl_Obj *format, int *widthPtr, int *heightPtr, Tcl_Interp *interp));
static int ChnReadXBM _ANSI_ARGS_((Tcl_Interp *interp,
Tcl_Channel chan, Tcl_Obj *fileName,
Tcl_Obj *format, Tk_PhotoHandle imageHandle,
int destX, int destY, int width, int height,
int srcX, int srcY));
static int ObjReadXBM _ANSI_ARGS_((Tcl_Interp *interp,
Tcl_Obj *dataObj, Tcl_Obj *format,
Tk_PhotoHandle imageHandle, int destX, int destY,
int width, int height, int srcX, int srcY));
static int ChnWriteXBM _ANSI_ARGS_((Tcl_Interp *interp,
char *fileName, Tcl_Obj *format,
Tk_PhotoImageBlock *blockPtr));
static int StringWriteXBM _ANSI_ARGS_((Tcl_Interp *interp,
Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr));
static int CommonReadXBM _ANSI_ARGS_((Tcl_Interp *interp,
ParseInfo *parseInfo,
Tcl_Obj *format, Tk_PhotoHandle imageHandle,
int destX, int destY, int width, int height,
int srcX, int srcY));
static int CommonWriteXBM _ANSI_ARGS_((Tcl_Interp *interp,
CONST char *fileName, Tcl_DString *dataPtr,
Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr));
Tk_PhotoImageFormat imgFmtXBM = {
"xbm", /* name */
ChnMatchXBM, /* fileMatchProc */
ObjMatchXBM, /* stringMatchProc */
ChnReadXBM, /* fileReadProc */
ObjReadXBM, /* stringReadProc */
ChnWriteXBM, /* fileWriteProc */
StringWriteXBM /* stringWriteProc */
};
/*
* Prototypes for local procedures defined in this file:
*/
static int ReadXBMFileHeader _ANSI_ARGS_((ParseInfo *parseInfo,
int *widthPtr, int *heightPtr));
static int NextBitmapWord _ANSI_ARGS_((ParseInfo *parseInfoPtr));
/*
*----------------------------------------------------------------------
*
* ObjMatchXBM --
*
* This procedure is invoked by the photo image type to see if
* a datastring contains image data in XBM format.
*
* Results:
* The return value is >0 if the first characters in data look
* like XBM data, and 0 otherwise.
*
* Side effects:
* none
*
*----------------------------------------------------------------------
*/
static int
ObjMatchXBM(data, format, widthPtr, heightPtr, interp)
Tcl_Obj *data; /* The data supplied by the image */
Tcl_Obj *format; /* User-specified format string, or NULL. */
int *widthPtr, *heightPtr; /* The dimensions of the image are
* returned here if the file is a valid
* raw XBM file. */
Tcl_Interp *interp;
{
ParseInfo parseInfo;
ImgFixObjMatchProc(&interp, &data, &format, &widthPtr, &heightPtr);
parseInfo.handle.data = ImgGetStringFromObj(data, &parseInfo.handle.length);
parseInfo.handle.state = IMG_STRING;
return ReadXBMFileHeader(&parseInfo, widthPtr, heightPtr);
}
/*
*----------------------------------------------------------------------
*
* ChnMatchXBM --
*
* This procedure is invoked by the photo image type to see if
* a channel contains image data in XBM format.
*
* Results:
* The return value is >0 if the first characters in channel "chan"
* look like XBM data, and 0 otherwise.
*
* Side effects:
* The access position in chan may change.
*
*----------------------------------------------------------------------
*/
static int
ChnMatchXBM(chan, fileName, format, widthPtr, heightPtr, interp)
Tcl_Channel chan; /* The image channel, open for reading. */
Tcl_Obj *fileName; /* The name of the image file. */
Tcl_Obj *format; /* User-specified format object, or NULL. */
int *widthPtr, *heightPtr; /* The dimensions of the image are
* returned here if the file is a valid
* raw XBM file. */
Tcl_Interp *interp;
{
ParseInfo parseInfo;
ImgFixChanMatchProc(&interp, &chan, &fileName, &format, &widthPtr, &heightPtr);
parseInfo.handle.data = (char *) chan;
parseInfo.handle.state = IMG_CHAN;
return ReadXBMFileHeader(&parseInfo, widthPtr, heightPtr);
}
/*
*----------------------------------------------------------------------
*
* CommonReadXBM --
*
* This procedure is called by the photo image type to read
* XBM format data from a file or string and write it into a
* given photo image.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned
* then an error message is left in interp->result.
*
* Side effects:
* The access position in file f is changed (if read from file)
* and new data is added to the image given by imageHandle.
*
*----------------------------------------------------------------------
*/
typedef struct myblock {
Tk_PhotoImageBlock ck;
int dummy; /* extra space for offset[3], if not included already
in Tk_PhotoImageBlock */
} myblock;
#define block bl.ck
static int
CommonReadXBM(interp, parseInfo, format, imageHandle, destX, destY,
width, height, srcX, srcY)
Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
ParseInfo *parseInfo;
Tcl_Obj *format; /* User-specified format string, or NULL. */
Tk_PhotoHandle imageHandle; /* The photo image to write into. */
int destX, destY; /* Coordinates of top-left pixel in
* photo image to be written to. */
int width, height; /* Dimensions of block of photo image to
* be written to. */
int srcX, srcY; /* Coordinates of top-left pixel to be used
* in image being read. */
{
myblock bl;
int fileWidth, fileHeight;
int numBytes, row, col, value, i;
unsigned char *data, *pixelPtr;
char *end;
ReadXBMFileHeader(parseInfo, &fileWidth, &fileHeight);
if ((srcX + width) > fileWidth) {
width = fileWidth - srcX;
}
if ((srcY + height) > fileHeight) {
height = fileHeight - srcY;
}
if ((width <= 0) || (height <= 0)
|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
return TCL_OK;
}
Tk_PhotoExpand(imageHandle, destX + width, destY + height);
numBytes = ((fileWidth+7)/8)*32;
block.width = fileWidth;
block.height = 1;
block.pixelSize = 4;
block.offset[0] = 0;
block.offset[1] = 1;
block.offset[2] = 2;
block.offset[3] = 3;
data = (unsigned char *) ckalloc((unsigned) numBytes);
block.pixelPtr = data + srcX*4;
for (row = 0; row < srcY + height; row++) {
pixelPtr = data;
for (col = 0; col<(numBytes/32); col++) {
if (NextBitmapWord(parseInfo) != TCL_OK) {
ckfree((char *) data);
return TCL_ERROR;
}
value = (int) strtol(parseInfo->word, &end, 0);
if (end == parseInfo->word) {
ckfree((char *) data);
return TCL_ERROR;
}
for (i=0; i<8; i++) {
*pixelPtr++ = 0;
*pixelPtr++ = 0;
*pixelPtr++ = 0;
*pixelPtr++ = (value & 0x1)? 255:0;
value >>= 1;
}
}
if (row >= srcY) {
ImgPhotoPutBlock(imageHandle, &block, destX, destY++, width, 1);
}
}
ckfree((char *) data);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ChnReadXBM --
*
* This procedure is called by the photo image type to read
* XBM format data from a channel and write it into a given
* photo image.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned
* then an error message is left in interp->result.
*
* Side effects:
* The access position in channel chan is changed, and new data is
* added to the image given by imageHandle.
*
*----------------------------------------------------------------------
*/
static int
ChnReadXBM(interp, chan, fileName, format, imageHandle, destX, destY,
width, height, srcX, srcY)
Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
Tcl_Channel chan; /* The image channel, open for reading. */
Tcl_Obj *fileName; /* The name of the image file. */
Tcl_Obj *format; /* User-specified format object, or NULL. */
Tk_PhotoHandle imageHandle; /* The photo image to write into. */
int destX, destY; /* Coordinates of top-left pixel in
* photo image to be written to. */
int width, height; /* Dimensions of block of photo image to
* be written to. */
int srcX, srcY; /* Coordinates of top-left pixel to be used
* in image being read. */
{
ParseInfo parseInfo;
parseInfo.handle.data = (char *) chan;
parseInfo.handle.state = IMG_CHAN;
return CommonReadXBM(interp, &parseInfo, format, imageHandle,
destX, destY, width, height, srcX, srcY);
}
/*
*----------------------------------------------------------------------
*
* ObjReadXBM --
*
* This procedure is called by the photo image type to read
* XBM format data from a string and write it into a given
* photo image.
*
* Results:
* A standard TCL completion code. If TCL_ERROR is returned
* then an error message is left in interp->result.
*
* Side effects:
* New data is added to the image given by imageHandle.
*
*----------------------------------------------------------------------
*/
static int
ObjReadXBM(interp, data, format, imageHandle, destX, destY,
width, height, srcX, srcY)
Tcl_Interp *interp; /* Interpreter to use for reporting errors. */
Tcl_Obj *data;
Tcl_Obj *format; /* User-specified format string, or NULL. */
Tk_PhotoHandle imageHandle; /* The photo image to write into. */
int destX, destY; /* Coordinates of top-left pixel in
* photo image to be written to. */
int width, height; /* Dimensions of block of photo image to
* be written to. */
int srcX, srcY; /* Coordinates of top-left pixel to be used
* in image being read. */
{
ParseInfo parseInfo;
parseInfo.handle.data = ImgGetStringFromObj(data, &parseInfo.handle.length);
parseInfo.handle.state = IMG_STRING;
return CommonReadXBM(interp, &parseInfo, format, imageHandle,
destX, destY, width, height, srcX, srcY);
}
/*
*----------------------------------------------------------------------
*
* ReadXBMFileHeader --
*
* This procedure reads the XBM header from the beginning of a
* XBM file and returns information from the header.
*
* Results:
* The return value is 1 if file "f" appears to start with a valid
* XBM header, and 0 otherwise. If the header is valid,
* then *widthPtr and *heightPtr are modified to hold the
* dimensions of the image and *numColors holds the number of
* colors and byteSize the number of bytes used for 1 pixel.
*
* Side effects:
* The access position in f advances.
*
*----------------------------------------------------------------------
*/
#define UCHAR(c) ((unsigned char) (c))
/*
*----------------------------------------------------------------------
*
* NextBitmapWord --
*
* This procedure retrieves the next word of information (stuff
* between commas or white space) from a bitmap description.
*
* Results:
* Returns TCL_OK if all went well. In this case the next word,
* and its length, will be availble in *parseInfoPtr. If the end
* of the bitmap description was reached then TCL_ERROR is returned.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
NextBitmapWord(parseInfoPtr)
ParseInfo *parseInfoPtr; /* Describes what we're reading
* and where we are in it. */
{
char *dst, buf;
int num;
parseInfoPtr->wordLength = 0;
dst = parseInfoPtr->word;
for (num=ImgRead(&parseInfoPtr->handle,&buf,1); isspace(UCHAR(buf)) || (buf == ',');
num=ImgRead(&parseInfoPtr->handle,&buf,1)) {
if (num == 0 || buf == EOF) {
return TCL_ERROR;
}
}
for ( ; !isspace(UCHAR(buf)) && (buf != ',') && (num != 0);
num=ImgRead(&parseInfoPtr->handle,&buf,1)) {
if (UCHAR(buf) < ' ' || UCHAR(buf) > '~') {
return TCL_ERROR;
}
*dst = buf;
dst++;
parseInfoPtr->wordLength++;
if (num == 0 || parseInfoPtr->wordLength > MAX_WORD_LENGTH) {
return TCL_ERROR;
}
}
if (parseInfoPtr->wordLength == 0) {
return TCL_ERROR;
}
parseInfoPtr->word[parseInfoPtr->wordLength] = 0;
return TCL_OK;
}
static int
ReadXBMFileHeader(pi, widthPtr, heightPtr)
ParseInfo *pi;
int *widthPtr, *heightPtr; /* The dimensions of the image are
* returned here. */
{
int width, height, hotX, hotY;
char *end;
/*
* Parse the lines that define the dimensions of the bitmap,
* plus the first line that defines the bitmap data (it declares
* the name of a data variable but doesn't include any actual
* data). These lines look something like the following:
*
* #define foo_width 16
* #define foo_height 16
* #define foo_x_hot 3
* #define foo_y_hot 3
* static char foo_bits[] = {
*
* The x_hot and y_hot lines may or may not be present. It's
* important to check for "char" in the last line, in order to
* reject old X10-style bitmaps that used shorts.
*/
width = 0;
height = 0;
hotX = -1;
hotY = -1;
while (1) {
if (NextBitmapWord(pi) != TCL_OK) {
return 0;
}
if ((pi->wordLength >= 6) && (pi->word[pi->wordLength-6] == '_')
&& (strcmp(pi->word+pi->wordLength-6, "_width") == 0)) {
if (NextBitmapWord(pi) != TCL_OK) {
return 0;
}
width = strtol(pi->word, &end, 0);
if ((end == pi->word) || (*end != 0)) {
return 0;
}
} else if ((pi->wordLength >= 7) && (pi->word[pi->wordLength-7] == '_')
&& (strcmp(pi->word+pi->wordLength-7, "_height") == 0)) {
if (NextBitmapWord(pi) != TCL_OK) {
return 0;
}
height = strtol(pi->word, &end, 0);
if ((end == pi->word) || (*end != 0)) {
return 0;
}
} else if ((pi->wordLength >= 6) && (pi->word[pi->wordLength-6] == '_')
&& (strcmp(pi->word+pi->wordLength-6, "_x_hot") == 0)) {
if (NextBitmapWord(pi) != TCL_OK) {
return 0;
}
hotX = strtol(pi->word, &end, 0);
if ((end == pi->word) || (*end != 0)) {
return 0;
}
} else if ((pi->wordLength >= 6) && (pi->word[pi->wordLength-6] == '_')
&& (strcmp(pi->word+pi->wordLength-6, "_y_hot") == 0)) {
if (NextBitmapWord(pi) != TCL_OK) {
return 0;
}
hotY = strtol(pi->word, &end, 0);
if ((end == pi->word) || (*end != 0)) {
return 0;
}
} else if ((pi->word[0] == 'c') && (strcmp(pi->word, "char") == 0)) {
while (1) {
if (NextBitmapWord(pi) != TCL_OK) {
return 0;
}
if ((pi->word[0] == '{') && (pi->word[1] == 0)) {
goto getData;
}
}
} else if ((pi->word[0] == '{') && (pi->word[1] == 0)) {
return 0;
}
}
getData:
*widthPtr = width;
*heightPtr = height;
return 1;
}
/*
*----------------------------------------------------------------------
*
* ChnWriteXBM
*
* Writes a XBM image to a file. Just calls CommonWriteXBM
* with appropriate arguments.
*
* Results:
* Returns the return value of CommonWriteXBM
*
* Side effects:
* A file is (hopefully) created on success.
*
*----------------------------------------------------------------------
*/
static int
ChnWriteXBM(interp, fileName, format, blockPtr)
Tcl_Interp *interp;
char *fileName;
Tcl_Obj *format;
Tk_PhotoImageBlock *blockPtr;
{
return CommonWriteXBM(interp, fileName, (Tcl_DString *)NULL, format, blockPtr);
}
/*
*----------------------------------------------------------------------
*
* StringWriteXBM
*
* Writes a XBM image to a string. Just calls CommonWriteXBM
* with appropriate arguments.
*
* Results:
* Returns the return value of CommonWriteXBM
*
* Side effects:
* The Tcl_DString dataPtr is modified on success.
*
*----------------------------------------------------------------------
*/
static int
StringWriteXBM(interp, format, blockPtr)
Tcl_Interp *interp;
Tcl_Obj *format;
Tk_PhotoImageBlock *blockPtr;
{
int result;
Tcl_DString data;
Tcl_DString *dataPtr;
ImgFixStringWriteProc(&data, &interp, &dataPtr, &format, &blockPtr);
result = CommonWriteXBM(interp, (CONST char *) NULL, dataPtr, format, blockPtr);
if ((result == TCL_OK) && (dataPtr == &data)) {
Tcl_DStringResult(interp, dataPtr);
}
return result;
}
/*
* Yes, I know these macros are dangerous. But it should work fine
*/
#define WRITE(buf) { if (chan) Tcl_Write(chan, buf, -1); else Tcl_DStringAppend(dataPtr, buf, -1);}
/*
*----------------------------------------------------------------------
*
* CommonWriteXBM
*
* This procedure writes a XBM image to the file filename
* (if filename != NULL) or to dataPtr.
*
* Results:
* Returns TCL_OK on success, or TCL_ERROR on error.
*
* Side effects:
* varies (see StringWriteXBM and ChnWriteXBM)
*
*----------------------------------------------------------------------
*/
static int
CommonWriteXBM(interp, fileName, dataPtr, format, blockPtr)
Tcl_Interp *interp;
CONST char *fileName;
Tcl_DString *dataPtr;
Tcl_Obj *format;
Tk_PhotoImageBlock *blockPtr;
{
Tcl_Channel chan = (Tcl_Channel) NULL;
char buffer[256];
unsigned char *pp;
int x, y, value, mask;
int sep = ' ';
int alphaOffset;
char *p = NULL;
static CONST char header[] =
"#define %s_width %d\n\
#define %s_height %d\n\
static char %s_bits[] = {\n";
alphaOffset = blockPtr->offset[0];
if (alphaOffset < blockPtr->offset[1]) alphaOffset = blockPtr->offset[1];
if (alphaOffset < blockPtr->offset[2]) alphaOffset = blockPtr->offset[2];
if (++alphaOffset < blockPtr->pixelSize) {
alphaOffset -= blockPtr->offset[0];
} else {
alphaOffset = 0;
}
/* open the output file (if needed) */
if (fileName) {
chan = Tcl_OpenFileChannel(interp, (char *) fileName, "w", 0644);
if (!chan) {
return TCL_ERROR;
}
}
/* compute image name */
if (chan) {
p = strrchr(fileName, '/');
if (p) {
fileName = p+1;
}
p = strrchr(fileName, '\\');
if (p) {
fileName = p+1;
}
p = strrchr(fileName, ':');
if (p) {
fileName = p+1;
}
p = strchr(fileName, '.');
if (p) {
*(char *)p = 0;
}
} else {
fileName = "unknown";
}
sprintf(buffer, header, fileName, blockPtr->width, fileName,
blockPtr->height, fileName);
if (p) {
*p = '.';
}
WRITE(buffer);
/* write image itself */
pp = blockPtr->pixelPtr + blockPtr->offset[0];
sep = ' ';
for (y = 0; y < blockPtr->height; y++) {
value = 0;
mask = 1;
for (x = 0; x < blockPtr->width; x++) {
if (!alphaOffset || pp[alphaOffset]) {
value |= mask;
} else {
/* make transparent pixel */
}
pp += blockPtr->pixelSize;
mask <<= 1;
if (mask >= 256)
{
sprintf(buffer,"%c 0x%02x",sep,value);
WRITE(buffer);
value = 0;
mask = 1;
sep = ',';
}
}
if (mask != 1) {
sprintf(buffer,"%c 0x%02x",sep, value);
WRITE(buffer);
}
if (y == blockPtr->height - 1) {
WRITE("};\n");
} else {
WRITE(",\n");
sep = ' ';
}
}
/* close the channel */
if (chan) {
Tcl_Close(interp, chan);
}
return TCL_OK;
}