1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* GdkPixbuf library - PNM image loader
4 * Copyright (C) 1999 Red Hat, Inc.
6 * Authors: Jeffrey Stedfast <fejj@helixcode.com>
7 * Michael Fulbright <drmike@redhat.com>
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.
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.
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.
31 #include "gdk-pixbuf-private.h"
32 #include "gdk-pixbuf-io.h"
35 #define PNM_BUF_SIZE 4096
37 #define PNM_FATAL_ERR -1
51 guchar buffer[PNM_BUF_SIZE];
57 ModuleUpdatedNotifyFunc updated_func;
58 ModulePreparedNotifyFunc prepared_func;
62 guchar *pixels; /* incoming pixel data buffer */
63 guchar *dptr; /* current position in pixbuf */
73 guint output_row; /* last row to be completed */
75 gboolean did_prescan; /* are we in image data yet? */
76 gboolean got_header; /* have we loaded pnm header? */
84 static GdkPixbuf *gdk_pixbuf__pnm_image_load (FILE *f, GError **error);
85 static gpointer gdk_pixbuf__pnm_image_begin_load (ModulePreparedNotifyFunc func,
86 ModuleUpdatedNotifyFunc func2,
89 static gboolean gdk_pixbuf__pnm_image_stop_load (gpointer context, GError **error);
90 static gboolean gdk_pixbuf__pnm_image_load_increment (gpointer context,
91 const guchar *buf, guint size,
94 static void explode_bitmap_into_buf (PnmLoaderContext *context);
95 static void explode_gray_into_buf (PnmLoaderContext *context);
97 /* Destroy notification function for the pixbuf */
99 free_buffer (guchar *pixels, gpointer data)
105 /* explode bitmap data into rgb components */
106 /* we need to know what the row so we can */
107 /* do sub-byte expansion (since 1 byte = 8 pixels) */
108 /* context->dptr MUST point at first byte in incoming data */
109 /* which corresponds to first pixel of row y */
111 explode_bitmap_into_buf (PnmLoaderContext *context)
114 guchar *from, *to, data;
119 g_return_if_fail (context != NULL);
120 g_return_if_fail (context->dptr != NULL);
122 /* I'm no clever bit-hacker so I'm sure this can be optimized */
123 dptr = context->dptr;
124 y = context->output_row;
125 wid = context->width;
127 from = dptr + ((wid - 1) / 8);
128 to = dptr + (wid - 1) * 3;
129 /* bit = 7 - (((y+1)*wid-1) % 8); */
130 bit = 7 - ((wid-1) % 8);
132 /* get first byte and align properly */
134 for (j = 0; j < bit; j++, data >>= 1);
136 for (x = wid-1; x >= 0; x--) {
137 /* g_print ("%c", (data & 1) ? '*' : ' '); */
139 to[0] = to[1] = to[2] = (data & 0x01) ? 0x00 : 0xff;
153 /* g_print ("\n"); */
156 /* explode gray image row into rgb components in pixbuf */
158 explode_gray_into_buf (PnmLoaderContext *context)
164 g_return_if_fail (context != NULL);
165 g_return_if_fail (context->dptr != NULL);
167 /* Expand grey->colour. Expand from the end of the
168 * memory down, so we can use the same buffer.
171 from = context->dptr + w - 1;
172 to = context->dptr + (w - 1) * 3;
173 for (j = w - 1; j >= 0; j--) {
182 /* skip over whitespace and comments in input buffer */
184 pnm_skip_whitespace (PnmIOBuffer *inbuf, GError **error)
186 register guchar *inptr;
189 g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR);
190 g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR);
192 inend = inbuf->byte + inbuf->nbytes;
195 for ( ; inptr < inend; inptr++) {
197 /* in comment - skip to the end of this line */
198 for ( ; *inptr != '\n' && inptr < inend; inptr++);
199 } else if (!isspace (*inptr)) {
201 inbuf->nbytes = (guint) (inend - inptr);
207 inbuf->nbytes = (guint) (inend - inptr);
212 /* read next number from buffer */
214 pnm_read_next_value (PnmIOBuffer *inbuf, guint *value, GError **error)
216 register guchar *inptr, *word, *p;
217 guchar *inend, buf[128];
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);
225 /* skip white space */
226 if ((retval = pnm_skip_whitespace (inbuf, error)) != PNM_OK)
229 inend = inbuf->byte + inbuf->nbytes;
232 /* copy this pnm 'word' into a temp buffer */
233 for (p = inptr, word = buf; (p < inend) && !isspace (*p) && (p - inptr < 128); p++, word++)
237 /* hmmm, there must be more data to this 'word' */
242 *value = strtol (buf, &endptr, 10);
243 if (*endptr != '\0') {
246 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
247 _("PNM loader expected to find an integer, but didn't"));
248 return PNM_FATAL_ERR;
252 inbuf->nbytes = (guint) (inend - p);
257 /* returns PNM_OK, PNM_SUSPEND, or PNM_FATAL_ERR */
259 pnm_read_header (PnmLoaderContext *context)
264 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
266 inbuf = &context->inbuf;
268 if (!context->type) {
269 /* file must start with a 'P' followed by a numeral */
270 /* so loop till we get enough data to determine type */
271 if (inbuf->nbytes < 2)
274 if (*inbuf->byte != 'P') {
275 g_set_error (context->error,
277 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
278 _("PNM file has an incorrect initial byte"));
279 return PNM_FATAL_ERR;
285 switch (*inbuf->byte) {
287 context->type = PNM_FORMAT_PBM;
290 context->type = PNM_FORMAT_PGM;
293 context->type = PNM_FORMAT_PPM;
296 context->type = PNM_FORMAT_PBM_RAW;
299 context->type = PNM_FORMAT_PGM_RAW;
302 context->type = PNM_FORMAT_PPM_RAW;
305 g_set_error (context->error,
307 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
308 _("PNM file is not in a recognized PNM subformat"));
309 return PNM_FATAL_ERR;
319 if (!context->width) {
320 /* read the pixmap width */
323 retval = pnm_read_next_value (inbuf, &width,
326 if (retval != PNM_OK)
330 g_set_error (context->error,
332 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
333 _("PNM file has an image width of 0"));
334 return PNM_FATAL_ERR;
337 context->width = width;
340 if (!context->height) {
341 /* read the pixmap height */
344 retval = pnm_read_next_value (inbuf, &height,
347 if (retval != PNM_OK)
351 g_set_error (context->error,
353 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
354 _("PNM file has an image height of 0"));
355 return PNM_FATAL_ERR;
358 context->height = height;
361 switch (context->type) {
363 case PNM_FORMAT_PPM_RAW:
365 case PNM_FORMAT_PGM_RAW:
366 if (!context->maxval) {
367 retval = pnm_read_next_value (inbuf, &context->maxval,
370 if (retval != PNM_OK)
373 if (context->maxval == 0) {
374 g_set_error (context->error,
376 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
377 _("Maximum color value in PNM file is 0"));
378 return PNM_FATAL_ERR;
390 pnm_read_raw_scanline (PnmLoaderContext *context)
393 guint numbytes, offset;
398 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
400 inbuf = &context->inbuf;
402 switch (context->type) {
403 case PNM_FORMAT_PBM_RAW:
404 numpix = inbuf->nbytes * 8;
406 case PNM_FORMAT_PGM_RAW:
407 numpix = inbuf->nbytes;
409 case PNM_FORMAT_PPM_RAW:
410 numpix = inbuf->nbytes / 3;
413 g_set_error (context->error,
415 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
416 _("Raw PNM image type is invalid"));
417 return PNM_FATAL_ERR;
420 numpix = MIN (numpix, context->width - context->output_col);
425 context->dptr = context->pixels + context->output_row * context->rowstride;
427 switch (context->type) {
428 case PNM_FORMAT_PBM_RAW:
429 numbytes = (numpix / 8) + ((numpix % 8) ? 1 : 0);
430 offset = context->output_col / 8;
432 case PNM_FORMAT_PGM_RAW:
434 offset = context->output_col;
436 case PNM_FORMAT_PPM_RAW:
437 numbytes = numpix * 3;
438 offset = context->output_col * 3;
441 g_set_error (context->error,
443 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
444 _("Raw PNM image type is invalid"));
445 return PNM_FATAL_ERR;
448 switch (context->type) {
449 case PNM_FORMAT_PBM_RAW:
450 dest = context->dptr + offset;
451 memcpy (dest, inbuf->byte, numbytes);
453 case PNM_FORMAT_PGM_RAW:
454 case PNM_FORMAT_PPM_RAW:
455 dest = context->dptr + offset;
457 if (context->maxval == 255) {
458 /* special-case optimization */
459 memcpy (dest, inbuf->byte, numbytes);
461 for (i = 0; i < numbytes; i++) {
462 guchar *byte = inbuf->byte + i;
464 /* scale the color to an 8-bit color depth */
465 if (*byte > context->maxval)
468 *dest++ = (guchar) (255 * *byte / context->maxval);
473 g_set_error (context->error,
475 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
476 _("Raw PNM image type is invalid"));
477 return PNM_FATAL_ERR;
480 inbuf->byte += numbytes;
481 inbuf->nbytes -= numbytes;
483 context->output_col += numpix;
484 if (context->output_col == context->width) {
485 if (context->type == PNM_FORMAT_PBM_RAW)
486 explode_bitmap_into_buf (context);
487 else if (context->type == PNM_FORMAT_PGM_RAW)
488 explode_gray_into_buf (context);
490 context->output_col = 0;
491 context->output_row++;
500 pnm_read_ascii_scanline (PnmLoaderContext *context)
504 guint value, numval, i;
510 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
514 inbuf = &context->inbuf;
516 context->dptr = context->pixels + context->output_row * context->rowstride;
518 switch (context->type) {
520 numval = MIN (8, context->width - context->output_col);
521 offset = context->output_col / 8;
525 offset = context->output_col;
529 offset = context->output_col * 3;
533 g_set_error (context->error,
535 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
536 _("PNM image format is invalid"));
538 return PNM_FATAL_ERR;
541 dptr = context->dptr + offset + context->scan_state;
544 if (context->type == PNM_FORMAT_PBM) {
547 numval = MIN (8, context->width - context->output_col);
550 for (i = context->scan_state; i < numval; i++) {
551 retval = pnm_read_next_value (inbuf, &value,
553 if (retval != PNM_OK) {
554 /* save state and return */
555 context->scan_state = i;
559 switch (context->type) {
568 /* scale the color to an 8-bit color depth */
569 if (value > context->maxval)
572 *dptr++ = (guchar)(255 * value / context->maxval);
575 g_set_error (context->error,
577 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
578 _("PNM image format is invalid"));
579 return PNM_FATAL_ERR;
584 context->scan_state = 0;
586 if (context->type == PNM_FORMAT_PBM) {
588 context->output_col += numval;
590 context->output_col++;
593 if (context->output_col == context->width) {
594 if (context->type == PNM_FORMAT_PBM)
595 explode_bitmap_into_buf (context);
596 else if (context->type == PNM_FORMAT_PGM)
597 explode_gray_into_buf (context);
599 context->output_col = 0;
600 context->output_row++;
608 /* returns 1 if a scanline was converted, 0 means we ran out of data */
610 pnm_read_scanline (PnmLoaderContext *context)
614 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
616 /* read in image data */
617 /* for raw formats this is trivial */
618 switch (context->type) {
619 case PNM_FORMAT_PBM_RAW:
620 case PNM_FORMAT_PGM_RAW:
621 case PNM_FORMAT_PPM_RAW:
622 retval = pnm_read_raw_scanline (context);
623 if (retval != PNM_OK)
629 retval = pnm_read_ascii_scanline (context);
630 if (retval != PNM_OK)
634 g_set_error (context->error,
636 GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
637 _("PNM image loader does not support this PNM subformat"));
639 return PNM_FATAL_ERR;
645 /* Shared library entry point */
647 gdk_pixbuf__pnm_image_load (FILE *f, GError **error)
649 PnmLoaderContext context;
654 /* pretend to be doing progressive loading */
655 context.updated_func = NULL;
656 context.prepared_func = NULL;
657 context.user_data = NULL;
659 context.inbuf.nbytes = 0;
660 context.inbuf.byte = NULL;
664 context.pixels = NULL;
665 context.pixbuf = NULL;
666 context.got_header = FALSE;
667 context.did_prescan = FALSE;
668 context.scan_state = 0;
669 context.error = error;
671 inbuf = &context.inbuf;
676 /* keep buffer as full as possible */
677 num_to_read = PNM_BUF_SIZE - inbuf->nbytes;
679 if (inbuf->byte != NULL && inbuf->nbytes > 0)
680 memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
682 nbytes = fread (inbuf->buffer + inbuf->nbytes, 1, num_to_read, f);
685 if (nbytes == 0 && ferror (f)) {
686 /* we ran out of data? */
688 gdk_pixbuf_unref (context.pixbuf);
689 g_warning ("io-pnm.c: Ran out of data.\n");
693 inbuf->nbytes += nbytes;
694 inbuf->byte = inbuf->buffer;
696 /* get header if needed */
697 if (!context.got_header) {
698 retval = pnm_read_header (&context);
699 if (retval == PNM_FATAL_ERR)
701 else if (retval == PNM_SUSPEND)
704 context.got_header = TRUE;
707 /* scan until we hit image data */
708 if (!context.did_prescan) {
709 retval = pnm_skip_whitespace (inbuf,
711 if (retval == PNM_FATAL_ERR)
713 else if (retval == PNM_SUSPEND)
716 context.did_prescan = TRUE;
717 context.output_row = 0;
718 context.output_col = 0;
720 context.rowstride = context.width * 3;
721 context.pixels = g_malloc (context.height * context.width * 3);
723 if (!context.pixels) {
724 /* Failed to allocate memory */
725 g_warning ("Couldn't allocate pixel buf");
729 /* if we got here we're reading image data */
730 while (context.output_row < context.height) {
731 retval = pnm_read_scanline (&context);
733 if (retval == PNM_SUSPEND) {
735 } else if (retval == PNM_FATAL_ERR) {
737 gdk_pixbuf_unref (context.pixbuf);
743 if (context.output_row < context.height)
749 return gdk_pixbuf_new_from_data (context.pixels, GDK_COLORSPACE_RGB, FALSE, 8,
750 context.width, context.height,
751 context.width * 3, free_buffer, NULL);
756 * func - called when we have pixmap created (but no image data)
757 * user_data - passed as arg 1 to func
758 * return context (opaque to user)
762 gdk_pixbuf__pnm_image_begin_load (ModulePreparedNotifyFunc prepared_func,
763 ModuleUpdatedNotifyFunc updated_func,
767 PnmLoaderContext *context;
769 context = g_new0 (PnmLoaderContext, 1);
770 context->prepared_func = prepared_func;
771 context->updated_func = updated_func;
772 context->user_data = user_data;
776 context->pixbuf = NULL;
777 context->pixels = NULL;
778 context->got_header = FALSE;
779 context->did_prescan = FALSE;
780 context->scan_state = 0;
782 context->inbuf.nbytes = 0;
783 context->inbuf.byte = NULL;
785 context->error = error;
787 return (gpointer) context;
791 * context - returned from image_begin_load
793 * free context, unref gdk_pixbuf
796 gdk_pixbuf__pnm_image_stop_load (gpointer data,
799 PnmLoaderContext *context = (PnmLoaderContext *) data;
800 gboolean retval = TRUE;
802 g_return_val_if_fail (context != NULL, TRUE);
805 gdk_pixbuf_unref (context->pixbuf);
807 if (context->inbuf.nbytes > 0) {
810 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
811 _("Unexpected end of PNM image data"));
821 * context - from image_begin_load
822 * buf - new image data
823 * size - length of new image data
825 * append image data onto inrecrementally built output image
828 gdk_pixbuf__pnm_image_load_increment (gpointer data,
829 const guchar *buf, guint size,
832 PnmLoaderContext *context = (PnmLoaderContext *)data;
837 guint num_left, spinguard;
840 g_return_val_if_fail (context != NULL, FALSE);
841 g_return_val_if_fail (buf != NULL, FALSE);
843 context->error = error;
846 inbuf = &context->inbuf;
847 old_nbytes = inbuf->nbytes;
848 old_byte = inbuf->byte;
855 /* keep buffer as full as possible */
856 num_to_copy = MIN (PNM_BUF_SIZE - inbuf->nbytes, num_left);
858 if (num_to_copy == 0)
864 if (inbuf->byte != NULL && inbuf->nbytes > 0)
865 memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
867 memcpy (inbuf->buffer + inbuf->nbytes, bufhd, num_to_copy);
868 bufhd += num_to_copy;
869 inbuf->nbytes += num_to_copy;
870 inbuf->byte = inbuf->buffer;
871 num_left -= num_to_copy;
873 /* ran out of data and we haven't exited main loop */
874 if (inbuf->nbytes == 0)
877 /* get header if needed */
878 if (!context->got_header) {
879 retval = pnm_read_header (context);
881 if (retval == PNM_FATAL_ERR)
883 else if (retval == PNM_SUSPEND)
886 context->got_header = TRUE;
889 /* scan until we hit image data */
890 if (!context->did_prescan) {
891 retval = pnm_skip_whitespace (inbuf,
894 if (retval == PNM_FATAL_ERR)
896 else if (retval == PNM_SUSPEND)
899 context->did_prescan = TRUE;
900 context->output_row = 0;
901 context->output_col = 0;
903 context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
909 if (context->pixbuf == NULL) {
912 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
913 _("Insufficient memory to load PNM file"));
917 context->pixels = context->pixbuf->pixels;
918 context->rowstride = context->pixbuf->rowstride;
920 /* Notify the client that we are ready to go */
921 (* context->prepared_func) (context->pixbuf,
926 /* if we got here we're reading image data */
927 while (context->output_row < context->height) {
928 retval = pnm_read_scanline (context);
930 if (retval == PNM_SUSPEND) {
932 } else if (retval == PNM_FATAL_ERR) {
934 gdk_pixbuf_unref (context->pixbuf);
936 } else if (retval == PNM_OK) {
937 /* send updated signal */
938 (* context->updated_func) (context->pixbuf,
940 context->output_row-1,
947 if (context->output_row < context->height)
957 gdk_pixbuf__pnm_fill_vtable (GdkPixbufModule *module)
959 module->load = gdk_pixbuf__pnm_image_load;
960 module->begin_load = gdk_pixbuf__pnm_image_begin_load;
961 module->stop_load = gdk_pixbuf__pnm_image_stop_load;
962 module->load_increment = gdk_pixbuf__pnm_image_load_increment;