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