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.
30 #include "gdk-pixbuf-private.h"
31 #include "gdk-pixbuf-io.h"
34 #define PNM_BUF_SIZE 4096
36 #define PNM_FATAL_ERR -1
50 guchar buffer[PNM_BUF_SIZE];
56 ModuleUpdatedNotifyFunc updated_func;
57 ModulePreparedNotifyFunc prepared_func;
61 guchar *pixels; /* incoming pixel data buffer */
62 guchar *dptr; /* current position in pixbuf */
72 guint output_row; /* last row to be completed */
74 gboolean did_prescan; /* are we in image data yet? */
75 gboolean got_header; /* have we loaded pnm header? */
83 static GdkPixbuf *gdk_pixbuf__pnm_image_load (FILE *f, GError **error);
84 static gpointer gdk_pixbuf__pnm_image_begin_load (ModulePreparedNotifyFunc func,
85 ModuleUpdatedNotifyFunc func2,
88 static gboolean gdk_pixbuf__pnm_image_stop_load (gpointer context, GError **error);
89 static gboolean gdk_pixbuf__pnm_image_load_increment (gpointer context,
90 const guchar *buf, guint size,
93 static void explode_bitmap_into_buf (PnmLoaderContext *context);
94 static void explode_gray_into_buf (PnmLoaderContext *context);
96 /* Destroy notification function for the pixbuf */
98 free_buffer (guchar *pixels, gpointer data)
104 /* explode bitmap data into rgb components */
105 /* we need to know what the row so we can */
106 /* do sub-byte expansion (since 1 byte = 8 pixels) */
107 /* context->dptr MUST point at first byte in incoming data */
108 /* which corresponds to first pixel of row y */
110 explode_bitmap_into_buf (PnmLoaderContext *context)
113 guchar *from, *to, data;
118 g_return_if_fail (context != NULL);
119 g_return_if_fail (context->dptr != NULL);
121 /* I'm no clever bit-hacker so I'm sure this can be optimized */
122 dptr = context->dptr;
123 y = context->output_row;
124 wid = context->width;
126 from = dptr + ((wid - 1) / 8);
127 to = dptr + (wid - 1) * 3;
128 /* bit = 7 - (((y+1)*wid-1) % 8); */
129 bit = 7 - ((wid-1) % 8);
131 /* get first byte and align properly */
133 for (j = 0; j < bit; j++, data >>= 1);
135 for (x = wid-1; x >= 0; x--) {
136 /* g_print ("%c", (data & 1) ? '*' : ' '); */
138 to[0] = to[1] = to[2] = (data & 0x01) ? 0x00 : 0xff;
152 /* g_print ("\n"); */
155 /* explode gray image row into rgb components in pixbuf */
157 explode_gray_into_buf (PnmLoaderContext *context)
163 g_return_if_fail (context != NULL);
164 g_return_if_fail (context->dptr != NULL);
166 /* Expand grey->colour. Expand from the end of the
167 * memory down, so we can use the same buffer.
170 from = context->dptr + w - 1;
171 to = context->dptr + (w - 1) * 3;
172 for (j = w - 1; j >= 0; j--) {
181 /* skip over whitespace and comments in input buffer */
183 pnm_skip_whitespace (PnmIOBuffer *inbuf, GError **error)
185 register guchar *inptr;
188 g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR);
189 g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR);
191 inend = inbuf->byte + inbuf->nbytes;
194 for ( ; inptr < inend; inptr++) {
196 /* in comment - skip to the end of this line */
197 for ( ; *inptr != '\n' && inptr < inend; inptr++)
200 if ( *inptr != '\n' ) {
201 /* couldn't read whole comment */
205 } else if (!g_ascii_isspace (*inptr)) {
207 inbuf->nbytes = (guint) (inend - inptr);
213 inbuf->nbytes = (guint) (inend - inptr);
218 /* read next number from buffer */
220 pnm_read_next_value (PnmIOBuffer *inbuf, guint *value, GError **error)
222 register guchar *inptr, *word, *p;
223 guchar *inend, buf[129];
228 g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR);
229 g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR);
230 g_return_val_if_fail (value != NULL, PNM_FATAL_ERR);
232 /* skip white space */
233 if ((retval = pnm_skip_whitespace (inbuf, error)) != PNM_OK)
236 inend = inbuf->byte + inbuf->nbytes;
239 /* copy this pnm 'word' into a temp buffer */
240 for (p = inptr, word = buf; (p < inend) && !g_ascii_isspace (*p) && (*p != '#') && (p - inptr < 128); p++, word++)
244 /* hmmm, there must be more data to this 'word' */
245 if (!g_ascii_isspace (*p) && (*p != '#') && (p - inptr < 128))
249 result = strtol (buf, &endptr, 10);
250 if (*endptr != '\0' || result < 0) {
253 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
254 _("PNM loader expected to find an integer, but didn't"));
255 return PNM_FATAL_ERR;
260 inbuf->nbytes = (guint) (inend - p);
265 /* returns PNM_OK, PNM_SUSPEND, or PNM_FATAL_ERR */
267 pnm_read_header (PnmLoaderContext *context)
272 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
274 inbuf = &context->inbuf;
276 if (!context->type) {
277 /* file must start with a 'P' followed by a numeral */
278 /* so loop till we get enough data to determine type */
279 if (inbuf->nbytes < 2)
282 if (*inbuf->byte != 'P') {
283 g_set_error (context->error,
285 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
286 _("PNM file has an incorrect initial byte"));
287 return PNM_FATAL_ERR;
293 switch (*inbuf->byte) {
295 context->type = PNM_FORMAT_PBM;
298 context->type = PNM_FORMAT_PGM;
301 context->type = PNM_FORMAT_PPM;
304 context->type = PNM_FORMAT_PBM_RAW;
307 context->type = PNM_FORMAT_PGM_RAW;
310 context->type = PNM_FORMAT_PPM_RAW;
313 g_set_error (context->error,
315 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
316 _("PNM file is not in a recognized PNM subformat"));
317 return PNM_FATAL_ERR;
327 if (!context->width) {
328 /* read the pixmap width */
331 retval = pnm_read_next_value (inbuf, &width,
334 if (retval != PNM_OK)
338 g_set_error (context->error,
340 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
341 _("PNM file has an image width of 0"));
342 return PNM_FATAL_ERR;
345 context->width = width;
348 if (!context->height) {
349 /* read the pixmap height */
352 retval = pnm_read_next_value (inbuf, &height,
355 if (retval != PNM_OK)
359 g_set_error (context->error,
361 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
362 _("PNM file has an image height of 0"));
363 return PNM_FATAL_ERR;
366 context->height = height;
369 switch (context->type) {
371 case PNM_FORMAT_PPM_RAW:
373 case PNM_FORMAT_PGM_RAW:
374 if (!context->maxval) {
375 retval = pnm_read_next_value (inbuf, &context->maxval,
378 if (retval != PNM_OK)
381 if (context->maxval == 0) {
382 g_set_error (context->error,
384 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
385 _("Maximum color value in PNM file is 0"));
386 return PNM_FATAL_ERR;
389 if (context->maxval > 65535) {
390 g_set_error (context->error,
392 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
393 _("Maximum color value in PNM file is too large"));
394 return PNM_FATAL_ERR;
397 if (context->maxval > 255) {
398 g_set_error (context->error,
400 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
401 _("Can't handle PNM files with maximum color values greater than 255"));
402 return PNM_FATAL_ERR;
414 pnm_read_raw_scanline (PnmLoaderContext *context)
417 guint numbytes, offset;
422 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
424 inbuf = &context->inbuf;
426 switch (context->type) {
427 case PNM_FORMAT_PBM_RAW:
428 numpix = inbuf->nbytes * 8;
430 case PNM_FORMAT_PGM_RAW:
431 numpix = inbuf->nbytes;
433 case PNM_FORMAT_PPM_RAW:
434 numpix = inbuf->nbytes / 3;
437 g_set_error (context->error,
439 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
440 _("Raw PNM image type is invalid"));
441 return PNM_FATAL_ERR;
444 numpix = MIN (numpix, context->width - context->output_col);
449 context->dptr = context->pixels + context->output_row * context->rowstride;
451 switch (context->type) {
452 case PNM_FORMAT_PBM_RAW:
453 numbytes = (numpix / 8) + ((numpix % 8) ? 1 : 0);
454 offset = context->output_col / 8;
456 case PNM_FORMAT_PGM_RAW:
458 offset = context->output_col;
460 case PNM_FORMAT_PPM_RAW:
461 numbytes = numpix * 3;
462 offset = context->output_col * 3;
465 g_set_error (context->error,
467 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
468 _("Raw PNM image type is invalid"));
469 return PNM_FATAL_ERR;
472 switch (context->type) {
473 case PNM_FORMAT_PBM_RAW:
474 dest = context->dptr + offset;
475 memcpy (dest, inbuf->byte, numbytes);
477 case PNM_FORMAT_PGM_RAW:
478 case PNM_FORMAT_PPM_RAW:
479 dest = context->dptr + offset;
481 if (context->maxval == 255) {
482 /* special-case optimization */
483 memcpy (dest, inbuf->byte, numbytes);
485 for (i = 0; i < numbytes; i++) {
486 guchar *byte = inbuf->byte + i;
488 /* scale the color to an 8-bit color depth */
489 if (*byte > context->maxval)
492 *dest++ = (guchar) (255 * *byte / context->maxval);
497 g_set_error (context->error,
499 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
500 _("Raw PNM image type is invalid"));
501 return PNM_FATAL_ERR;
504 inbuf->byte += numbytes;
505 inbuf->nbytes -= numbytes;
507 context->output_col += numpix;
508 if (context->output_col == context->width) {
509 if (context->type == PNM_FORMAT_PBM_RAW)
510 explode_bitmap_into_buf (context);
511 else if (context->type == PNM_FORMAT_PGM_RAW)
512 explode_gray_into_buf (context);
514 context->output_col = 0;
515 context->output_row++;
524 pnm_read_ascii_scanline (PnmLoaderContext *context)
528 guint value, numval, i;
534 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
538 inbuf = &context->inbuf;
540 context->dptr = context->pixels + context->output_row * context->rowstride;
542 switch (context->type) {
544 numval = MIN (8, context->width - context->output_col);
545 offset = context->output_col / 8;
549 offset = context->output_col;
553 offset = context->output_col * 3;
557 g_set_error (context->error,
559 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
560 _("PNM image format is invalid"));
562 return PNM_FATAL_ERR;
565 dptr = context->dptr + offset + context->scan_state;
568 if (context->type == PNM_FORMAT_PBM) {
571 numval = MIN (8, context->width - context->output_col);
574 for (i = context->scan_state; i < numval; i++) {
575 retval = pnm_read_next_value (inbuf, &value,
577 if (retval != PNM_OK) {
578 /* save state and return */
579 context->scan_state = i;
583 switch (context->type) {
592 /* scale the color to an 8-bit color depth */
593 if (value > context->maxval)
596 *dptr++ = (guchar)(255 * value / context->maxval);
599 g_set_error (context->error,
601 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
602 _("PNM image format is invalid"));
603 return PNM_FATAL_ERR;
608 context->scan_state = 0;
610 if (context->type == PNM_FORMAT_PBM) {
612 context->output_col += numval;
614 context->output_col++;
617 if (context->output_col == context->width) {
618 if (context->type == PNM_FORMAT_PBM)
619 explode_bitmap_into_buf (context);
620 else if (context->type == PNM_FORMAT_PGM)
621 explode_gray_into_buf (context);
623 context->output_col = 0;
624 context->output_row++;
632 /* returns 1 if a scanline was converted, 0 means we ran out of data */
634 pnm_read_scanline (PnmLoaderContext *context)
638 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
640 /* read in image data */
641 /* for raw formats this is trivial */
642 switch (context->type) {
643 case PNM_FORMAT_PBM_RAW:
644 case PNM_FORMAT_PGM_RAW:
645 case PNM_FORMAT_PPM_RAW:
646 retval = pnm_read_raw_scanline (context);
647 if (retval != PNM_OK)
653 retval = pnm_read_ascii_scanline (context);
654 if (retval != PNM_OK)
658 g_set_error (context->error,
660 GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
661 _("PNM image loader does not support this PNM subformat"));
663 return PNM_FATAL_ERR;
669 /* Shared library entry point */
671 gdk_pixbuf__pnm_image_load (FILE *f, GError **error)
673 PnmLoaderContext context;
678 /* pretend to be doing progressive loading */
679 context.updated_func = NULL;
680 context.prepared_func = NULL;
681 context.user_data = NULL;
683 context.inbuf.nbytes = 0;
684 context.inbuf.byte = NULL;
688 context.pixels = NULL;
689 context.pixbuf = NULL;
690 context.got_header = FALSE;
691 context.did_prescan = FALSE;
692 context.scan_state = 0;
693 context.error = error;
695 inbuf = &context.inbuf;
700 /* keep buffer as full as possible */
701 num_to_read = PNM_BUF_SIZE - inbuf->nbytes;
703 if (inbuf->byte != NULL && inbuf->nbytes > 0)
704 memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
706 nbytes = fread (inbuf->buffer + inbuf->nbytes, 1, num_to_read, f);
710 /* we ran out of data? */
712 gdk_pixbuf_unref (context.pixbuf);
715 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
716 _("Premature end-of-file encountered"));
720 inbuf->nbytes += nbytes;
721 inbuf->byte = inbuf->buffer;
723 /* get header if needed */
724 if (!context.got_header) {
725 retval = pnm_read_header (&context);
726 if (retval == PNM_FATAL_ERR)
728 else if (retval == PNM_SUSPEND)
731 context.got_header = TRUE;
734 /* scan until we hit image data */
735 if (!context.did_prescan) {
736 switch (context.type) {
737 case PNM_FORMAT_PBM_RAW:
738 case PNM_FORMAT_PGM_RAW:
739 case PNM_FORMAT_PPM_RAW:
740 if (inbuf->nbytes <= 0)
742 /* raw formats require exactly one whitespace */
743 if (!g_ascii_isspace(*(inbuf->byte)))
747 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
748 _("Raw PNM formats require exactly one whitespace before sample data"));
755 retval = pnm_skip_whitespace (inbuf,
757 if (retval == PNM_FATAL_ERR)
759 else if (retval == PNM_SUSPEND)
763 context.did_prescan = TRUE;
764 context.output_row = 0;
765 context.output_col = 0;
767 context.rowstride = context.width * 3;
768 context.pixels = g_try_malloc (context.height * context.width * 3);
770 if (!context.pixels) {
771 /* Failed to allocate memory */
774 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
775 _("Can't allocate memory for loading PNM image"));
780 /* if we got here we're reading image data */
781 while (context.output_row < context.height) {
782 retval = pnm_read_scanline (&context);
784 if (retval == PNM_SUSPEND) {
786 } else if (retval == PNM_FATAL_ERR) {
788 gdk_pixbuf_unref (context.pixbuf);
794 if (context.output_row < context.height)
800 return gdk_pixbuf_new_from_data (context.pixels, GDK_COLORSPACE_RGB, FALSE, 8,
801 context.width, context.height,
802 context.width * 3, free_buffer, NULL);
807 * func - called when we have pixmap created (but no image data)
808 * user_data - passed as arg 1 to func
809 * return context (opaque to user)
813 gdk_pixbuf__pnm_image_begin_load (ModulePreparedNotifyFunc prepared_func,
814 ModuleUpdatedNotifyFunc updated_func,
818 PnmLoaderContext *context;
820 context = g_try_malloc (sizeof (PnmLoaderContext));
822 g_set_error(error, GDK_PIXBUF_ERROR,
823 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
824 _("Insufficient memory to load PNM context struct"));
827 memset (context, 0, sizeof (PnmLoaderContext));
828 context->prepared_func = prepared_func;
829 context->updated_func = updated_func;
830 context->user_data = user_data;
834 context->pixbuf = NULL;
835 context->pixels = NULL;
836 context->got_header = FALSE;
837 context->did_prescan = FALSE;
838 context->scan_state = 0;
840 context->inbuf.nbytes = 0;
841 context->inbuf.byte = NULL;
843 context->error = error;
845 return (gpointer) context;
849 * context - returned from image_begin_load
851 * free context, unref gdk_pixbuf
854 gdk_pixbuf__pnm_image_stop_load (gpointer data,
857 PnmLoaderContext *context = (PnmLoaderContext *) data;
858 gboolean retval = TRUE;
860 g_return_val_if_fail (context != NULL, TRUE);
863 gdk_pixbuf_unref (context->pixbuf);
866 /* We should ignore trailing newlines and we can't
867 generally complain about trailing stuff at all, since
868 pnm allows to put multiple images in a file
870 if (context->inbuf.nbytes > 0) {
873 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
874 _("Unexpected end of PNM image data"));
885 * context - from image_begin_load
886 * buf - new image data
887 * size - length of new image data
889 * append image data onto inrecrementally built output image
892 gdk_pixbuf__pnm_image_load_increment (gpointer data,
893 const guchar *buf, guint size,
896 PnmLoaderContext *context = (PnmLoaderContext *)data;
901 guint num_left, spinguard;
904 g_return_val_if_fail (context != NULL, FALSE);
905 g_return_val_if_fail (buf != NULL, FALSE);
907 context->error = error;
910 inbuf = &context->inbuf;
911 old_nbytes = inbuf->nbytes;
912 old_byte = inbuf->byte;
919 /* keep buffer as full as possible */
920 num_to_copy = MIN (PNM_BUF_SIZE - inbuf->nbytes, num_left);
922 if (num_to_copy == 0)
928 if (inbuf->byte != NULL && inbuf->nbytes > 0)
929 memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
931 memcpy (inbuf->buffer + inbuf->nbytes, bufhd, num_to_copy);
932 bufhd += num_to_copy;
933 inbuf->nbytes += num_to_copy;
934 inbuf->byte = inbuf->buffer;
935 num_left -= num_to_copy;
937 /* ran out of data and we haven't exited main loop */
938 if (inbuf->nbytes == 0)
941 /* get header if needed */
942 if (!context->got_header) {
943 retval = pnm_read_header (context);
945 if (retval == PNM_FATAL_ERR)
947 else if (retval == PNM_SUSPEND)
950 context->got_header = TRUE;
953 /* scan until we hit image data */
954 if (!context->did_prescan) {
955 switch (context->type) {
956 case PNM_FORMAT_PBM_RAW:
957 case PNM_FORMAT_PGM_RAW:
958 case PNM_FORMAT_PPM_RAW:
959 if (inbuf->nbytes <= 0)
961 /* raw formats require exactly one whitespace */
962 if (!g_ascii_isspace(*(inbuf->byte)))
966 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
967 _("Raw PNM formats require exactly one whitespace before sample data"));
974 retval = pnm_skip_whitespace (inbuf,
976 if (retval == PNM_FATAL_ERR)
978 else if (retval == PNM_SUSPEND)
982 context->did_prescan = TRUE;
983 context->output_row = 0;
984 context->output_col = 0;
986 context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
992 if (context->pixbuf == NULL) {
995 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
996 _("Insufficient memory to load PNM file"));
1000 context->pixels = context->pixbuf->pixels;
1001 context->rowstride = context->pixbuf->rowstride;
1003 /* Notify the client that we are ready to go */
1004 (* context->prepared_func) (context->pixbuf,
1006 context->user_data);
1009 /* if we got here we're reading image data */
1010 while (context->output_row < context->height) {
1011 retval = pnm_read_scanline (context);
1013 if (retval == PNM_SUSPEND) {
1015 } else if (retval == PNM_FATAL_ERR) {
1016 if (context->pixbuf)
1017 gdk_pixbuf_unref (context->pixbuf);
1019 } else if (retval == PNM_OK) {
1020 /* send updated signal */
1021 (* context->updated_func) (context->pixbuf,
1023 context->output_row-1,
1026 context->user_data);
1030 if (context->output_row < context->height)
1040 gdk_pixbuf__pnm_fill_vtable (GdkPixbufModule *module)
1042 module->load = gdk_pixbuf__pnm_image_load;
1043 module->begin_load = gdk_pixbuf__pnm_image_begin_load;
1044 module->stop_load = gdk_pixbuf__pnm_image_stop_load;
1045 module->load_increment = gdk_pixbuf__pnm_image_load_increment;