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