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