1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ |
2 | /* GdkPixbuf library - PNM image loader |
3 | * |
4 | * Copyright (C) 1999 Red Hat, Inc. |
5 | * |
6 | * Authors: Jeffrey Stedfast <fejj@helixcode.com> |
7 | * Michael Fulbright <drmike@redhat.com> |
8 | * |
9 | * This library is free software; you can redistribute it and/or |
10 | * modify it under the terms of the GNU Library General Public |
11 | * License as published by the Free Software Foundation; either |
12 | * version 2 of the License, or (at your option) any later version. |
13 | * |
14 | * This library is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | * Library General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU Library General Public |
20 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
21 | */ |
22 | |
23 | #include "config.h" |
24 | #include <stdio.h> |
25 | #include <stdlib.h> |
26 | #include <string.h> |
27 | #include <setjmp.h> |
28 | #include <glib/gi18n-lib.h> |
29 | #include "gdk-pixbuf-io.h" |
30 | |
31 | #define PNM_BUF_SIZE 4096 |
32 | |
33 | #define PNM_FATAL_ERR -1 |
34 | #define PNM_SUSPEND 0 |
35 | #define PNM_OK 1 |
36 | |
37 | typedef enum { |
38 | PNM_FORMAT_PGM = 1, |
39 | PNM_FORMAT_PGM_RAW, |
40 | PNM_FORMAT_PPM, |
41 | PNM_FORMAT_PPM_RAW, |
42 | PNM_FORMAT_PBM, |
43 | PNM_FORMAT_PBM_RAW |
44 | } PnmFormat; |
45 | |
46 | typedef struct { |
47 | guchar buffer[PNM_BUF_SIZE]; |
48 | guchar *byte; |
49 | guint nbytes; |
50 | } PnmIOBuffer; |
51 | |
52 | typedef struct { |
53 | GdkPixbufModuleUpdatedFunc updated_func; |
54 | GdkPixbufModulePreparedFunc prepared_func; |
55 | GdkPixbufModuleSizeFunc size_func; |
56 | gpointer user_data; |
57 | |
58 | GdkPixbuf *pixbuf; |
59 | guchar *pixels; /* incoming pixel data buffer */ |
60 | guchar *dptr; /* current position in pixbuf */ |
61 | |
62 | PnmIOBuffer inbuf; |
63 | |
64 | guint width; |
65 | guint height; |
66 | guint maxval; |
67 | guint rowstride; |
68 | PnmFormat type; |
69 | |
70 | guint output_row; /* last row to be completed */ |
71 | guint output_col; |
72 | gboolean did_prescan; /* are we in image data yet? */ |
73 | gboolean ; /* have we loaded pnm header? */ |
74 | |
75 | guint scan_state; |
76 | |
77 | GError **error; |
78 | |
79 | } PnmLoaderContext; |
80 | |
81 | static GdkPixbuf *gdk_pixbuf__pnm_image_load (FILE *f, GError **error); |
82 | static gpointer gdk_pixbuf__pnm_image_begin_load (GdkPixbufModuleSizeFunc size_func, |
83 | GdkPixbufModulePreparedFunc func, |
84 | GdkPixbufModuleUpdatedFunc func2, |
85 | gpointer user_data, |
86 | GError **error); |
87 | static gboolean gdk_pixbuf__pnm_image_stop_load (gpointer context, GError **error); |
88 | static gboolean gdk_pixbuf__pnm_image_load_increment (gpointer context, |
89 | const guchar *buf, guint size, |
90 | GError **error); |
91 | |
92 | static void explode_bitmap_into_buf (PnmLoaderContext *context); |
93 | static void explode_gray_into_buf (PnmLoaderContext *context); |
94 | |
95 | |
96 | /* explode bitmap data into rgb components */ |
97 | /* we need to know what the row so we can */ |
98 | /* do sub-byte expansion (since 1 byte = 8 pixels) */ |
99 | /* context->dptr MUST point at first byte in incoming data */ |
100 | /* which corresponds to first pixel of row y */ |
101 | static void |
102 | explode_bitmap_into_buf (PnmLoaderContext *context) |
103 | { |
104 | gint j; |
105 | guchar *from, *to, data; |
106 | gint bit; |
107 | guchar *dptr; |
108 | gint wid, x; |
109 | |
110 | g_return_if_fail (context != NULL); |
111 | g_return_if_fail (context->dptr != NULL); |
112 | |
113 | /* I'm no clever bit-hacker so I'm sure this can be optimized */ |
114 | dptr = context->dptr; |
115 | wid = context->width; |
116 | |
117 | from = dptr + ((wid - 1) / 8); |
118 | to = dptr + (wid - 1) * 3; |
119 | bit = 7 - ((wid-1) % 8); |
120 | |
121 | /* get first byte and align properly */ |
122 | data = from[0]; |
123 | for (j = 0; j < bit; j++, data >>= 1); |
124 | |
125 | for (x = wid-1; x >= 0; x--) { |
126 | /* g_print ("%c", (data & 1) ? '*' : ' '); */ |
127 | |
128 | to[0] = to[1] = to[2] = (data & 0x01) ? 0x00 : 0xff; |
129 | |
130 | to -= 3; |
131 | bit++; |
132 | |
133 | if (bit > 7 && x > 0) { |
134 | from--; |
135 | data = from[0]; |
136 | bit = 0; |
137 | } else { |
138 | data >>= 1; |
139 | } |
140 | } |
141 | |
142 | /* g_print ("\n"); */ |
143 | } |
144 | |
145 | /* explode gray image row into rgb components in pixbuf */ |
146 | static void |
147 | explode_gray_into_buf (PnmLoaderContext *context) |
148 | { |
149 | gint j; |
150 | guchar *from, *to; |
151 | guint w; |
152 | |
153 | g_return_if_fail (context != NULL); |
154 | g_return_if_fail (context->dptr != NULL); |
155 | |
156 | /* Expand grey->colour. Expand from the end of the |
157 | * memory down, so we can use the same buffer. |
158 | */ |
159 | w = context->width; |
160 | from = context->dptr + w - 1; |
161 | to = context->dptr + (w - 1) * 3; |
162 | for (j = w - 1; j >= 0; j--) { |
163 | to[0] = from[0]; |
164 | to[1] = from[0]; |
165 | to[2] = from[0]; |
166 | to -= 3; |
167 | from--; |
168 | } |
169 | } |
170 | |
171 | /* skip over whitespace and comments in input buffer */ |
172 | static gint |
173 | pnm_skip_whitespace (PnmIOBuffer *inbuf, GError **error) |
174 | { |
175 | register guchar *inptr; |
176 | guchar *inend; |
177 | |
178 | g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR); |
179 | g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR); |
180 | |
181 | inend = inbuf->byte + inbuf->nbytes; |
182 | inptr = inbuf->byte; |
183 | |
184 | for ( ; inptr < inend; inptr++) { |
185 | if (*inptr == '#') { |
186 | /* in comment - skip to the end of this line */ |
187 | for ( ; *inptr != '\n' && inptr < inend; inptr++) |
188 | ; |
189 | |
190 | if ( inptr == inend || *inptr != '\n' ) { |
191 | /* couldn't read whole comment */ |
192 | return PNM_SUSPEND; |
193 | } |
194 | |
195 | } else if (!g_ascii_isspace (*inptr)) { |
196 | inbuf->byte = inptr; |
197 | inbuf->nbytes = (guint) (inend - inptr); |
198 | return PNM_OK; |
199 | } |
200 | } |
201 | |
202 | inbuf->byte = inptr; |
203 | inbuf->nbytes = (guint) (inend - inptr); |
204 | |
205 | return PNM_SUSPEND; |
206 | } |
207 | |
208 | /* read next number from buffer */ |
209 | static gint |
210 | pnm_read_next_value (PnmIOBuffer *inbuf, gint max_length, guint *value, GError **error) |
211 | { |
212 | register guchar *inptr, *word, *p; |
213 | guchar *inend, buf[129]; |
214 | gchar *endptr; |
215 | gint retval; |
216 | glong result; |
217 | |
218 | g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR); |
219 | g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR); |
220 | g_return_val_if_fail (value != NULL, PNM_FATAL_ERR); |
221 | |
222 | if (max_length < 0) |
223 | max_length = 128; |
224 | |
225 | /* skip white space */ |
226 | if ((retval = pnm_skip_whitespace (inbuf, error)) != PNM_OK) |
227 | return retval; |
228 | |
229 | inend = inbuf->byte + inbuf->nbytes; |
230 | inptr = inbuf->byte; |
231 | |
232 | /* copy this pnm 'word' into a temp buffer */ |
233 | for (p = inptr, word = buf; (p < inend) && !g_ascii_isspace (*p) && (*p != '#') && (p - inptr < max_length); p++, word++) |
234 | *word = *p; |
235 | *word = '\0'; |
236 | |
237 | /* hmmm, there must be more data to this 'word' */ |
238 | if (p == inend || (!g_ascii_isspace (*p) && (*p != '#') && (p - inptr < max_length))) |
239 | return PNM_SUSPEND; |
240 | |
241 | /* get the value */ |
242 | result = strtol (nptr: (gchar *)buf, endptr: &endptr, base: 10); |
243 | if (*endptr != '\0' || result < 0 || result > G_MAXUINT) { |
244 | g_set_error_literal (err: error, |
245 | GDK_PIXBUF_ERROR, |
246 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
247 | _("PNM loader expected to find an integer, but didn’t" )); |
248 | return PNM_FATAL_ERR; |
249 | } |
250 | *value = result; |
251 | |
252 | inbuf->byte = p; |
253 | inbuf->nbytes = (guint) (inend - p); |
254 | |
255 | return PNM_OK; |
256 | } |
257 | |
258 | /* returns PNM_OK, PNM_SUSPEND, or PNM_FATAL_ERR */ |
259 | static gint |
260 | (PnmLoaderContext *context) |
261 | { |
262 | PnmIOBuffer *inbuf; |
263 | gint retval; |
264 | |
265 | g_return_val_if_fail (context != NULL, PNM_FATAL_ERR); |
266 | |
267 | inbuf = &context->inbuf; |
268 | |
269 | if (!context->type) { |
270 | /* file must start with a 'P' followed by a numeral */ |
271 | /* so loop till we get enough data to determine type */ |
272 | if (inbuf->nbytes < 2) |
273 | return PNM_SUSPEND; |
274 | |
275 | if (*inbuf->byte != 'P') { |
276 | g_set_error_literal (err: context->error, |
277 | GDK_PIXBUF_ERROR, |
278 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
279 | _("PNM file has an incorrect initial byte" )); |
280 | return PNM_FATAL_ERR; |
281 | } |
282 | |
283 | inbuf->byte++; |
284 | inbuf->nbytes--; |
285 | |
286 | switch (*inbuf->byte) { |
287 | case '1': |
288 | context->type = PNM_FORMAT_PBM; |
289 | break; |
290 | case '2': |
291 | context->type = PNM_FORMAT_PGM; |
292 | break; |
293 | case '3': |
294 | context->type = PNM_FORMAT_PPM; |
295 | break; |
296 | case '4': |
297 | context->type = PNM_FORMAT_PBM_RAW; |
298 | break; |
299 | case '5': |
300 | context->type = PNM_FORMAT_PGM_RAW; |
301 | break; |
302 | case '6': |
303 | context->type = PNM_FORMAT_PPM_RAW; |
304 | break; |
305 | default: |
306 | g_set_error_literal (err: context->error, |
307 | GDK_PIXBUF_ERROR, |
308 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
309 | _("PNM file is not in a recognized PNM subformat" )); |
310 | return PNM_FATAL_ERR; |
311 | } |
312 | |
313 | if (!inbuf->nbytes) |
314 | return PNM_SUSPEND; |
315 | |
316 | inbuf->byte++; |
317 | inbuf->nbytes--; |
318 | } |
319 | |
320 | if (!context->width) { |
321 | /* read the pixmap width */ |
322 | guint width = 0; |
323 | |
324 | retval = pnm_read_next_value (inbuf, max_length: -1, value: &width, |
325 | error: context->error); |
326 | |
327 | if (retval != PNM_OK) |
328 | return retval; |
329 | |
330 | if (width > G_MAXINT) { |
331 | g_set_error_literal (err: context->error, |
332 | GDK_PIXBUF_ERROR, |
333 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
334 | _("PNM file has an invalid width" )); |
335 | return PNM_FATAL_ERR; |
336 | } |
337 | |
338 | if (!width) { |
339 | g_set_error_literal (err: context->error, |
340 | GDK_PIXBUF_ERROR, |
341 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
342 | _("PNM file has an image width of 0" )); |
343 | return PNM_FATAL_ERR; |
344 | } |
345 | |
346 | context->width = width; |
347 | } |
348 | |
349 | if (!context->height) { |
350 | /* read the pixmap height */ |
351 | guint height = 0; |
352 | |
353 | retval = pnm_read_next_value (inbuf, max_length: -1, value: &height, |
354 | error: context->error); |
355 | |
356 | if (retval != PNM_OK) |
357 | return retval; |
358 | |
359 | if (height > G_MAXINT) { |
360 | g_set_error_literal (err: context->error, |
361 | GDK_PIXBUF_ERROR, |
362 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
363 | _("PNM file has an invalid height" )); |
364 | return PNM_FATAL_ERR; |
365 | } |
366 | |
367 | if (!height) { |
368 | g_set_error_literal (err: context->error, |
369 | GDK_PIXBUF_ERROR, |
370 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
371 | _("PNM file has an image height of 0" )); |
372 | return PNM_FATAL_ERR; |
373 | } |
374 | |
375 | context->height = height; |
376 | } |
377 | |
378 | switch (context->type) { |
379 | case PNM_FORMAT_PPM: |
380 | case PNM_FORMAT_PPM_RAW: |
381 | case PNM_FORMAT_PGM: |
382 | case PNM_FORMAT_PGM_RAW: |
383 | if (!context->maxval) { |
384 | retval = pnm_read_next_value (inbuf, max_length: -1, value: &context->maxval, |
385 | error: context->error); |
386 | |
387 | if (retval != PNM_OK) |
388 | return retval; |
389 | |
390 | if (context->maxval == 0) { |
391 | g_set_error_literal (err: context->error, |
392 | GDK_PIXBUF_ERROR, |
393 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
394 | _("Maximum color value in PNM file is 0" )); |
395 | return PNM_FATAL_ERR; |
396 | } |
397 | |
398 | if (context->maxval > 65535) { |
399 | g_set_error_literal (err: context->error, |
400 | GDK_PIXBUF_ERROR, |
401 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
402 | _("Maximum color value in PNM file is too large" )); |
403 | return PNM_FATAL_ERR; |
404 | } |
405 | |
406 | } |
407 | break; |
408 | default: |
409 | break; |
410 | } |
411 | |
412 | return PNM_OK; |
413 | } |
414 | |
415 | static gint |
416 | pnm_read_raw_scanline (PnmLoaderContext *context) |
417 | { |
418 | PnmIOBuffer *inbuf; |
419 | guint numbytes, offset; |
420 | guint numpix = 0; |
421 | guchar *dest; |
422 | guint i; |
423 | |
424 | g_return_val_if_fail (context != NULL, PNM_FATAL_ERR); |
425 | |
426 | inbuf = &context->inbuf; |
427 | |
428 | switch (context->type) { |
429 | case PNM_FORMAT_PBM_RAW: |
430 | numpix = inbuf->nbytes * 8; |
431 | break; |
432 | case PNM_FORMAT_PGM_RAW: |
433 | numpix = inbuf->nbytes; |
434 | break; |
435 | case PNM_FORMAT_PPM_RAW: |
436 | numpix = inbuf->nbytes / 3; |
437 | break; |
438 | default: |
439 | g_set_error_literal (err: context->error, |
440 | GDK_PIXBUF_ERROR, |
441 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
442 | _("Raw PNM image type is invalid" )); |
443 | return PNM_FATAL_ERR; |
444 | } |
445 | if(context->maxval>255) |
446 | numpix/=2; |
447 | |
448 | numpix = MIN (numpix, context->width - context->output_col); |
449 | |
450 | if (!numpix) |
451 | return PNM_SUSPEND; |
452 | |
453 | context->dptr = context->pixels + context->output_row * context->rowstride; |
454 | |
455 | switch (context->type) { |
456 | case PNM_FORMAT_PBM_RAW: |
457 | numbytes = (numpix / 8) + ((numpix % 8) ? 1 : 0); |
458 | offset = context->output_col / 8; |
459 | break; |
460 | case PNM_FORMAT_PGM_RAW: |
461 | numbytes = numpix; |
462 | offset = context->output_col; |
463 | break; |
464 | case PNM_FORMAT_PPM_RAW: |
465 | numbytes = numpix * 3; |
466 | offset = context->output_col * 3; |
467 | break; |
468 | default: |
469 | g_set_error_literal (err: context->error, |
470 | GDK_PIXBUF_ERROR, |
471 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
472 | _("Raw PNM image type is invalid" )); |
473 | return PNM_FATAL_ERR; |
474 | } |
475 | if(context->maxval>255) |
476 | numbytes*=2; |
477 | |
478 | switch (context->type) { |
479 | case PNM_FORMAT_PBM_RAW: |
480 | dest = context->dptr + offset; |
481 | memcpy (dest: dest, src: inbuf->byte, n: numbytes); |
482 | break; |
483 | case PNM_FORMAT_PGM_RAW: |
484 | case PNM_FORMAT_PPM_RAW: |
485 | dest = context->dptr + offset; |
486 | |
487 | if (context->maxval == 255) { |
488 | /* special-case optimization */ |
489 | memcpy (dest: dest, src: inbuf->byte, n: numbytes); |
490 | } else if(context->maxval == 65535) { |
491 | /* optimized version of the next case */ |
492 | for(i=0; i < numbytes ; i+=2) { |
493 | *dest++=inbuf->byte[i]; |
494 | } |
495 | } else if(context->maxval > 255) { |
496 | /* scale down to 256 colors */ |
497 | for(i=0; i < numbytes ; i+=2) { |
498 | guint v=inbuf->byte[i]*256+inbuf->byte[i+1]; |
499 | *dest++=v*255/context->maxval; |
500 | } |
501 | } else { |
502 | for (i = 0; i < numbytes; i++) { |
503 | guchar *byte = inbuf->byte + i; |
504 | |
505 | /* scale the color to an 8-bit color depth */ |
506 | if (*byte > context->maxval) |
507 | *dest++ = 255; |
508 | else |
509 | *dest++ = (guchar) (255 * *byte / context->maxval); |
510 | } |
511 | } |
512 | break; |
513 | default: |
514 | g_set_error_literal (err: context->error, |
515 | GDK_PIXBUF_ERROR, |
516 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
517 | _("Raw PNM image type is invalid" )); |
518 | return PNM_FATAL_ERR; |
519 | } |
520 | |
521 | inbuf->byte += numbytes; |
522 | inbuf->nbytes -= numbytes; |
523 | |
524 | context->output_col += numpix; |
525 | if (context->output_col == context->width) { |
526 | if (context->type == PNM_FORMAT_PBM_RAW) |
527 | explode_bitmap_into_buf (context); |
528 | else if (context->type == PNM_FORMAT_PGM_RAW) |
529 | explode_gray_into_buf (context); |
530 | |
531 | context->output_col = 0; |
532 | context->output_row++; |
533 | } else { |
534 | return PNM_SUSPEND; |
535 | } |
536 | |
537 | return PNM_OK; |
538 | } |
539 | |
540 | static gint |
541 | pnm_read_ascii_mono_scanline (PnmLoaderContext *context) |
542 | { |
543 | PnmIOBuffer *inbuf; |
544 | guint value; |
545 | gint retval; |
546 | guchar *dptr; |
547 | gint max_length; |
548 | |
549 | if (context->type == PNM_FORMAT_PBM) |
550 | max_length = 1; |
551 | else |
552 | max_length = -1; |
553 | |
554 | inbuf = &context->inbuf; |
555 | |
556 | context->dptr = context->pixels + context->output_row * context->rowstride; |
557 | |
558 | dptr = context->dptr + context->output_col * 3; |
559 | |
560 | while (TRUE) { |
561 | retval = pnm_read_next_value (inbuf, max_length, value: &value, error: context->error); |
562 | if (retval != PNM_OK) |
563 | return retval; |
564 | |
565 | if (context->type == PNM_FORMAT_PBM) { |
566 | value = value ? 0 : 0xff; |
567 | } |
568 | else { |
569 | /* scale the color up or down to an 8-bit color depth */ |
570 | if (value > context->maxval) |
571 | value = 255; |
572 | else |
573 | value = (guchar)(255 * value / context->maxval); |
574 | } |
575 | |
576 | *dptr++ = value; |
577 | *dptr++ = value; |
578 | *dptr++ = value; |
579 | |
580 | context->output_col++; |
581 | |
582 | if (context->output_col == context->width) { |
583 | context->output_col = 0; |
584 | context->output_row++; |
585 | break; |
586 | } |
587 | } |
588 | |
589 | return PNM_OK; |
590 | } |
591 | |
592 | static gint |
593 | pnm_read_ascii_color_scanline (PnmLoaderContext *context) |
594 | { |
595 | PnmIOBuffer *inbuf; |
596 | guint value, i; |
597 | guchar *dptr; |
598 | gint retval; |
599 | |
600 | inbuf = &context->inbuf; |
601 | |
602 | context->dptr = context->pixels + context->output_row * context->rowstride; |
603 | |
604 | dptr = context->dptr + context->output_col * 3 + context->scan_state; |
605 | |
606 | while (TRUE) { |
607 | for (i = context->scan_state; i < 3; i++) { |
608 | retval = pnm_read_next_value (inbuf, max_length: -1, value: &value, error: context->error); |
609 | if (retval != PNM_OK) { |
610 | /* save state and return */ |
611 | context->scan_state = i; |
612 | return retval; |
613 | } |
614 | |
615 | if (value > context->maxval) |
616 | *dptr++ = 255; |
617 | else |
618 | *dptr++ = (guchar)(255 * value / context->maxval); |
619 | } |
620 | |
621 | context->scan_state = 0; |
622 | context->output_col++; |
623 | |
624 | if (context->output_col == context->width) { |
625 | context->output_col = 0; |
626 | context->output_row++; |
627 | break; |
628 | } |
629 | } |
630 | |
631 | return PNM_OK; |
632 | } |
633 | |
634 | /* returns 1 if a scanline was converted, 0 means we ran out of data */ |
635 | static gint |
636 | pnm_read_scanline (PnmLoaderContext *context) |
637 | { |
638 | gint retval; |
639 | |
640 | g_return_val_if_fail (context != NULL, PNM_FATAL_ERR); |
641 | |
642 | /* read in image data */ |
643 | /* for raw formats this is trivial */ |
644 | switch (context->type) { |
645 | case PNM_FORMAT_PBM_RAW: |
646 | case PNM_FORMAT_PGM_RAW: |
647 | case PNM_FORMAT_PPM_RAW: |
648 | retval = pnm_read_raw_scanline (context); |
649 | if (retval != PNM_OK) |
650 | return retval; |
651 | break; |
652 | case PNM_FORMAT_PBM: |
653 | case PNM_FORMAT_PGM: |
654 | retval = pnm_read_ascii_mono_scanline (context); |
655 | if (retval != PNM_OK) |
656 | return retval; |
657 | break; |
658 | case PNM_FORMAT_PPM: |
659 | retval = pnm_read_ascii_color_scanline (context); |
660 | if (retval != PNM_OK) |
661 | return retval; |
662 | break; |
663 | default: |
664 | g_set_error_literal (err: context->error, |
665 | GDK_PIXBUF_ERROR, |
666 | code: GDK_PIXBUF_ERROR_UNKNOWN_TYPE, |
667 | _("PNM image loader does not support this PNM subformat" )); |
668 | |
669 | return PNM_FATAL_ERR; |
670 | } |
671 | |
672 | return PNM_OK; |
673 | } |
674 | |
675 | /* Shared library entry point */ |
676 | static GdkPixbuf * |
677 | gdk_pixbuf__pnm_image_load (FILE *f, GError **error) |
678 | { |
679 | PnmLoaderContext context; |
680 | PnmIOBuffer *inbuf; |
681 | gint nbytes; |
682 | gint retval; |
683 | |
684 | /* pretend to be doing progressive loading */ |
685 | context.updated_func = NULL; |
686 | context.prepared_func = NULL; |
687 | context.user_data = NULL; |
688 | context.type = 0; |
689 | context.inbuf.nbytes = 0; |
690 | context.inbuf.byte = NULL; |
691 | context.width = 0; |
692 | context.height = 0; |
693 | context.maxval = 0; |
694 | context.pixels = NULL; |
695 | context.pixbuf = NULL; |
696 | context.got_header = FALSE; |
697 | context.did_prescan = FALSE; |
698 | context.scan_state = 0; |
699 | context.error = error; |
700 | |
701 | inbuf = &context.inbuf; |
702 | |
703 | while (TRUE) { |
704 | guint num_to_read; |
705 | |
706 | /* keep buffer as full as possible */ |
707 | num_to_read = PNM_BUF_SIZE - inbuf->nbytes; |
708 | |
709 | if (inbuf->byte != NULL && inbuf->nbytes > 0) |
710 | memmove (dest: inbuf->buffer, src: inbuf->byte, n: inbuf->nbytes); |
711 | |
712 | nbytes = fread (ptr: inbuf->buffer + inbuf->nbytes, size: 1, n: num_to_read, stream: f); |
713 | |
714 | /* error checking */ |
715 | if (nbytes == 0) { |
716 | /* we ran out of data? */ |
717 | if (context.pixbuf) |
718 | g_object_unref (object: context.pixbuf); |
719 | g_set_error_literal (err: error, |
720 | GDK_PIXBUF_ERROR, |
721 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
722 | _("Premature end-of-file encountered" )); |
723 | return NULL; |
724 | } |
725 | |
726 | inbuf->nbytes += nbytes; |
727 | inbuf->byte = inbuf->buffer; |
728 | |
729 | /* get header if needed */ |
730 | if (!context.got_header) { |
731 | retval = pnm_read_header (context: &context); |
732 | if (retval == PNM_FATAL_ERR) |
733 | return NULL; |
734 | else if (retval == PNM_SUSPEND) |
735 | continue; |
736 | |
737 | context.got_header = TRUE; |
738 | } |
739 | |
740 | /* scan until we hit image data */ |
741 | if (!context.did_prescan) { |
742 | switch (context.type) { |
743 | case PNM_FORMAT_PBM_RAW: |
744 | case PNM_FORMAT_PGM_RAW: |
745 | case PNM_FORMAT_PPM_RAW: |
746 | if (inbuf->nbytes <= 0) |
747 | continue; |
748 | /* raw formats require exactly one whitespace */ |
749 | if (!g_ascii_isspace(*(inbuf->byte))) |
750 | { |
751 | g_set_error_literal (err: error, |
752 | GDK_PIXBUF_ERROR, |
753 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
754 | _("Raw PNM formats require exactly one whitespace before sample data" )); |
755 | return NULL; |
756 | } |
757 | inbuf->nbytes--; |
758 | inbuf->byte++; |
759 | break; |
760 | default: |
761 | retval = pnm_skip_whitespace (inbuf, |
762 | error: context.error); |
763 | if (retval == PNM_FATAL_ERR) |
764 | return NULL; |
765 | else if (retval == PNM_SUSPEND) |
766 | continue; |
767 | break; |
768 | } |
769 | context.did_prescan = TRUE; |
770 | context.output_row = 0; |
771 | context.output_col = 0; |
772 | |
773 | context.pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, FALSE, bits_per_sample: 8, |
774 | width: context.width, height: context.height); |
775 | |
776 | if (!context.pixbuf) { |
777 | /* Failed to allocate memory */ |
778 | g_set_error_literal (err: error, |
779 | GDK_PIXBUF_ERROR, |
780 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
781 | _("Cannot allocate memory for loading PNM image" )); |
782 | return NULL; |
783 | } |
784 | |
785 | context.rowstride = gdk_pixbuf_get_rowstride (pixbuf: context.pixbuf); |
786 | context.pixels = gdk_pixbuf_get_pixels (pixbuf: context.pixbuf); |
787 | } |
788 | |
789 | /* if we got here we're reading image data */ |
790 | while (context.output_row < context.height) { |
791 | retval = pnm_read_scanline (context: &context); |
792 | |
793 | if (retval == PNM_SUSPEND) { |
794 | break; |
795 | } else if (retval == PNM_FATAL_ERR) { |
796 | if (context.pixbuf) |
797 | g_object_unref (object: context.pixbuf); |
798 | |
799 | return NULL; |
800 | } |
801 | } |
802 | |
803 | if (context.output_row < context.height) |
804 | continue; |
805 | else |
806 | break; |
807 | } |
808 | |
809 | return context.pixbuf; |
810 | } |
811 | |
812 | /* |
813 | * func - called when we have pixmap created (but no image data) |
814 | * user_data - passed as arg 1 to func |
815 | * return context (opaque to user) |
816 | */ |
817 | |
818 | static gpointer |
819 | gdk_pixbuf__pnm_image_begin_load (GdkPixbufModuleSizeFunc size_func, |
820 | GdkPixbufModulePreparedFunc prepared_func, |
821 | GdkPixbufModuleUpdatedFunc updated_func, |
822 | gpointer user_data, |
823 | GError **error) |
824 | { |
825 | PnmLoaderContext *context; |
826 | |
827 | g_assert (size_func != NULL); |
828 | g_assert (prepared_func != NULL); |
829 | g_assert (updated_func != NULL); |
830 | |
831 | context = g_try_malloc (n_bytes: sizeof (PnmLoaderContext)); |
832 | if (!context) { |
833 | g_set_error_literal (err: error, GDK_PIXBUF_ERROR, |
834 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
835 | _("Insufficient memory to load PNM context struct" )); |
836 | return NULL; |
837 | } |
838 | memset (s: context, c: 0, n: sizeof (PnmLoaderContext)); |
839 | context->size_func = size_func; |
840 | context->prepared_func = prepared_func; |
841 | context->updated_func = updated_func; |
842 | context->user_data = user_data; |
843 | context->width = 0; |
844 | context->height = 0; |
845 | context->maxval = 0; |
846 | context->pixbuf = NULL; |
847 | context->pixels = NULL; |
848 | context->got_header = FALSE; |
849 | context->did_prescan = FALSE; |
850 | context->scan_state = 0; |
851 | |
852 | context->inbuf.nbytes = 0; |
853 | context->inbuf.byte = NULL; |
854 | |
855 | context->error = error; |
856 | |
857 | return (gpointer) context; |
858 | } |
859 | |
860 | /* |
861 | * context - returned from image_begin_load |
862 | * |
863 | * free context, unref gdk_pixbuf |
864 | */ |
865 | static gboolean |
866 | gdk_pixbuf__pnm_image_stop_load (gpointer data, |
867 | GError **error) |
868 | { |
869 | PnmLoaderContext *context = (PnmLoaderContext *) data; |
870 | gboolean retval = TRUE; |
871 | |
872 | g_return_val_if_fail (context != NULL, TRUE); |
873 | |
874 | if (context->pixbuf) |
875 | g_object_unref (object: context->pixbuf); |
876 | else { |
877 | g_set_error_literal (err: error, GDK_PIXBUF_ERROR, |
878 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
879 | _("Premature end-of-file encountered" )); |
880 | retval = FALSE; |
881 | } |
882 | |
883 | #if 0 |
884 | /* We should ignore trailing newlines and we can't |
885 | generally complain about trailing stuff at all, since |
886 | pnm allows to put multiple images in a file |
887 | */ |
888 | if (context->inbuf.nbytes > 0) { |
889 | g_set_error_literal (error, |
890 | GDK_PIXBUF_ERROR, |
891 | GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
892 | _("Unexpected end of PNM image data" )); |
893 | retval = FALSE; |
894 | } |
895 | #endif |
896 | |
897 | g_free (mem: context); |
898 | |
899 | return retval; |
900 | } |
901 | |
902 | /* |
903 | * context - from image_begin_load |
904 | * buf - new image data |
905 | * size - length of new image data |
906 | * |
907 | * append image data onto inrecrementally built output image |
908 | */ |
909 | static gboolean |
910 | gdk_pixbuf__pnm_image_load_increment (gpointer data, |
911 | const guchar *buf, guint size, |
912 | GError **error) |
913 | { |
914 | PnmLoaderContext *context = (PnmLoaderContext *)data; |
915 | PnmIOBuffer *inbuf; |
916 | const guchar *bufhd; |
917 | guint num_left, spinguard; |
918 | gint retval; |
919 | |
920 | g_return_val_if_fail (context != NULL, FALSE); |
921 | g_return_val_if_fail (buf != NULL, FALSE); |
922 | |
923 | context->error = error; |
924 | |
925 | bufhd = buf; |
926 | inbuf = &context->inbuf; |
927 | |
928 | num_left = size; |
929 | spinguard = 0; |
930 | while (TRUE) { |
931 | guint num_to_copy; |
932 | |
933 | /* keep buffer as full as possible */ |
934 | num_to_copy = MIN (PNM_BUF_SIZE - inbuf->nbytes, num_left); |
935 | |
936 | if (num_to_copy == 0) |
937 | spinguard++; |
938 | |
939 | if (spinguard > 1) |
940 | return TRUE; |
941 | |
942 | if (inbuf->byte != NULL && inbuf->nbytes > 0) |
943 | memmove (dest: inbuf->buffer, src: inbuf->byte, n: inbuf->nbytes); |
944 | |
945 | memcpy (dest: inbuf->buffer + inbuf->nbytes, src: bufhd, n: num_to_copy); |
946 | bufhd += num_to_copy; |
947 | inbuf->nbytes += num_to_copy; |
948 | inbuf->byte = inbuf->buffer; |
949 | num_left -= num_to_copy; |
950 | |
951 | /* ran out of data and we haven't exited main loop */ |
952 | if (inbuf->nbytes == 0) |
953 | return TRUE; |
954 | |
955 | /* get header if needed */ |
956 | if (!context->got_header) { |
957 | retval = pnm_read_header (context); |
958 | |
959 | if (retval == PNM_FATAL_ERR) |
960 | return FALSE; |
961 | else if (retval == PNM_SUSPEND) |
962 | continue; |
963 | |
964 | context->got_header = TRUE; |
965 | } |
966 | |
967 | { |
968 | gint w = context->width; |
969 | gint h = context->height; |
970 | (*context->size_func) (&w, &h, context->user_data); |
971 | |
972 | if (w == 0 || h == 0) |
973 | return FALSE; |
974 | } |
975 | |
976 | |
977 | /* scan until we hit image data */ |
978 | if (!context->did_prescan) { |
979 | switch (context->type) { |
980 | case PNM_FORMAT_PBM_RAW: |
981 | case PNM_FORMAT_PGM_RAW: |
982 | case PNM_FORMAT_PPM_RAW: |
983 | if (inbuf->nbytes <= 0) |
984 | continue; |
985 | /* raw formats require exactly one whitespace */ |
986 | if (!g_ascii_isspace(*(inbuf->byte))) |
987 | { |
988 | g_set_error_literal (err: error, |
989 | GDK_PIXBUF_ERROR, |
990 | code: GDK_PIXBUF_ERROR_CORRUPT_IMAGE, |
991 | _("Raw PNM formats require exactly one whitespace before sample data" )); |
992 | return FALSE; |
993 | } |
994 | inbuf->nbytes--; |
995 | inbuf->byte++; |
996 | break; |
997 | default: |
998 | retval = pnm_skip_whitespace (inbuf, |
999 | error: context->error); |
1000 | if (retval == PNM_FATAL_ERR) |
1001 | return FALSE; |
1002 | else if (retval == PNM_SUSPEND) |
1003 | continue; |
1004 | break; |
1005 | } |
1006 | context->did_prescan = TRUE; |
1007 | context->output_row = 0; |
1008 | context->output_col = 0; |
1009 | |
1010 | context->pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, |
1011 | FALSE, |
1012 | bits_per_sample: 8, |
1013 | width: context->width, |
1014 | height: context->height); |
1015 | |
1016 | if (context->pixbuf == NULL) { |
1017 | g_set_error_literal (err: error, |
1018 | GDK_PIXBUF_ERROR, |
1019 | code: GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, |
1020 | _("Insufficient memory to load PNM file" )); |
1021 | return FALSE; |
1022 | } |
1023 | |
1024 | context->pixels = gdk_pixbuf_get_pixels (pixbuf: context->pixbuf); |
1025 | context->rowstride = gdk_pixbuf_get_rowstride (pixbuf: context->pixbuf); |
1026 | |
1027 | /* Notify the client that we are ready to go */ |
1028 | (* context->prepared_func) (context->pixbuf, |
1029 | NULL, |
1030 | context->user_data); |
1031 | } |
1032 | |
1033 | /* if we got here we're reading image data */ |
1034 | while (context->output_row < context->height) { |
1035 | retval = pnm_read_scanline (context); |
1036 | |
1037 | if (retval == PNM_SUSPEND) { |
1038 | break; |
1039 | } else if (retval == PNM_FATAL_ERR) { |
1040 | return FALSE; |
1041 | } else if (retval == PNM_OK) { |
1042 | /* send updated signal */ |
1043 | (* context->updated_func) (context->pixbuf, |
1044 | 0, |
1045 | context->output_row-1, |
1046 | context->width, |
1047 | 1, |
1048 | context->user_data); |
1049 | } |
1050 | } |
1051 | |
1052 | if (context->output_row < context->height) |
1053 | continue; |
1054 | else |
1055 | break; |
1056 | } |
1057 | |
1058 | return TRUE; |
1059 | } |
1060 | |
1061 | #ifndef INCLUDE_pnm |
1062 | #define MODULE_ENTRY(function) G_MODULE_EXPORT void function |
1063 | #else |
1064 | #define MODULE_ENTRY(function) void _gdk_pixbuf__pnm_ ## function |
1065 | #endif |
1066 | |
1067 | MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module) |
1068 | { |
1069 | module->load = gdk_pixbuf__pnm_image_load; |
1070 | module->begin_load = gdk_pixbuf__pnm_image_begin_load; |
1071 | module->stop_load = gdk_pixbuf__pnm_image_stop_load; |
1072 | module->load_increment = gdk_pixbuf__pnm_image_load_increment; |
1073 | } |
1074 | |
1075 | MODULE_ENTRY (fill_info) (GdkPixbufFormat *info) |
1076 | { |
1077 | static const GdkPixbufModulePattern signature[] = { |
1078 | { "P1" , NULL, 100 }, |
1079 | { "P2" , NULL, 100 }, |
1080 | { "P3" , NULL, 100 }, |
1081 | { "P4" , NULL, 100 }, |
1082 | { "P5" , NULL, 100 }, |
1083 | { "P6" , NULL, 100 }, |
1084 | { NULL, NULL, 0 } |
1085 | }; |
1086 | static const gchar *mime_types[] = { |
1087 | "image/x-portable-anymap" , |
1088 | "image/x-portable-bitmap" , |
1089 | "image/x-portable-graymap" , |
1090 | "image/x-portable-pixmap" , |
1091 | NULL |
1092 | }; |
1093 | static const gchar *extensions[] = { |
1094 | "pnm" , |
1095 | "pbm" , |
1096 | "pgm" , |
1097 | "ppm" , |
1098 | NULL |
1099 | }; |
1100 | |
1101 | info->name = "pnm" ; |
1102 | info->signature = (GdkPixbufModulePattern *) signature; |
1103 | info->description = NC_("image format" , "PNM/PBM/PGM/PPM" ); |
1104 | info->mime_types = (gchar **) mime_types; |
1105 | info->extensions = (gchar **) extensions; |
1106 | info->flags = GDK_PIXBUF_FORMAT_THREADSAFE; |
1107 | info->license = "LGPL" ; |
1108 | } |
1109 | |