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