1 |
/* $Header$ */ |
2 |
|
3 |
/* |
4 |
* tkImgPPM.c -- |
5 |
* |
6 |
* A photo image file handler for PPM (Portable PixMap) files. |
7 |
* |
8 |
* Copyright (c) 1994 The Australian National University. |
9 |
* Copyright (c) 1994-1997 Sun Microsystems, Inc. |
10 |
* |
11 |
* See the file "license.terms" for information on usage and redistribution |
12 |
* of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
13 |
* |
14 |
* Author: Paul Mackerras (paulus@cs.anu.edu.au), |
15 |
* Department of Computer Science, |
16 |
* Australian National University. |
17 |
* |
18 |
* RCS: @(#) $Id: tkimgppm.c,v 1.1.1.1 2001/06/13 05:03:30 dtashley Exp $ |
19 |
*/ |
20 |
|
21 |
#define USE_OLD_IMAGE |
22 |
|
23 |
#include "tkInt.h" |
24 |
#include "tkPort.h" |
25 |
|
26 |
/* |
27 |
* The maximum amount of memory to allocate for data read from the |
28 |
* file. If we need more than this, we do it in pieces. |
29 |
*/ |
30 |
|
31 |
#define MAX_MEMORY 10000 /* don't allocate > 10KB */ |
32 |
|
33 |
/* |
34 |
* Define PGM and PPM, i.e. gray images and color images. |
35 |
*/ |
36 |
|
37 |
#define PGM 1 |
38 |
#define PPM 2 |
39 |
|
40 |
/* |
41 |
* The format record for the PPM file format: |
42 |
*/ |
43 |
|
44 |
static int FileMatchPPM _ANSI_ARGS_((Tcl_Channel chan, |
45 |
char *fileName, char *formatString, |
46 |
int *widthPtr, int *heightPtr)); |
47 |
static int FileReadPPM _ANSI_ARGS_((Tcl_Interp *interp, |
48 |
Tcl_Channel chan, char *fileName, |
49 |
char *formatString, Tk_PhotoHandle imageHandle, |
50 |
int destX, int destY, int width, int height, |
51 |
int srcX, int srcY)); |
52 |
static int FileWritePPM _ANSI_ARGS_((Tcl_Interp *interp, |
53 |
char *fileName, char *formatString, |
54 |
Tk_PhotoImageBlock *blockPtr)); |
55 |
|
56 |
Tk_PhotoImageFormat tkImgFmtPPM = { |
57 |
"PPM", /* name */ |
58 |
FileMatchPPM, /* fileMatchProc */ |
59 |
NULL, /* stringMatchProc */ |
60 |
FileReadPPM, /* fileReadProc */ |
61 |
NULL, /* stringReadProc */ |
62 |
FileWritePPM, /* fileWriteProc */ |
63 |
NULL, /* stringWriteProc */ |
64 |
}; |
65 |
|
66 |
/* |
67 |
* Prototypes for local procedures defined in this file: |
68 |
*/ |
69 |
|
70 |
static int ReadPPMFileHeader _ANSI_ARGS_((Tcl_Channel chan, |
71 |
int *widthPtr, int *heightPtr, |
72 |
int *maxIntensityPtr)); |
73 |
|
74 |
/* |
75 |
*---------------------------------------------------------------------- |
76 |
* |
77 |
* FileMatchPPM -- |
78 |
* |
79 |
* This procedure is invoked by the photo image type to see if |
80 |
* a file contains image data in PPM format. |
81 |
* |
82 |
* Results: |
83 |
* The return value is >0 if the first characters in file "f" look |
84 |
* like PPM data, and 0 otherwise. |
85 |
* |
86 |
* Side effects: |
87 |
* The access position in f may change. |
88 |
* |
89 |
*---------------------------------------------------------------------- |
90 |
*/ |
91 |
|
92 |
static int |
93 |
FileMatchPPM(chan, fileName, formatString, widthPtr, heightPtr) |
94 |
Tcl_Channel chan; /* The image file, open for reading. */ |
95 |
char *fileName; /* The name of the image file. */ |
96 |
char *formatString; /* User-specified format string, or NULL. */ |
97 |
int *widthPtr, *heightPtr; /* The dimensions of the image are |
98 |
* returned here if the file is a valid |
99 |
* raw PPM file. */ |
100 |
{ |
101 |
int dummy; |
102 |
|
103 |
return ReadPPMFileHeader(chan, widthPtr, heightPtr, &dummy); |
104 |
} |
105 |
|
106 |
/* |
107 |
*---------------------------------------------------------------------- |
108 |
* |
109 |
* FileReadPPM -- |
110 |
* |
111 |
* This procedure is called by the photo image type to read |
112 |
* PPM format data from a file and write it into a given |
113 |
* photo image. |
114 |
* |
115 |
* Results: |
116 |
* A standard TCL completion code. If TCL_ERROR is returned |
117 |
* then an error message is left in the interp's result. |
118 |
* |
119 |
* Side effects: |
120 |
* The access position in file f is changed, and new data is |
121 |
* added to the image given by imageHandle. |
122 |
* |
123 |
*---------------------------------------------------------------------- |
124 |
*/ |
125 |
|
126 |
static int |
127 |
FileReadPPM(interp, chan, fileName, formatString, imageHandle, destX, destY, |
128 |
width, height, srcX, srcY) |
129 |
Tcl_Interp *interp; /* Interpreter to use for reporting errors. */ |
130 |
Tcl_Channel chan; /* The image file, open for reading. */ |
131 |
char *fileName; /* The name of the image file. */ |
132 |
char *formatString; /* User-specified format string, or NULL. */ |
133 |
Tk_PhotoHandle imageHandle; /* The photo image to write into. */ |
134 |
int destX, destY; /* Coordinates of top-left pixel in |
135 |
* photo image to be written to. */ |
136 |
int width, height; /* Dimensions of block of photo image to |
137 |
* be written to. */ |
138 |
int srcX, srcY; /* Coordinates of top-left pixel to be used |
139 |
* in image being read. */ |
140 |
{ |
141 |
int fileWidth, fileHeight, maxIntensity; |
142 |
int nLines, nBytes, h, type, count; |
143 |
unsigned char *pixelPtr; |
144 |
Tk_PhotoImageBlock block; |
145 |
|
146 |
type = ReadPPMFileHeader(chan, &fileWidth, &fileHeight, &maxIntensity); |
147 |
if (type == 0) { |
148 |
Tcl_AppendResult(interp, "couldn't read raw PPM header from file \"", |
149 |
fileName, "\"", NULL); |
150 |
return TCL_ERROR; |
151 |
} |
152 |
if ((fileWidth <= 0) || (fileHeight <= 0)) { |
153 |
Tcl_AppendResult(interp, "PPM image file \"", fileName, |
154 |
"\" has dimension(s) <= 0", (char *) NULL); |
155 |
return TCL_ERROR; |
156 |
} |
157 |
if ((maxIntensity <= 0) || (maxIntensity >= 256)) { |
158 |
char buffer[TCL_INTEGER_SPACE]; |
159 |
|
160 |
sprintf(buffer, "%d", maxIntensity); |
161 |
Tcl_AppendResult(interp, "PPM image file \"", fileName, |
162 |
"\" has bad maximum intensity value ", buffer, |
163 |
(char *) NULL); |
164 |
return TCL_ERROR; |
165 |
} |
166 |
|
167 |
if ((srcX + width) > fileWidth) { |
168 |
width = fileWidth - srcX; |
169 |
} |
170 |
if ((srcY + height) > fileHeight) { |
171 |
height = fileHeight - srcY; |
172 |
} |
173 |
if ((width <= 0) || (height <= 0) |
174 |
|| (srcX >= fileWidth) || (srcY >= fileHeight)) { |
175 |
return TCL_OK; |
176 |
} |
177 |
|
178 |
if (type == PGM) { |
179 |
block.pixelSize = 1; |
180 |
block.offset[0] = 0; |
181 |
block.offset[1] = 0; |
182 |
block.offset[2] = 0; |
183 |
} |
184 |
else { |
185 |
block.pixelSize = 3; |
186 |
block.offset[0] = 0; |
187 |
block.offset[1] = 1; |
188 |
block.offset[2] = 2; |
189 |
} |
190 |
block.offset[3] = 0; |
191 |
block.width = width; |
192 |
block.pitch = block.pixelSize * fileWidth; |
193 |
|
194 |
Tk_PhotoExpand(imageHandle, destX + width, destY + height); |
195 |
|
196 |
if (srcY > 0) { |
197 |
Tcl_Seek(chan, (srcY * block.pitch), SEEK_CUR); |
198 |
} |
199 |
|
200 |
nLines = (MAX_MEMORY + block.pitch - 1) / block.pitch; |
201 |
if (nLines > height) { |
202 |
nLines = height; |
203 |
} |
204 |
if (nLines <= 0) { |
205 |
nLines = 1; |
206 |
} |
207 |
nBytes = nLines * block.pitch; |
208 |
pixelPtr = (unsigned char *) ckalloc((unsigned) nBytes); |
209 |
block.pixelPtr = pixelPtr + srcX * block.pixelSize; |
210 |
|
211 |
for (h = height; h > 0; h -= nLines) { |
212 |
if (nLines > h) { |
213 |
nLines = h; |
214 |
nBytes = nLines * block.pitch; |
215 |
} |
216 |
count = Tcl_Read(chan, (char *) pixelPtr, nBytes); |
217 |
if (count != nBytes) { |
218 |
Tcl_AppendResult(interp, "error reading PPM image file \"", |
219 |
fileName, "\": ", |
220 |
Tcl_Eof(chan) ? "not enough data" : Tcl_PosixError(interp), |
221 |
(char *) NULL); |
222 |
ckfree((char *) pixelPtr); |
223 |
return TCL_ERROR; |
224 |
} |
225 |
if (maxIntensity != 255) { |
226 |
unsigned char *p; |
227 |
|
228 |
for (p = pixelPtr; count > 0; count--, p++) { |
229 |
*p = (((int) *p) * 255)/maxIntensity; |
230 |
} |
231 |
} |
232 |
block.height = nLines; |
233 |
Tk_PhotoPutBlock(imageHandle, &block, destX, destY, width, nLines); |
234 |
destY += nLines; |
235 |
} |
236 |
|
237 |
ckfree((char *) pixelPtr); |
238 |
return TCL_OK; |
239 |
} |
240 |
|
241 |
/* |
242 |
*---------------------------------------------------------------------- |
243 |
* |
244 |
* FileWritePPM -- |
245 |
* |
246 |
* This procedure is invoked to write image data to a file in PPM |
247 |
* format. |
248 |
* |
249 |
* Results: |
250 |
* A standard TCL completion code. If TCL_ERROR is returned |
251 |
* then an error message is left in the interp's result. |
252 |
* |
253 |
* Side effects: |
254 |
* Data is written to the file given by "fileName". |
255 |
* |
256 |
*---------------------------------------------------------------------- |
257 |
*/ |
258 |
|
259 |
static int |
260 |
FileWritePPM(interp, fileName, formatString, blockPtr) |
261 |
Tcl_Interp *interp; |
262 |
char *fileName; |
263 |
char *formatString; |
264 |
Tk_PhotoImageBlock *blockPtr; |
265 |
{ |
266 |
Tcl_Channel chan; |
267 |
int w, h; |
268 |
int greenOffset, blueOffset, nBytes; |
269 |
unsigned char *pixelPtr, *pixLinePtr; |
270 |
char header[16 + TCL_INTEGER_SPACE * 2]; |
271 |
|
272 |
chan = Tcl_OpenFileChannel(interp, fileName, "w", 0666); |
273 |
if (chan == NULL) { |
274 |
return TCL_ERROR; |
275 |
} |
276 |
|
277 |
if (Tcl_SetChannelOption(interp, chan, "-translation", "binary") |
278 |
!= TCL_OK) { |
279 |
return TCL_ERROR; |
280 |
} |
281 |
if (Tcl_SetChannelOption(interp, chan, "-encoding", "binary") |
282 |
!= TCL_OK) { |
283 |
return TCL_ERROR; |
284 |
} |
285 |
|
286 |
sprintf(header, "P6\n%d %d\n255\n", blockPtr->width, blockPtr->height); |
287 |
Tcl_Write(chan, header, -1); |
288 |
|
289 |
pixLinePtr = blockPtr->pixelPtr + blockPtr->offset[0]; |
290 |
greenOffset = blockPtr->offset[1] - blockPtr->offset[0]; |
291 |
blueOffset = blockPtr->offset[2] - blockPtr->offset[0]; |
292 |
|
293 |
if ((greenOffset == 1) && (blueOffset == 2) && (blockPtr->pixelSize == 3) |
294 |
&& (blockPtr->pitch == (blockPtr->width * 3))) { |
295 |
nBytes = blockPtr->height * blockPtr->pitch; |
296 |
if (Tcl_Write(chan, (char *) pixLinePtr, nBytes) != nBytes) { |
297 |
goto writeerror; |
298 |
} |
299 |
} else { |
300 |
for (h = blockPtr->height; h > 0; h--) { |
301 |
pixelPtr = pixLinePtr; |
302 |
for (w = blockPtr->width; w > 0; w--) { |
303 |
if ((Tcl_Write(chan, (char *) &pixelPtr[0], 1) == -1) |
304 |
|| (Tcl_Write(chan, (char *) &pixelPtr[greenOffset], 1) == -1) |
305 |
|| (Tcl_Write(chan, (char *) &pixelPtr[blueOffset], 1) == -1)) { |
306 |
goto writeerror; |
307 |
} |
308 |
pixelPtr += blockPtr->pixelSize; |
309 |
} |
310 |
pixLinePtr += blockPtr->pitch; |
311 |
} |
312 |
} |
313 |
|
314 |
if (Tcl_Close(NULL, chan) == 0) { |
315 |
return TCL_OK; |
316 |
} |
317 |
chan = NULL; |
318 |
|
319 |
writeerror: |
320 |
Tcl_AppendResult(interp, "error writing \"", fileName, "\": ", |
321 |
Tcl_PosixError(interp), (char *) NULL); |
322 |
if (chan != NULL) { |
323 |
Tcl_Close(NULL, chan); |
324 |
} |
325 |
return TCL_ERROR; |
326 |
} |
327 |
|
328 |
/* |
329 |
*---------------------------------------------------------------------- |
330 |
* |
331 |
* ReadPPMFileHeader -- |
332 |
* |
333 |
* This procedure reads the PPM header from the beginning of a |
334 |
* PPM file and returns information from the header. |
335 |
* |
336 |
* Results: |
337 |
* The return value is PGM if file "f" appears to start with |
338 |
* a valid PGM header, PPM if "f" appears to start with a valid |
339 |
* PPM header, and 0 otherwise. If the header is valid, |
340 |
* then *widthPtr and *heightPtr are modified to hold the |
341 |
* dimensions of the image and *maxIntensityPtr is modified to |
342 |
* hold the value of a "fully on" intensity value. |
343 |
* |
344 |
* Side effects: |
345 |
* The access position in f advances. |
346 |
* |
347 |
*---------------------------------------------------------------------- |
348 |
*/ |
349 |
|
350 |
static int |
351 |
ReadPPMFileHeader(chan, widthPtr, heightPtr, maxIntensityPtr) |
352 |
Tcl_Channel chan; /* Image file to read the header from */ |
353 |
int *widthPtr, *heightPtr; /* The dimensions of the image are |
354 |
* returned here. */ |
355 |
int *maxIntensityPtr; /* The maximum intensity value for |
356 |
* the image is stored here. */ |
357 |
{ |
358 |
#define BUFFER_SIZE 1000 |
359 |
char buffer[BUFFER_SIZE]; |
360 |
int i, numFields; |
361 |
int type = 0; |
362 |
char c; |
363 |
|
364 |
/* |
365 |
* Read 4 space-separated fields from the file, ignoring |
366 |
* comments (any line that starts with "#"). |
367 |
*/ |
368 |
|
369 |
if (Tcl_Read(chan, &c, 1) != 1) { |
370 |
return 0; |
371 |
} |
372 |
i = 0; |
373 |
for (numFields = 0; numFields < 4; numFields++) { |
374 |
/* |
375 |
* Skip comments and white space. |
376 |
*/ |
377 |
|
378 |
while (1) { |
379 |
while (isspace(UCHAR(c))) { |
380 |
if (Tcl_Read(chan, &c, 1) != 1) { |
381 |
return 0; |
382 |
} |
383 |
} |
384 |
if (c != '#') { |
385 |
break; |
386 |
} |
387 |
do { |
388 |
if (Tcl_Read(chan, &c, 1) != 1) { |
389 |
return 0; |
390 |
} |
391 |
} while (c != '\n'); |
392 |
} |
393 |
|
394 |
/* |
395 |
* Read a field (everything up to the next white space). |
396 |
*/ |
397 |
|
398 |
while (!isspace(UCHAR(c))) { |
399 |
if (i < (BUFFER_SIZE-2)) { |
400 |
buffer[i] = c; |
401 |
i++; |
402 |
} |
403 |
if (Tcl_Read(chan, &c, 1) != 1) { |
404 |
goto done; |
405 |
} |
406 |
} |
407 |
if (i < (BUFFER_SIZE-1)) { |
408 |
buffer[i] = ' '; |
409 |
i++; |
410 |
} |
411 |
} |
412 |
done: |
413 |
buffer[i] = 0; |
414 |
|
415 |
/* |
416 |
* Parse the fields, which are: id, width, height, maxIntensity. |
417 |
*/ |
418 |
|
419 |
if (strncmp(buffer, "P6 ", 3) == 0) { |
420 |
type = PPM; |
421 |
} else if (strncmp(buffer, "P5 ", 3) == 0) { |
422 |
type = PGM; |
423 |
} else { |
424 |
return 0; |
425 |
} |
426 |
if (sscanf(buffer+3, "%d %d %d", widthPtr, heightPtr, maxIntensityPtr) |
427 |
!= 3) { |
428 |
return 0; |
429 |
} |
430 |
return type; |
431 |
} |
432 |
|
433 |
/* End of tkimgppm.c */ |