1/*
2 * tkImgPPM.c --
3 *
4 *	A photo image file handler for PPM (Portable PixMap) files.
5 *
6 * Copyright (c) 1994 The Australian National University.
7 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * Author: Paul Mackerras (paulus@cs.anu.edu.au),
13 *	Department of Computer Science,
14 *	Australian National University.
15 *
16 * RCS: @(#) $Id$
17 */
18
19#include "tkInt.h"
20
21/*
22 * The maximum amount of memory to allocate for data read from the file. If we
23 * need more than this, we do it in pieces.
24 */
25
26#define MAX_MEMORY	10000		/* don't allocate > 10KB */
27
28/*
29 * Define PGM and PPM, i.e. gray images and color images.
30 */
31
32#define PGM 1
33#define PPM 2
34
35/*
36 * The format record for the PPM file format:
37 */
38
39static int		FileMatchPPM(Tcl_Channel chan, CONST char *fileName,
40			    Tcl_Obj *format, int *widthPtr, int *heightPtr,
41			    Tcl_Interp *interp);
42static int		FileReadPPM(Tcl_Interp *interp, Tcl_Channel chan,
43			    CONST char *fileName, Tcl_Obj *format,
44			    Tk_PhotoHandle imageHandle, int destX, int destY,
45			    int width, int height, int srcX, int srcY);
46static int		FileWritePPM(Tcl_Interp *interp, CONST char *fileName,
47			    Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr);
48static int		StringWritePPM(Tcl_Interp *interp, Tcl_Obj *format,
49			    Tk_PhotoImageBlock *blockPtr);
50static int		StringMatchPPM(Tcl_Obj *dataObj, Tcl_Obj *format,
51			    int *widthPtr, int *heightPtr, Tcl_Interp *interp);
52static int		StringReadPPM(Tcl_Interp *interp, Tcl_Obj *dataObj,
53			    Tcl_Obj *format, Tk_PhotoHandle imageHandle,
54			    int destX, int destY, int width, int height,
55			    int srcX, int srcY);
56
57Tk_PhotoImageFormat tkImgFmtPPM = {
58    "ppm",			/* name */
59    FileMatchPPM,		/* fileMatchProc */
60    StringMatchPPM,		/* stringMatchProc */
61    FileReadPPM,		/* fileReadProc */
62    StringReadPPM,		/* stringReadProc */
63    FileWritePPM,		/* fileWriteProc */
64    StringWritePPM,		/* stringWriteProc */
65};
66
67/*
68 * Prototypes for local functions defined in this file:
69 */
70
71static int		ReadPPMFileHeader(Tcl_Channel chan, int *widthPtr,
72			    int *heightPtr, int *maxIntensityPtr);
73static int		ReadPPMStringHeader(Tcl_Obj *dataObj, int *widthPtr,
74			    int *heightPtr, int *maxIntensityPtr,
75			    unsigned char **dataBufferPtr, int *dataSizePtr);
76
77/*
78 *----------------------------------------------------------------------
79 *
80 * FileMatchPPM --
81 *
82 *	This function is invoked by the photo image type to see if a file
83 *	contains image data in PPM format.
84 *
85 * Results:
86 *	The return value is >0 if the first characters in file "f" look like
87 *	PPM data, and 0 otherwise.
88 *
89 * Side effects:
90 *	The access position in f may change.
91 *
92 *----------------------------------------------------------------------
93 */
94
95static int
96FileMatchPPM(
97    Tcl_Channel chan,		/* The image file, open for reading. */
98    CONST char *fileName,	/* The name of the image file. */
99    Tcl_Obj *format,		/* User-specified format string, or NULL. */
100    int *widthPtr, int *heightPtr,
101				/* The dimensions of the image are returned
102				 * here if the file is a valid raw PPM
103				 * file. */
104    Tcl_Interp *interp)		/* unused */
105{
106    int dummy;
107
108    return ReadPPMFileHeader(chan, widthPtr, heightPtr, &dummy);
109}
110
111/*
112 *----------------------------------------------------------------------
113 *
114 * FileReadPPM --
115 *
116 *	This function is called by the photo image type to read PPM format
117 *	data from a file and write it into a given photo image.
118 *
119 * Results:
120 *	A standard TCL completion code. If TCL_ERROR is returned then an error
121 *	message is left in the interp's result.
122 *
123 * Side effects:
124 *	The access position in file f is changed, and new data is added to the
125 *	image given by imageHandle.
126 *
127 *----------------------------------------------------------------------
128 */
129
130static int
131FileReadPPM(
132    Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
133    Tcl_Channel chan,		/* The image file, open for reading. */
134    CONST char *fileName,	/* The name of the image file. */
135    Tcl_Obj *format,		/* User-specified format string, or NULL. */
136    Tk_PhotoHandle imageHandle,	/* The photo image to write into. */
137    int destX, int destY,	/* Coordinates of top-left pixel in photo
138				 * image to be written to. */
139    int width, int height,	/* Dimensions of block of photo image to be
140				 * written to. */
141    int srcX, int srcY)		/* Coordinates of top-left pixel to be used in
142				 * image being read. */
143{
144    int fileWidth, fileHeight, maxIntensity;
145    int nLines, nBytes, h, type, count;
146    unsigned char *pixelPtr;
147    Tk_PhotoImageBlock block;
148
149    type = ReadPPMFileHeader(chan, &fileWidth, &fileHeight, &maxIntensity);
150    if (type == 0) {
151	Tcl_AppendResult(interp, "couldn't read raw PPM header from file \"",
152		fileName, "\"", NULL);
153	return TCL_ERROR;
154    }
155    if ((fileWidth <= 0) || (fileHeight <= 0)) {
156	Tcl_AppendResult(interp, "PPM image file \"", fileName,
157		"\" has dimension(s) <= 0", NULL);
158	return TCL_ERROR;
159    }
160    if ((maxIntensity <= 0) || (maxIntensity >= 256)) {
161	char buffer[TCL_INTEGER_SPACE];
162
163	sprintf(buffer, "%d", maxIntensity);
164	Tcl_AppendResult(interp, "PPM image file \"", fileName,
165		"\" has bad maximum intensity value ", buffer, NULL);
166	return TCL_ERROR;
167    }
168
169    if ((srcX + width) > fileWidth) {
170	width = fileWidth - srcX;
171    }
172    if ((srcY + height) > fileHeight) {
173	height = fileHeight - srcY;
174    }
175    if ((width <= 0) || (height <= 0)
176	|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
177	return TCL_OK;
178    }
179
180    if (type == PGM) {
181	block.pixelSize = 1;
182	block.offset[0] = 0;
183	block.offset[1] = 0;
184	block.offset[2] = 0;
185    } else {
186	block.pixelSize = 3;
187	block.offset[0] = 0;
188	block.offset[1] = 1;
189	block.offset[2] = 2;
190    }
191    block.offset[3] = 0;
192    block.width = width;
193    block.pitch = block.pixelSize * fileWidth;
194
195    if (Tk_PhotoExpand(interp, imageHandle,
196	    destX + width, destY + height) != TCL_OK) {
197	return TCL_ERROR;
198    }
199
200    if (srcY > 0) {
201	Tcl_Seek(chan, (Tcl_WideInt)(srcY * block.pitch), SEEK_CUR);
202    }
203
204    nLines = (MAX_MEMORY + block.pitch - 1) / block.pitch;
205    if (nLines > height) {
206	nLines = height;
207    }
208    if (nLines <= 0) {
209	nLines = 1;
210    }
211    nBytes = nLines * block.pitch;
212    pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);
213    block.pixelPtr = pixelPtr + srcX * block.pixelSize;
214
215    for (h = height; h > 0; h -= nLines) {
216	if (nLines > h) {
217	    nLines = h;
218	    nBytes = nLines * block.pitch;
219	}
220	count = Tcl_Read(chan, (char *) pixelPtr, nBytes);
221	if (count != nBytes) {
222	    Tcl_AppendResult(interp, "error reading PPM image file \"",
223		    fileName, "\": ",
224		    Tcl_Eof(chan) ? "not enough data" : Tcl_PosixError(interp),
225		    NULL);
226	    ckfree((char *) pixelPtr);
227	    return TCL_ERROR;
228	}
229	if (maxIntensity != 255) {
230	    unsigned char *p;
231
232	    for (p = pixelPtr; count > 0; count--, p++) {
233		*p = (((int) *p) * 255)/maxIntensity;
234	    }
235	}
236	block.height = nLines;
237	if (Tk_PhotoPutBlock(interp, imageHandle, &block, destX, destY,
238		width, nLines, TK_PHOTO_COMPOSITE_SET) != TCL_OK) {
239	    ckfree((char *) pixelPtr);
240	    return TCL_ERROR;
241	}
242	destY += nLines;
243    }
244
245    ckfree((char *) pixelPtr);
246    return TCL_OK;
247}
248
249/*
250 *----------------------------------------------------------------------
251 *
252 * FileWritePPM --
253 *
254 *	This function is invoked to write image data to a file in PPM format
255 *	(although we can read PGM files, we never write them).
256 *
257 * Results:
258 *	A standard TCL completion code. If TCL_ERROR is returned then an error
259 *	message is left in the interp's result.
260 *
261 * Side effects:
262 *	Data is written to the file given by "fileName".
263 *
264 *----------------------------------------------------------------------
265 */
266
267static int
268FileWritePPM(
269    Tcl_Interp *interp,
270    CONST char *fileName,
271    Tcl_Obj *format,
272    Tk_PhotoImageBlock *blockPtr)
273{
274    Tcl_Channel chan;
275    int w, h, greenOffset, blueOffset, nBytes;
276    unsigned char *pixelPtr, *pixLinePtr;
277    char header[16 + TCL_INTEGER_SPACE * 2];
278
279    chan = Tcl_OpenFileChannel(interp, fileName, "w", 0666);
280    if (chan == NULL) {
281	return TCL_ERROR;
282    }
283
284    if (Tcl_SetChannelOption(interp, chan, "-translation", "binary")
285	    != TCL_OK) {
286	Tcl_Close(NULL, chan);
287	return TCL_ERROR;
288    }
289    if (Tcl_SetChannelOption(interp, chan, "-encoding", "binary")
290	    != TCL_OK) {
291	Tcl_Close(NULL, chan);
292	return TCL_ERROR;
293    }
294
295    sprintf(header, "P6\n%d %d\n255\n", blockPtr->width, blockPtr->height);
296    Tcl_Write(chan, header, -1);
297
298    pixLinePtr = blockPtr->pixelPtr + blockPtr->offset[0];
299    greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
300    blueOffset = blockPtr->offset[2] - blockPtr->offset[0];
301
302    if ((greenOffset == 1) && (blueOffset == 2) && (blockPtr->pixelSize == 3)
303	    && (blockPtr->pitch == (blockPtr->width * 3))) {
304	nBytes = blockPtr->height * blockPtr->pitch;
305	if (Tcl_Write(chan, (char *) pixLinePtr, nBytes) != nBytes) {
306	    goto writeerror;
307	}
308    } else {
309	for (h = blockPtr->height; h > 0; h--) {
310	    pixelPtr = pixLinePtr;
311	    for (w = blockPtr->width; w > 0; w--) {
312		if (    Tcl_Write(chan,(char *)&pixelPtr[0], 1) == -1 ||
313			Tcl_Write(chan,(char *)&pixelPtr[greenOffset],1)==-1 ||
314			Tcl_Write(chan,(char *)&pixelPtr[blueOffset],1) ==-1) {
315		    goto writeerror;
316		}
317		pixelPtr += blockPtr->pixelSize;
318	    }
319	    pixLinePtr += blockPtr->pitch;
320	}
321    }
322
323    if (Tcl_Close(NULL, chan) == 0) {
324	return TCL_OK;
325    }
326    chan = NULL;
327
328  writeerror:
329    Tcl_AppendResult(interp, "error writing \"", fileName, "\": ",
330	    Tcl_PosixError(interp), NULL);
331    if (chan != NULL) {
332	Tcl_Close(NULL, chan);
333    }
334    return TCL_ERROR;
335}
336
337/*
338 *----------------------------------------------------------------------
339 *
340 * StringWritePPM --
341 *
342 *	This function is invoked to write image data to a string in PPM
343 *	format.
344 *
345 * Results:
346 *	A standard TCL completion code. If TCL_ERROR is returned then an error
347 *	message is left in the interp's result.
348 *
349 * Side effects:
350 *	None.
351 *
352 *----------------------------------------------------------------------
353 */
354
355static int
356StringWritePPM(
357    Tcl_Interp *interp,
358    Tcl_Obj *format,
359    Tk_PhotoImageBlock *blockPtr)
360{
361    int w, h, size, greenOffset, blueOffset;
362    unsigned char *pixLinePtr, *byteArray;
363    char header[16 + TCL_INTEGER_SPACE * 2];
364    Tcl_Obj *byteArrayObj;
365
366    sprintf(header, "P6\n%d %d\n255\n", blockPtr->width, blockPtr->height);
367
368    /*
369     * Construct a byte array of the right size with the header and
370     * get a pointer to the data part of it.
371     */
372
373    size = strlen(header);
374    byteArrayObj = Tcl_NewByteArrayObj((unsigned char *)header, size);
375    byteArray = Tcl_SetByteArrayLength(byteArrayObj,
376	    size + 3*blockPtr->width*blockPtr->height);
377    byteArray += size;
378
379    pixLinePtr = blockPtr->pixelPtr + blockPtr->offset[0];
380    greenOffset = blockPtr->offset[1] - blockPtr->offset[0];
381    blueOffset = blockPtr->offset[2] - blockPtr->offset[0];
382
383    /*
384     * Check if we can do the data move in single action.
385     */
386
387    if ((greenOffset == 1) && (blueOffset == 2) && (blockPtr->pixelSize == 3)
388	    && (blockPtr->pitch == (blockPtr->width * 3))) {
389	memcpy(byteArray, pixLinePtr,
390		(unsigned)blockPtr->height * blockPtr->pitch);
391    } else {
392	for (h = blockPtr->height; h > 0; h--) {
393	    unsigned char *pixelPtr = pixLinePtr;
394
395	    for (w = blockPtr->width; w > 0; w--) {
396		*byteArray++ = pixelPtr[0];
397		*byteArray++ = pixelPtr[greenOffset];
398		*byteArray++ = pixelPtr[blueOffset];
399		pixelPtr += blockPtr->pixelSize;
400	    }
401	    pixLinePtr += blockPtr->pitch;
402	}
403    }
404
405    /*
406     * Return the object in the interpreter result.
407     */
408
409    Tcl_SetObjResult(interp, byteArrayObj);
410    return TCL_OK;
411}
412
413/*
414 *----------------------------------------------------------------------
415 *
416 * StringMatchPPM --
417 *
418 *	This function is invoked by the photo image type to see if a string
419 *	contains image data in PPM format.
420 *
421 * Results:
422 *	The return value is >0 if the first characters in file "f" look like
423 *	PPM data, and 0 otherwise.
424 *
425 * Side effects:
426 *	The access position in f may change.
427 *
428 *----------------------------------------------------------------------
429 */
430
431static int
432StringMatchPPM(
433    Tcl_Obj *dataObj,		/* The image data. */
434    Tcl_Obj *format,		/* User-specified format string, or NULL. */
435    int *widthPtr, int *heightPtr,
436				/* The dimensions of the image are returned
437				 * here if the file is a valid raw PPM
438				 * file. */
439    Tcl_Interp *interp)		/* unused */
440{
441    int dummy;
442
443    return ReadPPMStringHeader(dataObj, widthPtr, heightPtr,
444	    &dummy, NULL, NULL);
445}
446
447/*
448 *----------------------------------------------------------------------
449 *
450 * StringReadPPM --
451 *
452 *	This function is called by the photo image type to read PPM format
453 *	data from a string and write it into a given photo image.
454 *
455 * Results:
456 *	A standard TCL completion code. If TCL_ERROR is returned then an error
457 *	message is left in the interp's result.
458 *
459 * Side effects:
460 *	New data is added to the image given by imageHandle.
461 *
462 *----------------------------------------------------------------------
463 */
464
465static int
466StringReadPPM(
467    Tcl_Interp *interp,		/* Interpreter to use for reporting errors. */
468    Tcl_Obj *dataObj,		/* The image data. */
469    Tcl_Obj *format,		/* User-specified format string, or NULL. */
470    Tk_PhotoHandle imageHandle,	/* The photo image to write into. */
471    int destX, int destY,	/* Coordinates of top-left pixel in photo
472				 * image to be written to. */
473    int width, int height,	/* Dimensions of block of photo image to be
474				 * written to. */
475    int srcX, int srcY)		/* Coordinates of top-left pixel to be used in
476				 * image being read. */
477{
478    int fileWidth, fileHeight, maxIntensity;
479    int nLines, nBytes, h, type, count, dataSize;
480    unsigned char *pixelPtr, *dataBuffer;
481    Tk_PhotoImageBlock block;
482
483    type = ReadPPMStringHeader(dataObj, &fileWidth, &fileHeight,
484	    &maxIntensity, &dataBuffer, &dataSize);
485    if (type == 0) {
486	Tcl_AppendResult(interp, "couldn't read raw PPM header from string",
487		NULL);
488	return TCL_ERROR;
489    }
490    if ((fileWidth <= 0) || (fileHeight <= 0)) {
491	Tcl_AppendResult(interp, "PPM image data has dimension(s) <= 0",
492		NULL);
493	return TCL_ERROR;
494    }
495    if ((maxIntensity <= 0) || (maxIntensity >= 256)) {
496	char buffer[TCL_INTEGER_SPACE];
497
498	sprintf(buffer, "%d", maxIntensity);
499	Tcl_AppendResult(interp,
500		"PPM image data has bad maximum intensity value ", buffer,
501		NULL);
502	return TCL_ERROR;
503    }
504
505    if ((srcX + width) > fileWidth) {
506	width = fileWidth - srcX;
507    }
508    if ((srcY + height) > fileHeight) {
509	height = fileHeight - srcY;
510    }
511    if ((width <= 0) || (height <= 0)
512	|| (srcX >= fileWidth) || (srcY >= fileHeight)) {
513	return TCL_OK;
514    }
515
516    if (type == PGM) {
517	block.pixelSize = 1;
518	block.offset[0] = 0;
519	block.offset[1] = 0;
520	block.offset[2] = 0;
521    } else {
522	block.pixelSize = 3;
523	block.offset[0] = 0;
524	block.offset[1] = 1;
525	block.offset[2] = 2;
526    }
527    block.offset[3] = 0;
528    block.width = width;
529    block.pitch = block.pixelSize * fileWidth;
530
531    if (srcY > 0) {
532	dataBuffer += srcY * block.pitch;
533	dataSize -= srcY * block.pitch;
534    }
535
536    if (maxIntensity == 255) {
537	/*
538	 * We have all the data in memory, so write everything in one go.
539	 */
540
541	if (block.pitch*height > dataSize) {
542	    Tcl_AppendResult(interp, "truncated PPM data", NULL);
543	    return TCL_ERROR;
544	}
545	block.pixelPtr = dataBuffer + srcX * block.pixelSize;
546	block.height = height;
547	return Tk_PhotoPutBlock(interp, imageHandle, &block, destX, destY,
548		width, height, TK_PHOTO_COMPOSITE_SET);
549    }
550
551    if (Tk_PhotoExpand(interp, imageHandle,
552	    destX + width, destY + height) != TCL_OK) {
553	return TCL_ERROR;
554    }
555
556    nLines = (MAX_MEMORY + block.pitch - 1) / block.pitch;
557    if (nLines > height) {
558	nLines = height;
559    }
560    if (nLines <= 0) {
561	nLines = 1;
562    }
563    nBytes = nLines * block.pitch;
564    pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes);
565    block.pixelPtr = pixelPtr + srcX * block.pixelSize;
566
567    for (h = height; h > 0; h -= nLines) {
568	unsigned char *p;
569
570	if (nLines > h) {
571	    nLines = h;
572	    nBytes = nLines * block.pitch;
573	}
574	if (dataSize < nBytes) {
575	    ckfree((char *) pixelPtr);
576	    Tcl_AppendResult(interp, "truncated PPM data", NULL);
577	    return TCL_ERROR;
578	}
579	for (p=pixelPtr,count=nBytes ; count>0 ; count--,p++,dataBuffer++) {
580	    *p = (((int) *dataBuffer) * 255)/maxIntensity;
581	}
582	dataSize -= nBytes;
583	block.height = nLines;
584	if (Tk_PhotoPutBlock(interp, imageHandle, &block, destX, destY,
585		width, nLines, TK_PHOTO_COMPOSITE_SET) != TCL_OK) {
586	    ckfree((char *) pixelPtr);
587	    return TCL_ERROR;
588	}
589	destY += nLines;
590    }
591
592    ckfree((char *) pixelPtr);
593    return TCL_OK;
594}
595
596/*
597 *----------------------------------------------------------------------
598 *
599 * ReadPPMFileHeader --
600 *
601 *	This function reads the PPM header from the beginning of a PPM file
602 *	and returns information from the header.
603 *
604 * Results:
605 *	The return value is PGM if file "f" appears to start with a valid PGM
606 *	header, PPM if "f" appears to start with a valid PPM header, and 0
607 *	otherwise. If the header is valid, then *widthPtr and *heightPtr are
608 *	modified to hold the dimensions of the image and *maxIntensityPtr is
609 *	modified to hold the value of a "fully on" intensity value.
610 *
611 * Side effects:
612 *	The access position in f advances.
613 *
614 *----------------------------------------------------------------------
615 */
616
617static int
618ReadPPMFileHeader(
619    Tcl_Channel chan,		/* Image file to read the header from. */
620    int *widthPtr, int *heightPtr,
621				/* The dimensions of the image are returned
622				 * here. */
623    int *maxIntensityPtr)	/* The maximum intensity value for the image
624				 * is stored here. */
625{
626#define BUFFER_SIZE 1000
627    char buffer[BUFFER_SIZE], c;
628    int i, numFields, type = 0;
629
630    /*
631     * Read 4 space-separated fields from the file, ignoring comments (any
632     * line that starts with "#").
633     */
634
635    if (Tcl_Read(chan, &c, 1) != 1) {
636	return 0;
637    }
638    i = 0;
639    for (numFields = 0; numFields < 4; numFields++) {
640	/*
641	 * Skip comments and white space.
642	 */
643
644	while (1) {
645	    while (isspace(UCHAR(c))) {
646		if (Tcl_Read(chan, &c, 1) != 1) {
647		    return 0;
648		}
649	    }
650	    if (c != '#') {
651		break;
652	    }
653	    do {
654		if (Tcl_Read(chan, &c, 1) != 1) {
655		    return 0;
656		}
657	    } while (c != '\n');
658	}
659
660	/*
661	 * Read a field (everything up to the next white space).
662	 */
663
664	while (!isspace(UCHAR(c))) {
665	    if (i < (BUFFER_SIZE-2)) {
666		buffer[i] = c;
667		i++;
668	    }
669	    if (Tcl_Read(chan, &c, 1) != 1) {
670		goto done;
671	    }
672	}
673	if (i < (BUFFER_SIZE-1)) {
674	    buffer[i] = ' ';
675	    i++;
676	}
677    }
678
679  done:
680    buffer[i] = 0;
681
682    /*
683     * Parse the fields, which are: id, width, height, maxIntensity.
684     */
685
686    if (strncmp(buffer, "P6 ", 3) == 0) {
687	type = PPM;
688    } else if (strncmp(buffer, "P5 ", 3) == 0) {
689	type = PGM;
690    } else {
691	return 0;
692    }
693    if (sscanf(buffer+3, "%d %d %d", widthPtr, heightPtr, maxIntensityPtr)
694	    != 3) {
695	return 0;
696    }
697    return type;
698}
699
700/*
701 *----------------------------------------------------------------------
702 *
703 * ReadPPMStringHeader --
704 *
705 *	This function reads the PPM header from the beginning of a PPM-format
706 *	string and returns information from the header.
707 *
708 * Results:
709 *	The return value is PGM if the string appears to start with a valid
710 *	PGM header, PPM if the string appears to start with a valid PPM
711 *	header, and 0 otherwise. If the header is valid, then *widthPtr and
712 *	*heightPtr are modified to hold the dimensions of the image and
713 *	*maxIntensityPtr is modified to hold the value of a "fully on"
714 *	intensity value.
715 *
716 * Side effects:
717 *	None
718 *
719 *----------------------------------------------------------------------
720 */
721
722static int
723ReadPPMStringHeader(
724    Tcl_Obj *dataPtr,		/* Object to read the header from. */
725    int *widthPtr, int *heightPtr,
726				/* The dimensions of the image are returned
727				 * here. */
728    int *maxIntensityPtr,	/* The maximum intensity value for the image
729				 * is stored here. */
730    unsigned char **dataBufferPtr,
731    int *dataSizePtr)
732{
733#define BUFFER_SIZE 1000
734    char buffer[BUFFER_SIZE], c;
735    int i, numFields, dataSize, type = 0;
736    unsigned char *dataBuffer;
737
738    dataBuffer = Tcl_GetByteArrayFromObj(dataPtr, &dataSize);
739
740    /*
741     * Read 4 space-separated fields from the string, ignoring comments (any
742     * line that starts with "#").
743     */
744
745    if (dataSize-- < 1) {
746	return 0;
747    }
748    c = (char) (*dataBuffer++);
749    i = 0;
750    for (numFields = 0; numFields < 4; numFields++) {
751	/*
752	 * Skip comments and white space.
753	 */
754
755	while (1) {
756	    while (isspace(UCHAR(c))) {
757		if (dataSize-- < 1) {
758		    return 0;
759		}
760		c = (char) (*dataBuffer++);
761	    }
762	    if (c != '#') {
763		break;
764	    }
765	    do {
766		if (dataSize-- < 1) {
767		    return 0;
768		}
769		c = (char) (*dataBuffer++);
770	    } while (c != '\n');
771	}
772
773	/*
774	 * Read a field (everything up to the next white space).
775	 */
776
777	while (!isspace(UCHAR(c))) {
778	    if (i < (BUFFER_SIZE-2)) {
779		buffer[i] = c;
780		i++;
781	    }
782	    if (dataSize-- < 1) {
783		goto done;
784	    }
785	    c = (char) (*dataBuffer++);
786	}
787	if (i < (BUFFER_SIZE-1)) {
788	    buffer[i] = ' ';
789	    i++;
790	}
791    }
792
793  done:
794    buffer[i] = 0;
795
796    /*
797     * Parse the fields, which are: id, width, height, maxIntensity.
798     */
799
800    if (strncmp(buffer, "P6 ", 3) == 0) {
801	type = PPM;
802    } else if (strncmp(buffer, "P5 ", 3) == 0) {
803	type = PGM;
804    } else {
805	return 0;
806    }
807    if (sscanf(buffer+3, "%d %d %d", widthPtr, heightPtr, maxIntensityPtr)
808	    != 3) {
809	return 0;
810    }
811    if (dataBufferPtr != NULL) {
812	*dataBufferPtr = dataBuffer;
813	*dataSizePtr = dataSize;
814    }
815    return type;
816}
817
818/*
819 * Local Variables:
820 * mode: c
821 * c-basic-offset: 4
822 * fill-column: 78
823 * End:
824 */
825