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 GdkPixbufModuleUpdatedFunc updated_func;
57 GdkPixbufModulePreparedFunc 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 (GdkPixbufModuleSizeFunc size_func,
85 GdkPixbufModulePreparedFunc func,
86 GdkPixbufModuleUpdatedFunc 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);
98 /* explode bitmap data into rgb components */
99 /* we need to know what the row so we can */
100 /* do sub-byte expansion (since 1 byte = 8 pixels) */
101 /* context->dptr MUST point at first byte in incoming data */
102 /* which corresponds to first pixel of row y */
104 explode_bitmap_into_buf (PnmLoaderContext *context)
107 guchar *from, *to, data;
112 g_return_if_fail (context != NULL);
113 g_return_if_fail (context->dptr != NULL);
115 /* I'm no clever bit-hacker so I'm sure this can be optimized */
116 dptr = context->dptr;
117 y = context->output_row;
118 wid = context->width;
120 from = dptr + ((wid - 1) / 8);
121 to = dptr + (wid - 1) * 3;
122 /* bit = 7 - (((y+1)*wid-1) % 8); */
123 bit = 7 - ((wid-1) % 8);
125 /* get first byte and align properly */
127 for (j = 0; j < bit; j++, data >>= 1);
129 for (x = wid-1; x >= 0; x--) {
130 /* g_print ("%c", (data & 1) ? '*' : ' '); */
132 to[0] = to[1] = to[2] = (data & 0x01) ? 0x00 : 0xff;
146 /* g_print ("\n"); */
149 /* explode gray image row into rgb components in pixbuf */
151 explode_gray_into_buf (PnmLoaderContext *context)
157 g_return_if_fail (context != NULL);
158 g_return_if_fail (context->dptr != NULL);
160 /* Expand grey->colour. Expand from the end of the
161 * memory down, so we can use the same buffer.
164 from = context->dptr + w - 1;
165 to = context->dptr + (w - 1) * 3;
166 for (j = w - 1; j >= 0; j--) {
175 /* skip over whitespace and comments in input buffer */
177 pnm_skip_whitespace (PnmIOBuffer *inbuf, GError **error)
179 register guchar *inptr;
182 g_return_val_if_fail (inbuf != NULL, PNM_FATAL_ERR);
183 g_return_val_if_fail (inbuf->byte != NULL, PNM_FATAL_ERR);
185 inend = inbuf->byte + inbuf->nbytes;
188 for ( ; inptr < inend; inptr++) {
190 /* in comment - skip to the end of this line */
191 for ( ; *inptr != '\n' && inptr < inend; inptr++)
194 if ( *inptr != '\n' ) {
195 /* couldn't read whole comment */
199 } else if (!g_ascii_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[129];
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);
226 /* skip white space */
227 if ((retval = pnm_skip_whitespace (inbuf, error)) != PNM_OK)
230 inend = inbuf->byte + inbuf->nbytes;
233 /* copy this pnm 'word' into a temp buffer */
234 for (p = inptr, word = buf; (p < inend) && !g_ascii_isspace (*p) && (*p != '#') && (p - inptr < 128); p++, word++)
238 /* hmmm, there must be more data to this 'word' */
239 if (!g_ascii_isspace (*p) && (*p != '#') && (p - inptr < 128))
243 result = strtol (buf, &endptr, 10);
244 if (*endptr != '\0' || result < 0) {
247 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
248 _("PNM loader expected to find an integer, but didn't"));
249 return PNM_FATAL_ERR;
254 inbuf->nbytes = (guint) (inend - p);
259 /* returns PNM_OK, PNM_SUSPEND, or PNM_FATAL_ERR */
261 pnm_read_header (PnmLoaderContext *context)
266 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
268 inbuf = &context->inbuf;
270 if (!context->type) {
271 /* file must start with a 'P' followed by a numeral */
272 /* so loop till we get enough data to determine type */
273 if (inbuf->nbytes < 2)
276 if (*inbuf->byte != 'P') {
277 g_set_error (context->error,
279 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
280 _("PNM file has an incorrect initial byte"));
281 return PNM_FATAL_ERR;
287 switch (*inbuf->byte) {
289 context->type = PNM_FORMAT_PBM;
292 context->type = PNM_FORMAT_PGM;
295 context->type = PNM_FORMAT_PPM;
298 context->type = PNM_FORMAT_PBM_RAW;
301 context->type = PNM_FORMAT_PGM_RAW;
304 context->type = PNM_FORMAT_PPM_RAW;
307 g_set_error (context->error,
309 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
310 _("PNM file is not in a recognized PNM subformat"));
311 return PNM_FATAL_ERR;
321 if (!context->width) {
322 /* read the pixmap width */
325 retval = pnm_read_next_value (inbuf, &width,
328 if (retval != PNM_OK)
332 g_set_error (context->error,
334 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
335 _("PNM file has an image width of 0"));
336 return PNM_FATAL_ERR;
339 context->width = width;
342 if (!context->height) {
343 /* read the pixmap height */
346 retval = pnm_read_next_value (inbuf, &height,
349 if (retval != PNM_OK)
353 g_set_error (context->error,
355 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
356 _("PNM file has an image height of 0"));
357 return PNM_FATAL_ERR;
360 context->height = height;
363 switch (context->type) {
365 case PNM_FORMAT_PPM_RAW:
367 case PNM_FORMAT_PGM_RAW:
368 if (!context->maxval) {
369 retval = pnm_read_next_value (inbuf, &context->maxval,
372 if (retval != PNM_OK)
375 if (context->maxval == 0) {
376 g_set_error (context->error,
378 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
379 _("Maximum color value in PNM file is 0"));
380 return PNM_FATAL_ERR;
383 if (context->maxval > 65535) {
384 g_set_error (context->error,
386 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
387 _("Maximum color value in PNM file is too large"));
388 return PNM_FATAL_ERR;
391 if (context->maxval > 255) {
392 g_set_error (context->error,
394 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
395 _("Can't handle PNM files with maximum color values greater than 255"));
396 return PNM_FATAL_ERR;
408 pnm_read_raw_scanline (PnmLoaderContext *context)
411 guint numbytes, offset;
416 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
418 inbuf = &context->inbuf;
420 switch (context->type) {
421 case PNM_FORMAT_PBM_RAW:
422 numpix = inbuf->nbytes * 8;
424 case PNM_FORMAT_PGM_RAW:
425 numpix = inbuf->nbytes;
427 case PNM_FORMAT_PPM_RAW:
428 numpix = inbuf->nbytes / 3;
431 g_set_error (context->error,
433 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
434 _("Raw PNM image type is invalid"));
435 return PNM_FATAL_ERR;
438 numpix = MIN (numpix, context->width - context->output_col);
443 context->dptr = context->pixels + context->output_row * context->rowstride;
445 switch (context->type) {
446 case PNM_FORMAT_PBM_RAW:
447 numbytes = (numpix / 8) + ((numpix % 8) ? 1 : 0);
448 offset = context->output_col / 8;
450 case PNM_FORMAT_PGM_RAW:
452 offset = context->output_col;
454 case PNM_FORMAT_PPM_RAW:
455 numbytes = numpix * 3;
456 offset = context->output_col * 3;
459 g_set_error (context->error,
461 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
462 _("Raw PNM image type is invalid"));
463 return PNM_FATAL_ERR;
466 switch (context->type) {
467 case PNM_FORMAT_PBM_RAW:
468 dest = context->dptr + offset;
469 memcpy (dest, inbuf->byte, numbytes);
471 case PNM_FORMAT_PGM_RAW:
472 case PNM_FORMAT_PPM_RAW:
473 dest = context->dptr + offset;
475 if (context->maxval == 255) {
476 /* special-case optimization */
477 memcpy (dest, inbuf->byte, numbytes);
479 for (i = 0; i < numbytes; i++) {
480 guchar *byte = inbuf->byte + i;
482 /* scale the color to an 8-bit color depth */
483 if (*byte > context->maxval)
486 *dest++ = (guchar) (255 * *byte / context->maxval);
491 g_set_error (context->error,
493 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
494 _("Raw PNM image type is invalid"));
495 return PNM_FATAL_ERR;
498 inbuf->byte += numbytes;
499 inbuf->nbytes -= numbytes;
501 context->output_col += numpix;
502 if (context->output_col == context->width) {
503 if (context->type == PNM_FORMAT_PBM_RAW)
504 explode_bitmap_into_buf (context);
505 else if (context->type == PNM_FORMAT_PGM_RAW)
506 explode_gray_into_buf (context);
508 context->output_col = 0;
509 context->output_row++;
518 pnm_read_ascii_scanline (PnmLoaderContext *context)
522 guint value, numval, i;
528 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
532 inbuf = &context->inbuf;
534 context->dptr = context->pixels + context->output_row * context->rowstride;
536 switch (context->type) {
538 numval = MIN (8, context->width - context->output_col);
539 offset = context->output_col / 8;
543 offset = context->output_col;
547 offset = context->output_col * 3;
551 g_set_error (context->error,
553 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
554 _("PNM image format is invalid"));
556 return PNM_FATAL_ERR;
559 dptr = context->dptr + offset + context->scan_state;
562 if (context->type == PNM_FORMAT_PBM) {
565 numval = MIN (8, context->width - context->output_col);
568 for (i = context->scan_state; i < numval; i++) {
569 retval = pnm_read_next_value (inbuf, &value,
571 if (retval != PNM_OK) {
572 /* save state and return */
573 context->scan_state = i;
577 switch (context->type) {
586 /* scale the color to an 8-bit color depth */
587 if (value > context->maxval)
590 *dptr++ = (guchar)(255 * value / context->maxval);
593 g_set_error (context->error,
595 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
596 _("PNM image format is invalid"));
597 return PNM_FATAL_ERR;
602 context->scan_state = 0;
604 if (context->type == PNM_FORMAT_PBM) {
606 context->output_col += numval;
608 context->output_col++;
611 if (context->output_col == context->width) {
612 if (context->type == PNM_FORMAT_PBM)
613 explode_bitmap_into_buf (context);
614 else if (context->type == PNM_FORMAT_PGM)
615 explode_gray_into_buf (context);
617 context->output_col = 0;
618 context->output_row++;
626 /* returns 1 if a scanline was converted, 0 means we ran out of data */
628 pnm_read_scanline (PnmLoaderContext *context)
632 g_return_val_if_fail (context != NULL, PNM_FATAL_ERR);
634 /* read in image data */
635 /* for raw formats this is trivial */
636 switch (context->type) {
637 case PNM_FORMAT_PBM_RAW:
638 case PNM_FORMAT_PGM_RAW:
639 case PNM_FORMAT_PPM_RAW:
640 retval = pnm_read_raw_scanline (context);
641 if (retval != PNM_OK)
647 retval = pnm_read_ascii_scanline (context);
648 if (retval != PNM_OK)
652 g_set_error (context->error,
654 GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
655 _("PNM image loader does not support this PNM subformat"));
657 return PNM_FATAL_ERR;
663 /* Shared library entry point */
665 gdk_pixbuf__pnm_image_load (FILE *f, GError **error)
667 PnmLoaderContext context;
672 /* pretend to be doing progressive loading */
673 context.updated_func = NULL;
674 context.prepared_func = NULL;
675 context.user_data = NULL;
677 context.inbuf.nbytes = 0;
678 context.inbuf.byte = NULL;
682 context.pixels = NULL;
683 context.pixbuf = NULL;
684 context.got_header = FALSE;
685 context.did_prescan = FALSE;
686 context.scan_state = 0;
687 context.error = error;
689 inbuf = &context.inbuf;
694 /* keep buffer as full as possible */
695 num_to_read = PNM_BUF_SIZE - inbuf->nbytes;
697 if (inbuf->byte != NULL && inbuf->nbytes > 0)
698 memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
700 nbytes = fread (inbuf->buffer + inbuf->nbytes, 1, num_to_read, f);
704 /* we ran out of data? */
706 g_object_unref (context.pixbuf);
709 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
710 _("Premature end-of-file encountered"));
714 inbuf->nbytes += nbytes;
715 inbuf->byte = inbuf->buffer;
717 /* get header if needed */
718 if (!context.got_header) {
719 retval = pnm_read_header (&context);
720 if (retval == PNM_FATAL_ERR)
722 else if (retval == PNM_SUSPEND)
725 context.got_header = TRUE;
728 /* scan until we hit image data */
729 if (!context.did_prescan) {
730 switch (context.type) {
731 case PNM_FORMAT_PBM_RAW:
732 case PNM_FORMAT_PGM_RAW:
733 case PNM_FORMAT_PPM_RAW:
734 if (inbuf->nbytes <= 0)
736 /* raw formats require exactly one whitespace */
737 if (!g_ascii_isspace(*(inbuf->byte)))
741 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
742 _("Raw PNM formats require exactly one whitespace before sample data"));
749 retval = pnm_skip_whitespace (inbuf,
751 if (retval == PNM_FATAL_ERR)
753 else if (retval == PNM_SUSPEND)
757 context.did_prescan = TRUE;
758 context.output_row = 0;
759 context.output_col = 0;
761 context.pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
762 context.width, context.height);
764 if (!context.pixbuf) {
765 /* Failed to allocate memory */
768 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
769 _("Can't allocate memory for loading PNM image"));
773 context.rowstride = context.pixbuf->rowstride;
774 context.pixels = context.pixbuf->pixels;
777 /* if we got here we're reading image data */
778 while (context.output_row < context.height) {
779 retval = pnm_read_scanline (&context);
781 if (retval == PNM_SUSPEND) {
783 } else if (retval == PNM_FATAL_ERR) {
785 g_object_unref (context.pixbuf);
791 if (context.output_row < context.height)
797 return context.pixbuf;
801 * func - called when we have pixmap created (but no image data)
802 * user_data - passed as arg 1 to func
803 * return context (opaque to user)
807 gdk_pixbuf__pnm_image_begin_load (GdkPixbufModuleSizeFunc size_func,
808 GdkPixbufModulePreparedFunc prepared_func,
809 GdkPixbufModuleUpdatedFunc updated_func,
813 PnmLoaderContext *context;
815 context = g_try_malloc (sizeof (PnmLoaderContext));
817 g_set_error(error, GDK_PIXBUF_ERROR,
818 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
819 _("Insufficient memory to load PNM context struct"));
822 memset (context, 0, sizeof (PnmLoaderContext));
823 context->prepared_func = prepared_func;
824 context->updated_func = updated_func;
825 context->user_data = user_data;
829 context->pixbuf = NULL;
830 context->pixels = NULL;
831 context->got_header = FALSE;
832 context->did_prescan = FALSE;
833 context->scan_state = 0;
835 context->inbuf.nbytes = 0;
836 context->inbuf.byte = NULL;
838 context->error = error;
840 return (gpointer) context;
844 * context - returned from image_begin_load
846 * free context, unref gdk_pixbuf
849 gdk_pixbuf__pnm_image_stop_load (gpointer data,
852 PnmLoaderContext *context = (PnmLoaderContext *) data;
853 gboolean retval = TRUE;
855 g_return_val_if_fail (context != NULL, TRUE);
858 g_object_unref (context->pixbuf);
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
865 if (context->inbuf.nbytes > 0) {
868 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
869 _("Unexpected end of PNM image data"));
880 * context - from image_begin_load
881 * buf - new image data
882 * size - length of new image data
884 * append image data onto inrecrementally built output image
887 gdk_pixbuf__pnm_image_load_increment (gpointer data,
888 const guchar *buf, guint size,
891 PnmLoaderContext *context = (PnmLoaderContext *)data;
896 guint num_left, spinguard;
899 g_return_val_if_fail (context != NULL, FALSE);
900 g_return_val_if_fail (buf != NULL, FALSE);
902 context->error = error;
905 inbuf = &context->inbuf;
906 old_nbytes = inbuf->nbytes;
907 old_byte = inbuf->byte;
914 /* keep buffer as full as possible */
915 num_to_copy = MIN (PNM_BUF_SIZE - inbuf->nbytes, num_left);
917 if (num_to_copy == 0)
923 if (inbuf->byte != NULL && inbuf->nbytes > 0)
924 memmove (inbuf->buffer, inbuf->byte, inbuf->nbytes);
926 memcpy (inbuf->buffer + inbuf->nbytes, bufhd, num_to_copy);
927 bufhd += num_to_copy;
928 inbuf->nbytes += num_to_copy;
929 inbuf->byte = inbuf->buffer;
930 num_left -= num_to_copy;
932 /* ran out of data and we haven't exited main loop */
933 if (inbuf->nbytes == 0)
936 /* get header if needed */
937 if (!context->got_header) {
938 retval = pnm_read_header (context);
940 if (retval == PNM_FATAL_ERR)
942 else if (retval == PNM_SUSPEND)
945 context->got_header = TRUE;
948 /* scan until we hit image data */
949 if (!context->did_prescan) {
950 switch (context->type) {
951 case PNM_FORMAT_PBM_RAW:
952 case PNM_FORMAT_PGM_RAW:
953 case PNM_FORMAT_PPM_RAW:
954 if (inbuf->nbytes <= 0)
956 /* raw formats require exactly one whitespace */
957 if (!g_ascii_isspace(*(inbuf->byte)))
961 GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
962 _("Raw PNM formats require exactly one whitespace before sample data"));
969 retval = pnm_skip_whitespace (inbuf,
971 if (retval == PNM_FATAL_ERR)
973 else if (retval == PNM_SUSPEND)
977 context->did_prescan = TRUE;
978 context->output_row = 0;
979 context->output_col = 0;
981 context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
987 if (context->pixbuf == NULL) {
990 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
991 _("Insufficient memory to load PNM file"));
995 context->pixels = context->pixbuf->pixels;
996 context->rowstride = context->pixbuf->rowstride;
998 /* Notify the client that we are ready to go */
999 (* context->prepared_func) (context->pixbuf,
1001 context->user_data);
1004 /* if we got here we're reading image data */
1005 while (context->output_row < context->height) {
1006 retval = pnm_read_scanline (context);
1008 if (retval == PNM_SUSPEND) {
1010 } else if (retval == PNM_FATAL_ERR) {
1012 } else if (retval == PNM_OK) {
1013 /* send updated signal */
1014 (* context->updated_func) (context->pixbuf,
1016 context->output_row-1,
1019 context->user_data);
1023 if (context->output_row < context->height)
1033 MODULE_ENTRY (pnm, fill_vtable) (GdkPixbufModule *module)
1035 module->load = gdk_pixbuf__pnm_image_load;
1036 module->begin_load = gdk_pixbuf__pnm_image_begin_load;
1037 module->stop_load = gdk_pixbuf__pnm_image_stop_load;
1038 module->load_increment = gdk_pixbuf__pnm_image_load_increment;
1042 MODULE_ENTRY (pnm, fill_info) (GdkPixbufFormat *info)
1044 static GdkPixbufModulePattern signature[] = {
1045 { "P1", NULL, 100 },
1046 { "P2", NULL, 100 },
1047 { "P3", NULL, 100 },
1048 { "P4", NULL, 100 },
1049 { "P5", NULL, 100 },
1050 { "P6", NULL, 100 },
1053 static gchar * mime_types[] = {
1054 "image/x-portable-anymap",
1055 "image/x-portable-bitmap",
1056 "image/x-portable-graymap",
1057 "image/x-portable-pixmap",
1060 static gchar * extensions[] = {
1069 info->signature = signature;
1070 info->description = N_("The PNM/PBM/PGM/PPM image format family");
1071 info->mime_types = mime_types;
1072 info->extensions = extensions;