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
37typedef 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
46typedef struct {
47 guchar buffer[PNM_BUF_SIZE];
48 guchar *byte;
49 guint nbytes;
50} PnmIOBuffer;
51
52typedef 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 got_header; /* have we loaded pnm header? */
74
75 guint scan_state;
76
77 GError **error;
78
79} PnmLoaderContext;
80
81static GdkPixbuf *gdk_pixbuf__pnm_image_load (FILE *f, GError **error);
82static gpointer gdk_pixbuf__pnm_image_begin_load (GdkPixbufModuleSizeFunc size_func,
83 GdkPixbufModulePreparedFunc func,
84 GdkPixbufModuleUpdatedFunc func2,
85 gpointer user_data,
86 GError **error);
87static gboolean gdk_pixbuf__pnm_image_stop_load (gpointer context, GError **error);
88static gboolean gdk_pixbuf__pnm_image_load_increment (gpointer context,
89 const guchar *buf, guint size,
90 GError **error);
91
92static void explode_bitmap_into_buf (PnmLoaderContext *context);
93static 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 */
101static void
102explode_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 */
146static void
147explode_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 */
172static gint
173pnm_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 */
209static gint
210pnm_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 */
259static gint
260pnm_read_header (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
415static gint
416pnm_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
540static gint
541pnm_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
592static gint
593pnm_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 */
635static gint
636pnm_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 */
676static GdkPixbuf *
677gdk_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
818static gpointer
819gdk_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 */
865static gboolean
866gdk_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 */
909static gboolean
910gdk_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
1067MODULE_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
1075MODULE_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

source code of gtk/subprojects/gdk-pixbuf/gdk-pixbuf/io-pnm.c