From 44e60258a4be07705c139d5b27563470ef5a95ee Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 27 Mar 2004 00:12:21 +0000 Subject: Allow PPMs to be read from a string. [FRQ 540375] --- ChangeLog | 5 + generic/tkImgPPM.c | 330 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 327 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index fd8993b..fc5539e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2004-03-26 Donal K. Fellows + + * generic/tkImgPPM.c (ReadPPMStringHeader): Code to read PPM/PGM data + (StringReadPPM, StringMatchPPM): from strings/bytearrays. [FRQ 540375] + 2004-03-26 Don Porter * unix/tcl.m4: Replaced -Wno-strict-alias with more portable diff --git a/generic/tkImgPPM.c b/generic/tkImgPPM.c index 1f3585e..ac989db 100644 --- a/generic/tkImgPPM.c +++ b/generic/tkImgPPM.c @@ -13,7 +13,7 @@ * Department of Computer Science, * Australian National University. * - * RCS: @(#) $Id: tkImgPPM.c,v 1.13 2004/03/26 19:49:40 dgp Exp $ + * RCS: @(#) $Id: tkImgPPM.c,v 1.14 2004/03/27 00:12:22 dkf Exp $ */ #include "tkInt.h" @@ -51,13 +51,21 @@ static int FileWritePPM _ANSI_ARGS_((Tcl_Interp *interp, Tk_PhotoImageBlock *blockPtr)); static int StringWritePPM _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr)); +static int StringMatchPPM _ANSI_ARGS_((Tcl_Obj *dataObj, + Tcl_Obj *format, int *widthPtr, int *heightPtr, + Tcl_Interp *interp)); +static int StringReadPPM _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)); + Tk_PhotoImageFormat tkImgFmtPPM = { "ppm", /* name */ FileMatchPPM, /* fileMatchProc */ - NULL, /* stringMatchProc */ + StringMatchPPM, /* stringMatchProc */ FileReadPPM, /* fileReadProc */ - NULL, /* stringReadProc */ + StringReadPPM, /* stringReadProc */ FileWritePPM, /* fileWriteProc */ StringWritePPM, /* stringWriteProc */ }; @@ -69,6 +77,10 @@ Tk_PhotoImageFormat tkImgFmtPPM = { static int ReadPPMFileHeader _ANSI_ARGS_((Tcl_Channel chan, int *widthPtr, int *heightPtr, int *maxIntensityPtr)); +static int ReadPPMStringHeader _ANSI_ARGS_((Tcl_Obj *dataObj, + int *widthPtr, int *heightPtr, + int *maxIntensityPtr, + unsigned char **dataBufferPtr, int *dataSizePtr)); /* *---------------------------------------------------------------------- @@ -151,7 +163,7 @@ FileReadPPM(interp, chan, fileName, format, imageHandle, destX, destY, } if ((fileWidth <= 0) || (fileHeight <= 0)) { Tcl_AppendResult(interp, "PPM image file \"", fileName, - "\" has dimension(s) <= 0", (char *) NULL); + "\" has dimension(s) <= 0", NULL); return TCL_ERROR; } if ((maxIntensity <= 0) || (maxIntensity >= 256)) { @@ -159,8 +171,7 @@ FileReadPPM(interp, chan, fileName, format, imageHandle, destX, destY, sprintf(buffer, "%d", maxIntensity); Tcl_AppendResult(interp, "PPM image file \"", fileName, - "\" has bad maximum intensity value ", buffer, - (char *) NULL); + "\" has bad maximum intensity value ", buffer, NULL); return TCL_ERROR; } @@ -221,7 +232,7 @@ FileReadPPM(interp, chan, fileName, format, imageHandle, destX, destY, Tcl_AppendResult(interp, "error reading PPM image file \"", fileName, "\": ", Tcl_Eof(chan) ? "not enough data" : Tcl_PosixError(interp), - (char *) NULL); + NULL); ckfree((char *) pixelPtr); return TCL_ERROR; } @@ -327,7 +338,7 @@ FileWritePPM(interp, fileName, format, blockPtr) writeerror: Tcl_AppendResult(interp, "error writing \"", fileName, "\": ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_PosixError(interp), NULL); if (chan != NULL) { Tcl_Close(NULL, chan); } @@ -409,6 +420,190 @@ StringWritePPM(interp, format, blockPtr) /* *---------------------------------------------------------------------- * + * StringMatchPPM -- + * + * This procedure is invoked by the photo image type to see if + * a string contains image data in PPM format. + * + * Results: + * The return value is >0 if the first characters in file "f" look + * like PPM data, and 0 otherwise. + * + * Side effects: + * The access position in f may change. + * + *---------------------------------------------------------------------- + */ + +static int +StringMatchPPM(dataObj, format, widthPtr, heightPtr, interp) + Tcl_Obj *dataObj; /* The image data. */ + 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 PPM file. */ + Tcl_Interp *interp; /* unused */ +{ + int dummy; + + return ReadPPMStringHeader(dataObj, widthPtr, heightPtr, + &dummy, NULL, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * StringReadPPM -- + * + * This procedure is called by the photo image type to read + * PPM 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 the interp's result. + * + * Side effects: + * New data is added to the image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +StringReadPPM(interp, dataObj, format, imageHandle, destX, destY, + width, height, srcX, srcY) + Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ + Tcl_Obj *dataObj; /* The image 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. */ +{ + int fileWidth, fileHeight, maxIntensity; + int nLines, nBytes, h, type, count, dataSize; + unsigned char *pixelPtr, *dataBuffer; + Tk_PhotoImageBlock block; + + type = ReadPPMStringHeader(dataObj, &fileWidth, &fileHeight, + &maxIntensity, &dataBuffer, &dataSize); + if (type == 0) { + Tcl_AppendResult(interp, "couldn't read raw PPM header from string", + NULL); + return TCL_ERROR; + } + if ((fileWidth <= 0) || (fileHeight <= 0)) { + Tcl_AppendResult(interp, "PPM image data has dimension(s) <= 0", + NULL); + return TCL_ERROR; + } + if ((maxIntensity <= 0) || (maxIntensity >= 256)) { + char buffer[TCL_INTEGER_SPACE]; + + sprintf(buffer, "%d", maxIntensity); + Tcl_AppendResult(interp, + "PPM image data has bad maximum intensity value ", buffer, + NULL); + return TCL_ERROR; + } + + 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; + } + + if (type == PGM) { + block.pixelSize = 1; + block.offset[0] = 0; + block.offset[1] = 0; + block.offset[2] = 0; + } else { + block.pixelSize = 3; + block.offset[0] = 0; + block.offset[1] = 1; + block.offset[2] = 2; + } + block.offset[3] = 0; + block.width = width; + block.pitch = block.pixelSize * fileWidth; + + if (srcY > 0) { + dataBuffer += srcY * block.pitch; + dataSize -= srcY * block.pitch; + } + + if (maxIntensity == 255) { + /* + * We have all the data in memory, so write everything in one + * go. + */ + if (block.pitch*height < dataSize) { + Tcl_AppendResult(interp, "truncated PPM data", NULL); + return TCL_ERROR; + } + block.pixelPtr = dataBuffer + srcX * block.pixelSize; + block.height = height; + return Tk_PhotoPutBlock(interp, imageHandle, &block, destX, destY, + width, height, TK_PHOTO_COMPOSITE_SET); + } + + if (Tk_PhotoExpand(interp, imageHandle, + destX + width, destY + height) != TCL_OK) { + return TCL_ERROR; + } + + nLines = (MAX_MEMORY + block.pitch - 1) / block.pitch; + if (nLines > height) { + nLines = height; + } + if (nLines <= 0) { + nLines = 1; + } + nBytes = nLines * block.pitch; + pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes); + block.pixelPtr = pixelPtr + srcX * block.pixelSize; + + for (h = height; h > 0; h -= nLines) { + unsigned char *p; + + if (nLines > h) { + nLines = h; + nBytes = nLines * block.pitch; + } + if (dataSize < nBytes) { + ckfree((char *) pixelPtr); + Tcl_AppendResult(interp, "truncated PPM data", NULL); + return TCL_ERROR; + } + for (p=pixelPtr,count=nBytes ; count>0 ; count--,p++,dataBuffer++) { + *p = (((int) *dataBuffer) * 255)/maxIntensity; + } + dataSize -= nBytes; + block.height = nLines; + if (Tk_PhotoPutBlock(interp, imageHandle, &block, destX, destY, + width, nLines, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { + ckfree((char *) pixelPtr); + return TCL_ERROR; + } + destY += nLines; + } + + ckfree((char *) pixelPtr); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * ReadPPMFileHeader -- * * This procedure reads the PPM header from the beginning of a @@ -510,3 +705,122 @@ ReadPPMFileHeader(chan, widthPtr, heightPtr, maxIntensityPtr) } return type; } + +/* + *---------------------------------------------------------------------- + * + * ReadPPMStringHeader -- + * + * This procedure reads the PPM header from the beginning of a + * PPM-format string and returns information from the header. + * + * Results: + * The return value is PGM if the string appears to start with + * a valid PGM header, PPM if the string appears to start with + * a valid PPM header, and 0 otherwise. If the header is valid, + * then *widthPtr and *heightPtr are modified to hold the + * dimensions of the image and *maxIntensityPtr is modified to + * hold the value of a "fully on" intensity value. + * + * Side effects: + * None + * + *---------------------------------------------------------------------- + */ + +static int +ReadPPMStringHeader(dataPtr, widthPtr, heightPtr, maxIntensityPtr, + dataBufferPtr, dataSizePtr) + Tcl_Obj *dataPtr; /* Object to read the header from. */ + int *widthPtr, *heightPtr; /* The dimensions of the image are + * returned here. */ + int *maxIntensityPtr; /* The maximum intensity value for + * the image is stored here. */ + unsigned char **dataBufferPtr; + int *dataSizePtr; +{ +#define BUFFER_SIZE 1000 + char buffer[BUFFER_SIZE]; + int i, numFields, dataSize; + int type = 0; + char c; + unsigned char *dataBuffer; + + dataBuffer = Tcl_GetByteArrayFromObj(dataPtr, &dataSize); + + /* + * Read 4 space-separated fields from the string, ignoring + * comments (any line that starts with "#"). + */ + + if (dataSize-- < 1) { + return 0; + } + c = (char) (*dataBuffer++); + i = 0; + for (numFields = 0; numFields < 4; numFields++) { + /* + * Skip comments and white space. + */ + + while (1) { + while (isspace(UCHAR(c))) { + if (dataSize-- < 1) { + return 0; + } + c = (char) (*dataBuffer++); + } + if (c != '#') { + break; + } + do { + if (dataSize-- < 1) { + return 0; + } + c = (char) (*dataBuffer++); + } while (c != '\n'); + } + + /* + * Read a field (everything up to the next white space). + */ + + while (!isspace(UCHAR(c))) { + if (i < (BUFFER_SIZE-2)) { + buffer[i] = c; + i++; + } + if (dataSize-- < 1) { + goto done; + } + c = (char) (*dataBuffer++); + } + if (i < (BUFFER_SIZE-1)) { + buffer[i] = ' '; + i++; + } + } + done: + buffer[i] = 0; + + /* + * Parse the fields, which are: id, width, height, maxIntensity. + */ + + if (strncmp(buffer, "P6 ", 3) == 0) { + type = PPM; + } else if (strncmp(buffer, "P5 ", 3) == 0) { + type = PGM; + } else { + return 0; + } + if (sscanf(buffer+3, "%d %d %d", widthPtr, heightPtr, maxIntensityPtr) + != 3) { + return 0; + } + if (dataBufferPtr != NULL) { + *dataBufferPtr = dataBuffer; + *dataSizePtr = dataSize; + } + return type; +} -- cgit v0.12