]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-pnm.c
fix some shell typos
[~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 <ctype.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <setjmp.h>
31 #include "gdk-pixbuf-private.h"
32 #include "gdk-pixbuf-io.h"
33
34
35 #define PNM_BUF_SIZE 4096
36
37 #define PNM_FATAL_ERR  -1
38 #define PNM_SUSPEND     0
39 #define PNM_OK          1
40
41 typedef enum {
42         PNM_FORMAT_PGM = 1,
43         PNM_FORMAT_PGM_RAW,
44         PNM_FORMAT_PPM,
45         PNM_FORMAT_PPM_RAW,
46         PNM_FORMAT_PBM,
47         PNM_FORMAT_PBM_RAW
48 } PnmFormat;
49
50 typedef struct {
51         guchar buffer[PNM_BUF_SIZE];
52         guchar *byte;
53         guint nbytes;
54 } PnmIOBuffer;
55
56 typedef struct {
57         ModuleUpdatedNotifyFunc updated_func;
58         ModulePreparedNotifyFunc prepared_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     (ModulePreparedNotifyFunc func, 
86                                                          ModuleUpdatedNotifyFunc func2,
87                                                          gpointer user_data,
88                                                          GError **error);
89 static gboolean    gdk_pixbuf__pnm_image_stop_load      (gpointer context, GError **error);
90 static gboolean    gdk_pixbuf__pnm_image_load_increment (gpointer context,
91                                                          const guchar *buf, guint size,
92                                                          GError **error);
93
94 static void explode_bitmap_into_buf              (PnmLoaderContext *context);
95 static void explode_gray_into_buf                (PnmLoaderContext *context);
96
97 /* Destroy notification function for the pixbuf */
98 static void
99 free_buffer (guchar *pixels, gpointer data)
100 {
101         g_free (pixels);
102 }
103
104
105 /* explode bitmap data into rgb components         */
106 /* we need to know what the row so we can          */
107 /* do sub-byte expansion (since 1 byte = 8 pixels) */
108 /* context->dptr MUST point at first byte in incoming data  */
109 /* which corresponds to first pixel of row y       */
110 static void
111 explode_bitmap_into_buf (PnmLoaderContext *context)
112 {
113         gint j;
114         guchar *from, *to, data;
115         gint bit;
116         guchar *dptr;
117         gint wid, x, y;
118         
119         g_return_if_fail (context != NULL);
120         g_return_if_fail (context->dptr != NULL);
121         
122         /* I'm no clever bit-hacker so I'm sure this can be optimized */
123         dptr = context->dptr;
124         y    = context->output_row;
125         wid  = context->width;
126         
127         from = dptr + ((wid - 1) / 8);
128         to   = dptr + (wid - 1) * 3;
129 /*      bit  = 7 - (((y+1)*wid-1) % 8); */
130         bit  = 7 - ((wid-1) % 8);
131         
132         /* get first byte and align properly */
133         data = from[0];
134         for (j = 0; j < bit; j++, data >>= 1);
135         
136         for (x = wid-1; x >= 0; x--) {
137 /*              g_print ("%c",  (data & 1) ? '*' : ' '); */
138                 
139                 to[0] = to[1] = to[2] = (data & 0x01) ? 0x00 : 0xff;
140                 
141                 to -= 3;
142                 bit++;
143                 
144                 if (bit > 7) {
145                         from--;
146                         data = from[0];
147                         bit = 0;
148                 } else {
149                         data >>= 1;
150                 }
151         }
152         
153 /*      g_print ("\n"); */
154 }
155
156 /* explode gray image row into rgb components in pixbuf */
157 static void
158 explode_gray_into_buf (PnmLoaderContext *context)
159 {
160         gint j;
161         guchar *from, *to;
162         guint w;
163         
164         g_return_if_fail (context != NULL);
165         g_return_if_fail (context->dptr != NULL);
166         
167         /* Expand grey->colour.  Expand from the end of the
168          * memory down, so we can use the same buffer.
169          */
170         w = context->width;
171         from = context->dptr + w - 1;
172         to = context->dptr + (w - 1) * 3;
173         for (j = w - 1; j >= 0; j--) {
174                 to[0] = from[0];
175                 to[1] = from[0];
176                 to[2] = from[0];
177                 to -= 3;
178                 from--;
179         }
180 }
181
182 /* skip over whitespace and comments in input buffer */
183 static gint
184 pnm_skip_whitespace (PnmIOBuffer *inbuf, GError **error)
185 {
186         register guchar *inptr;
187         guchar *inend;
188         
189         g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR);
190         g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR);
191         
192         inend = inbuf->byte + inbuf->nbytes;
193         inptr = inbuf->byte;
194         
195         for ( ; inptr < inend; inptr++) {
196                 if (*inptr == '#') {
197                         /* in comment - skip to the end of this line */
198                         for ( ; *inptr != '\n' && inptr < inend; inptr++);
199                 } else if (!isspace (*inptr)) {
200                         inbuf->byte = inptr;
201                         inbuf->nbytes = (guint) (inend - inptr);
202                         return PNM_OK;
203                 }
204         }
205         
206         inbuf->byte = inptr;
207         inbuf->nbytes = (guint) (inend - inptr);
208         
209         return PNM_SUSPEND;
210 }
211
212 /* read next number from buffer */
213 static gint
214 pnm_read_next_value (PnmIOBuffer *inbuf, guint *value, GError **error)
215 {
216         register guchar *inptr, *word, *p;
217         guchar *inend, buf[128];
218         gchar *endptr;
219         gint retval;
220         
221         g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR);
222         g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR);
223         g_return_val_if_fail (value != NULL, PNM_FATAL_ERR);
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) && !isspace (*p) && (p - inptr < 128); p++, word++)
234                 *word = *p;
235         *word = '\0';
236         
237         /* hmmm, there must be more data to this 'word' */
238         if (!isspace (*p))
239                 return PNM_SUSPEND;
240         
241         /* get the value */
242         *value = strtol (buf, &endptr, 10);
243         if (*endptr != '\0') {
244                 g_set_error (error,
245                              GDK_PIXBUF_ERROR,
246                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
247                              _("PNM loader expected to find an integer, but didn't"));
248                 return PNM_FATAL_ERR;
249         }
250         
251         inbuf->byte = p;
252         inbuf->nbytes = (guint) (inend - p);
253         
254         return PNM_OK;
255 }
256
257 /* returns PNM_OK, PNM_SUSPEND, or PNM_FATAL_ERR */
258 static gint
259 pnm_read_header (PnmLoaderContext *context)
260 {
261         PnmIOBuffer *inbuf;
262         gint retval;
263         
264         g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
265         
266         inbuf = &context->inbuf;
267         
268         if (!context->type) {
269                 /* file must start with a 'P' followed by a numeral  */
270                 /* so loop till we get enough data to determine type */
271                 if (inbuf->nbytes < 2)
272                         return PNM_SUSPEND;
273                 
274                 if (*inbuf->byte != 'P') {
275                         g_set_error (context->error,
276                                      GDK_PIXBUF_ERROR,
277                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
278                                      _("PNM file has an incorrect initial byte"));
279                         return PNM_FATAL_ERR;
280                 }
281                 
282                 inbuf->byte++;
283                 inbuf->nbytes--;
284                 
285                 switch (*inbuf->byte) {
286                 case '1':
287                         context->type = PNM_FORMAT_PBM;
288                         break;
289                 case '2':
290                         context->type = PNM_FORMAT_PGM;
291                         break;
292                 case '3':
293                         context->type = PNM_FORMAT_PPM;
294                         break;
295                 case '4':
296                         context->type = PNM_FORMAT_PBM_RAW;
297                         break;
298                 case '5':
299                         context->type = PNM_FORMAT_PGM_RAW;
300                         break;
301                 case '6':
302                         context->type = PNM_FORMAT_PPM_RAW;
303                         break;
304                 default:
305                         g_set_error (context->error,
306                                      GDK_PIXBUF_ERROR,
307                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
308                                      _("PNM file is not in a recognized PNM subformat"));
309                         return PNM_FATAL_ERR;
310                 }
311                 
312                 if (!inbuf->nbytes)
313                         return PNM_SUSPEND;
314                 
315                 inbuf->byte++;
316                 inbuf->nbytes--;
317         }
318         
319         if (!context->width) {
320                 /* read the pixmap width */
321                 guint width = 0;
322                 
323                 retval = pnm_read_next_value (inbuf, &width,
324                                               context->error);
325                 
326                 if (retval != PNM_OK) 
327                         return retval;
328                 
329                 if (!width) {
330                         g_set_error (context->error,
331                                      GDK_PIXBUF_ERROR,
332                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
333                                      _("PNM file has an image width of 0"));
334                         return PNM_FATAL_ERR;
335                 }
336                 
337                 context->width = width;
338         }
339         
340         if (!context->height) {
341                 /* read the pixmap height */
342                 guint height = 0;
343                 
344                 retval = pnm_read_next_value (inbuf, &height,
345                                               context->error);
346                 
347                 if (retval != PNM_OK)
348                         return retval;
349                 
350                 if (!height) {
351                         g_set_error (context->error,
352                                      GDK_PIXBUF_ERROR,
353                                      GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
354                                      _("PNM file has an image height of 0"));
355                         return PNM_FATAL_ERR;
356                 }
357                 
358                 context->height = height;
359         }
360         
361         switch (context->type) {
362         case PNM_FORMAT_PPM:
363         case PNM_FORMAT_PPM_RAW:
364         case PNM_FORMAT_PGM:
365         case PNM_FORMAT_PGM_RAW:
366                 if (!context->maxval) {
367                         retval = pnm_read_next_value (inbuf, &context->maxval,
368                                                       context->error);
369                         
370                         if (retval != PNM_OK)
371                                 return retval;
372                         
373                         if (context->maxval == 0) {
374                                 g_set_error (context->error,
375                                              GDK_PIXBUF_ERROR,
376                                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
377                                              _("Maximum color value in PNM file is 0"));
378                                 return PNM_FATAL_ERR;
379                         }
380                 }
381                 break;
382         default:
383                 break;
384         }
385         
386         return PNM_OK;
387 }
388
389 static gint
390 pnm_read_raw_scanline (PnmLoaderContext *context)
391 {
392         PnmIOBuffer *inbuf;
393         guint numbytes, offset;
394         guint numpix = 0;
395         guchar *dest;
396         guint i;
397         
398         g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
399         
400         inbuf = &context->inbuf;
401         
402         switch (context->type) {
403         case PNM_FORMAT_PBM_RAW:
404                 numpix = inbuf->nbytes * 8;
405                 break;
406         case PNM_FORMAT_PGM_RAW:
407                 numpix = inbuf->nbytes;
408                 break;
409         case PNM_FORMAT_PPM_RAW:
410                 numpix = inbuf->nbytes / 3;
411                 break;
412         default:
413                 g_set_error (context->error,
414                              GDK_PIXBUF_ERROR,
415                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
416                              _("Raw PNM image type is invalid"));
417                 return PNM_FATAL_ERR;
418         }
419         
420         numpix = MIN (numpix, context->width - context->output_col);
421         
422         if (!numpix)
423                 return PNM_SUSPEND;
424         
425         context->dptr = context->pixels + context->output_row * context->rowstride;
426         
427         switch (context->type) {
428         case PNM_FORMAT_PBM_RAW:
429                 numbytes = (numpix / 8) + ((numpix % 8) ? 1 : 0);
430                 offset = context->output_col / 8;
431                 break;
432         case PNM_FORMAT_PGM_RAW:
433                 numbytes = numpix;
434                 offset = context->output_col;
435                 break;
436         case PNM_FORMAT_PPM_RAW:
437                 numbytes = numpix * 3;
438                 offset = context->output_col * 3;
439                 break;
440         default:
441                 g_set_error (context->error,
442                              GDK_PIXBUF_ERROR,
443                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
444                              _("Raw PNM image type is invalid"));
445                 return PNM_FATAL_ERR;
446         }
447         
448         switch (context->type) {
449         case PNM_FORMAT_PBM_RAW:
450                 dest = context->dptr + offset;          
451                 memcpy (dest, inbuf->byte, numbytes);
452                 break;
453         case PNM_FORMAT_PGM_RAW:
454         case PNM_FORMAT_PPM_RAW:
455                 dest = context->dptr + offset;
456                 
457                 if (context->maxval == 255) {
458                         /* special-case optimization */
459                         memcpy (dest, inbuf->byte, numbytes);
460                 } else {
461                         for (i = 0; i < numbytes; i++) {
462                                 guchar *byte = inbuf->byte + i;
463                                 
464                                 /* scale the color to an 8-bit color depth */
465                                 if (*byte > context->maxval)
466                                         *dest++ = 255;
467                                 else
468                                         *dest++ = (guchar) (255 * *byte / context->maxval);
469                         }
470                 }
471                 break;
472         default:
473                 g_set_error (context->error,
474                              GDK_PIXBUF_ERROR,
475                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
476                              _("Raw PNM image type is invalid"));
477                 return PNM_FATAL_ERR;
478         }
479         
480         inbuf->byte += numbytes;
481         inbuf->nbytes -= numbytes;
482         
483         context->output_col += numpix;
484         if (context->output_col == context->width) {
485                 if (context->type == PNM_FORMAT_PBM_RAW)
486                         explode_bitmap_into_buf (context);
487                 else if (context->type == PNM_FORMAT_PGM_RAW)
488                         explode_gray_into_buf (context);
489                 
490                 context->output_col = 0;
491                 context->output_row++;
492         } else {
493                 return PNM_SUSPEND;
494         }
495         
496         return PNM_OK;
497 }
498
499 static gint
500 pnm_read_ascii_scanline (PnmLoaderContext *context)
501 {
502         PnmIOBuffer *inbuf;
503         guint offset;
504         guint value, numval, i;
505         guchar data;
506         guchar mask;
507         guchar *dptr;
508         gint retval;
509         
510         g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
511         
512         data = mask = 0;
513         
514         inbuf = &context->inbuf;
515         
516         context->dptr = context->pixels + context->output_row * context->rowstride;
517         
518         switch (context->type) {
519         case PNM_FORMAT_PBM:
520                 numval = MIN (8, context->width - context->output_col);
521                 offset = context->output_col / 8;
522                 break;
523         case PNM_FORMAT_PGM:
524                 numval = 1;
525                 offset = context->output_col;
526                 break;
527         case PNM_FORMAT_PPM:
528                 numval = 3;
529                 offset = context->output_col * 3;
530                 break;
531                 
532         default:
533                 g_set_error (context->error,
534                              GDK_PIXBUF_ERROR,
535                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
536                              _("PNM image format is invalid"));
537
538                 return PNM_FATAL_ERR;
539         }
540         
541         dptr = context->dptr + offset + context->scan_state;
542         
543         while (TRUE) {
544                 if (context->type == PNM_FORMAT_PBM) {
545                         mask = 0x80;
546                         data = 0;
547                         numval = MIN (8, context->width - context->output_col);
548                 }
549                 
550                 for (i = context->scan_state; i < numval; i++) {
551                         retval = pnm_read_next_value (inbuf, &value,
552                                                       context->error);
553                         if (retval != PNM_OK) {
554                                 /* save state and return */
555                                 context->scan_state = i;
556                                 return retval;
557                         }
558                         
559                         switch (context->type) {
560                         case PNM_FORMAT_PBM:
561                                 if (value)
562                                         data |= mask;
563                                 mask >>= 1;
564                                 
565                                 break;
566                         case PNM_FORMAT_PGM:
567                         case PNM_FORMAT_PPM:
568                                 /* scale the color to an 8-bit color depth */
569                                 if (value > context->maxval)
570                                         *dptr++ = 255;
571                                 else
572                                         *dptr++ = (guchar)(255 * value / context->maxval);
573                                 break;
574                         default:
575                                 g_set_error (context->error,
576                                              GDK_PIXBUF_ERROR,
577                                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
578                                              _("PNM image format is invalid"));
579                                 return PNM_FATAL_ERR;
580                                 break;
581                         }
582                 }
583                 
584                 context->scan_state = 0;
585                 
586                 if (context->type == PNM_FORMAT_PBM) {
587                         *dptr++ = data;
588                         context->output_col += numval;
589                 } else {
590                         context->output_col++;
591                 }
592                 
593                 if (context->output_col == context->width) {
594                         if (context->type == PNM_FORMAT_PBM)
595                                 explode_bitmap_into_buf (context);
596                         else if (context->type == PNM_FORMAT_PGM)
597                                 explode_gray_into_buf (context);
598                         
599                         context->output_col = 0;
600                         context->output_row++;
601                         break;
602                 }
603         }
604         
605         return PNM_OK;
606 }
607
608 /* returns 1 if a scanline was converted, 0 means we ran out of data */
609 static gint
610 pnm_read_scanline (PnmLoaderContext *context)
611 {
612         gint retval;
613         
614         g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
615         
616         /* read in image data */
617         /* for raw formats this is trivial */
618         switch (context->type) {
619         case PNM_FORMAT_PBM_RAW:
620         case PNM_FORMAT_PGM_RAW:
621         case PNM_FORMAT_PPM_RAW:
622                 retval = pnm_read_raw_scanline (context);
623                 if (retval != PNM_OK)
624                         return retval;
625                 break;
626         case PNM_FORMAT_PBM:
627         case PNM_FORMAT_PGM:
628         case PNM_FORMAT_PPM:
629                 retval = pnm_read_ascii_scanline (context);
630                 if (retval != PNM_OK)
631                         return retval;
632                 break;
633         default:
634                 g_set_error (context->error,
635                              GDK_PIXBUF_ERROR,
636                              GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
637                              _("PNM image loader does not support this PNM subformat"));
638
639                 return PNM_FATAL_ERR;
640         }
641         
642         return PNM_OK;
643 }
644
645 /* Shared library entry point */
646 static GdkPixbuf *
647 gdk_pixbuf__pnm_image_load (FILE *f, GError **error)
648 {
649         PnmLoaderContext context;
650         PnmIOBuffer *inbuf;
651         gint nbytes;
652         gint retval;
653         
654         /* pretend to be doing progressive loading */
655         context.updated_func = NULL;
656         context.prepared_func = NULL;
657         context.user_data = NULL;
658         context.type = 0;
659         context.inbuf.nbytes = 0;
660         context.inbuf.byte = NULL;
661         context.width = 0;
662         context.height = 0;
663         context.maxval = 0;
664         context.pixels = NULL;
665         context.pixbuf = NULL;
666         context.got_header = FALSE;
667         context.did_prescan = FALSE;
668         context.scan_state = 0;
669         context.error = error;
670         
671         inbuf = &context.inbuf;
672         
673         while (!feof (f)) {
674                 guint num_to_read;
675                 
676                 /* keep buffer as full as possible */
677                 num_to_read = PNM_BUF_SIZE - inbuf->nbytes;
678                 
679                 if (inbuf->byte != NULL && inbuf->nbytes > 0)
680                         memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
681                 
682                 nbytes = fread (inbuf->buffer + inbuf->nbytes, 1, num_to_read, f);
683                 
684                 /* error checking */
685                 if (nbytes == 0 && ferror (f)) {
686                         /* we ran out of data? */
687                         if (context.pixbuf)
688                                 gdk_pixbuf_unref (context.pixbuf);
689                         g_warning ("io-pnm.c: Ran out of data.\n");
690                         return NULL;
691                 }
692                 
693                 inbuf->nbytes += nbytes;
694                 inbuf->byte = inbuf->buffer;
695                 
696                 /* get header if needed */
697                 if (!context.got_header) {
698                         retval = pnm_read_header (&context);
699                         if (retval == PNM_FATAL_ERR)
700                                 return NULL;
701                         else if (retval == PNM_SUSPEND)
702                                 continue;
703                         
704                         context.got_header = TRUE;
705                 }
706                 
707                 /* scan until we hit image data */
708                 if (!context.did_prescan) {
709                         retval = pnm_skip_whitespace (inbuf,
710                                                       context.error);
711                         if (retval == PNM_FATAL_ERR)
712                                 return NULL;
713                         else if (retval == PNM_SUSPEND)
714                                 continue;
715                         
716                         context.did_prescan = TRUE;
717                         context.output_row = 0;
718                         context.output_col = 0;
719                         
720                         context.rowstride = context.width * 3;
721                         context.pixels = g_malloc (context.height * context.width * 3);
722                         
723                         if (!context.pixels) {
724                                 /* Failed to allocate memory */
725                                 g_warning ("Couldn't allocate pixel buf");
726                         }
727                 }
728                 
729                 /* if we got here we're reading image data */
730                 while (context.output_row < context.height) {
731                         retval = pnm_read_scanline (&context);
732                         
733                         if (retval == PNM_SUSPEND) {
734                                 break;
735                         } else if (retval == PNM_FATAL_ERR) {
736                                 if (context.pixbuf)
737                                         gdk_pixbuf_unref (context.pixbuf);
738
739                                 return NULL;
740                         }
741                 }
742                 
743                 if (context.output_row < context.height)
744                         continue;
745                 else
746                         break;
747         }
748         
749         return gdk_pixbuf_new_from_data (context.pixels, GDK_COLORSPACE_RGB, FALSE, 8,
750                                          context.width, context.height, 
751                                          context.width * 3, free_buffer, NULL);
752
753 }
754
755 /* 
756  * func - called when we have pixmap created (but no image data)
757  * user_data - passed as arg 1 to func
758  * return context (opaque to user)
759  */
760
761 static gpointer
762 gdk_pixbuf__pnm_image_begin_load (ModulePreparedNotifyFunc prepared_func, 
763                                   ModuleUpdatedNotifyFunc  updated_func,
764                                   gpointer user_data,
765                                   GError **error)
766 {
767         PnmLoaderContext *context;
768         
769         context = g_new0 (PnmLoaderContext, 1);
770         context->prepared_func = prepared_func;
771         context->updated_func  = updated_func;
772         context->user_data = user_data;
773         context->width = 0;
774         context->height = 0;
775         context->maxval = 0;
776         context->pixbuf = NULL;
777         context->pixels = NULL;
778         context->got_header = FALSE;
779         context->did_prescan = FALSE;
780         context->scan_state = 0;
781         
782         context->inbuf.nbytes = 0;
783         context->inbuf.byte  = NULL;
784
785         context->error = error;
786         
787         return (gpointer) context;
788 }
789
790 /*
791  * context - returned from image_begin_load
792  *
793  * free context, unref gdk_pixbuf
794  */
795 static gboolean
796 gdk_pixbuf__pnm_image_stop_load (gpointer data,
797                                  GError **error)
798 {
799         PnmLoaderContext *context = (PnmLoaderContext *) data;
800         gboolean retval = TRUE;
801         
802         g_return_val_if_fail (context != NULL, TRUE);
803         
804         if (context->pixbuf)
805                 gdk_pixbuf_unref (context->pixbuf);
806
807         if (context->inbuf.nbytes > 0) {
808                 g_set_error (error,
809                              GDK_PIXBUF_ERROR,
810                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
811                              _("Unexpected end of PNM image data"));
812                 retval = FALSE;
813         }
814         
815         g_free (context);
816
817         return retval;
818 }
819
820 /*
821  * context - from image_begin_load
822  * buf - new image data
823  * size - length of new image data
824  *
825  * append image data onto inrecrementally built output image
826  */
827 static gboolean
828 gdk_pixbuf__pnm_image_load_increment (gpointer data,
829                                       const guchar *buf, guint size,
830                                       GError **error)
831 {
832         PnmLoaderContext *context = (PnmLoaderContext *)data;
833         PnmIOBuffer *inbuf;
834         guchar *old_byte;
835         guint old_nbytes;
836         guchar *bufhd;
837         guint num_left, spinguard;
838         gint retval;
839         
840         g_return_val_if_fail (context != NULL, FALSE);
841         g_return_val_if_fail (buf != NULL, FALSE);
842
843         context->error = error;
844         
845         bufhd = buf;
846         inbuf = &context->inbuf;
847         old_nbytes = inbuf->nbytes;
848         old_byte  = inbuf->byte;
849         
850         num_left = size;
851         spinguard = 0;
852         while (TRUE) {
853                 guint num_to_copy;
854                 
855                 /* keep buffer as full as possible */
856                 num_to_copy = MIN (PNM_BUF_SIZE - inbuf->nbytes, num_left);
857                 
858                 if (num_to_copy == 0)
859                         spinguard++;
860                 
861                 if (spinguard > 1)
862                         return TRUE;
863                 
864                 if (inbuf->byte != NULL && inbuf->nbytes > 0)
865                         memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
866                 
867                 memcpy (inbuf->buffer + inbuf->nbytes, bufhd, num_to_copy);
868                 bufhd += num_to_copy;
869                 inbuf->nbytes += num_to_copy;
870                 inbuf->byte = inbuf->buffer;
871                 num_left -= num_to_copy;
872                 
873                 /* ran out of data and we haven't exited main loop */
874                 if (inbuf->nbytes == 0)
875                         return TRUE;
876                 
877                 /* get header if needed */
878                 if (!context->got_header) {
879                         retval = pnm_read_header (context);
880                         
881                         if (retval == PNM_FATAL_ERR)
882                                 return FALSE;
883                         else if (retval == PNM_SUSPEND)
884                                 continue;
885                         
886                         context->got_header = TRUE;
887                 }
888                 
889                 /* scan until we hit image data */
890                 if (!context->did_prescan) {
891                         retval = pnm_skip_whitespace (inbuf,
892                                                       context->error);
893                         
894                         if (retval == PNM_FATAL_ERR)
895                                 return FALSE;
896                         else if (retval == PNM_SUSPEND)
897                                 continue;
898                                                 
899                         context->did_prescan = TRUE;
900                         context->output_row = 0;
901                         context->output_col = 0;
902                         
903                         context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, 
904                                                           FALSE,
905                                                           8, 
906                                                           context->width,
907                                                           context->height);
908                         
909                         if (context->pixbuf == NULL) {
910                                 g_set_error (error,
911                                              GDK_PIXBUF_ERROR,
912                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
913                                              _("Insufficient memory to load PNM file"));
914                                 return FALSE;
915                         }
916                         
917                         context->pixels = context->pixbuf->pixels;
918                         context->rowstride = context->pixbuf->rowstride;
919                         
920                         /* Notify the client that we are ready to go */
921                         (* context->prepared_func) (context->pixbuf,
922                                                     NULL,
923                                                     context->user_data);
924                 }
925                 
926                 /* if we got here we're reading image data */
927                 while (context->output_row < context->height) {
928                         retval = pnm_read_scanline (context);
929                         
930                         if (retval == PNM_SUSPEND) {
931                                 break;
932                         } else if (retval == PNM_FATAL_ERR) {
933                                 if (context->pixbuf)
934                                         gdk_pixbuf_unref (context->pixbuf);
935                                 return FALSE;
936                         } else if (retval == PNM_OK) {  
937                                 /* send updated signal */
938                                 (* context->updated_func) (context->pixbuf,
939                                                            0, 
940                                                            context->output_row-1,
941                                                            context->width, 
942                                                            1,
943                                                            context->user_data);
944                         }
945                 }
946                 
947                 if (context->output_row < context->height)
948                         continue;
949                 else
950                         break;
951         }
952         
953         return TRUE;
954 }
955
956 void
957 gdk_pixbuf__pnm_fill_vtable (GdkPixbufModule *module)
958 {
959   module->load = gdk_pixbuf__pnm_image_load;
960   module->begin_load = gdk_pixbuf__pnm_image_begin_load;
961   module->stop_load = gdk_pixbuf__pnm_image_stop_load;
962   module->load_increment = gdk_pixbuf__pnm_image_load_increment;
963 }