diff --git a/gdk-pixbuf/io-jpeg.c b/gdk-pixbuf/io-jpeg.c index 99972abb89..b994780b44 100644 --- a/gdk-pixbuf/io-jpeg.c +++ b/gdk-pixbuf/io-jpeg.c @@ -2,9 +2,12 @@ * * Copyright (C) 1999 Michael Zucchi * Copyright (C) 1999 The Free Software Foundation + * + * Progressive loading code Copyright (C) 1999 Red Hat, Inc. * * Authors: Michael Zucchi * Federico Mena-Quintero + * Michael Fulbright * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -22,20 +25,74 @@ * Boston, MA 02111-1307, USA. */ + +/* + Progressive file loading notes (10/29/199) ... + + These are issues I know of and will be dealing with shortly: + + - Currently does not handle progressive jpegs - this + requires a change in the way image_load_increment () calls + libjpeg. Progressive jpegs are rarer but I will add this + support asap. + + - gray images are not properly loaded + + - error handling is not as good as it should be + + */ + + #include #include #include #include #include "gdk-pixbuf.h" +#include "gdk-pixbuf-io.h" +/* we are a "source manager" as far as libjpeg is concerned */ +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + JOCTET * buffer; /* start of buffer */ + gboolean start_of_file; /* have we gotten any data yet? */ + long skip_next; /* number of bytes to skip next read */ + +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + /* error handler data */ struct error_handler_data { struct jpeg_error_mgr pub; sigjmp_buf setjmp_buffer; }; +/* progressive loader context */ +typedef struct { + ModulePreparedNotifyFunc notify_func; + gpointer notify_user_data; + + GdkPixbuf *pixbuf; + guchar *dptr; /* current position in pixbuf */ + + gboolean did_prescan; /* are we in image data yet? */ + gboolean got_header; /* have we loaded jpeg header? */ + gboolean src_initialized;/* TRUE when jpeg lib initialized */ + struct jpeg_decompress_struct cinfo; + struct error_handler_data jerr; +} JpegProgContext; + +#define JPEG_PROG_BUF_SIZE 4096 + +GdkPixbuf *image_load (FILE *f); +gpointer image_begin_load (ModulePreparedNotifyFunc func, gpointer user_data); +void image_stop_load (gpointer context); +gboolean image_load_increment(gpointer context, guchar *buf, guint size); + + static void fatal_error_handler (j_common_ptr cinfo) { @@ -57,11 +114,42 @@ free_buffer (gpointer user_data, gpointer data) free (data); } + +/* explode gray image data from jpeg library into rgb components in pixbuf */ +static void +explode_gray_into_buf (struct jpeg_decompress_struct *cinfo, + guchar **lines) +{ + gint i, j; + guint w; + + g_return_if_fail (cinfo != NULL); + g_return_if_fail (cinfo->output_components == 1); + + /* Expand grey->colour. Expand from the end of the + * memory down, so we can use the same buffer. + */ + w = cinfo->image_width; + for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) { + guchar *from, *to; + + from = lines[i] + w - 1; + to = lines[i] + (w - 1) * 3; + for (j = w - 1; j >= 0; j--) { + to[0] = from[0]; + to[1] = from[1]; + to[2] = from[2]; + to -= 3; + from--; + } + } +} + /* Shared library entry point */ GdkPixbuf * image_load (FILE *f) { - int w, h, i, j; + int w, h, i; guchar *pixels = NULL; guchar *dptr; guchar *lines[4]; /* Used to expand rows, via rec_outbuf_height, from the header file: @@ -114,24 +202,9 @@ image_load (FILE *f) } jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height); - if (cinfo.output_components == 1) { - /* Expand grey->colour. Expand from the end of the - * memory down, so we can use the same buffer. - */ - for (i = cinfo.rec_outbuf_height - 1; i >= 0; i--) { - guchar *from, *to; - from = lines[i] + w - 1; - to = lines[i] + w * 3 - 3; - for (j = w - 1; j >= 0; j--) { - to[0] = from[0]; - to[1] = from[0]; - to[2] = from[0]; - to -= 3; - from--; - } - } - } + if (cinfo.output_components == 1) + explode_gray_into_buf (&cinfo, lines); } jpeg_finish_decompress (&cinfo); @@ -143,3 +216,289 @@ image_load (FILE *f) return pixbuf; } + + +/**** Progressive image loading handling *****/ + +/* these routines required because we are acting as a source manager for */ +/* libjpeg. */ +static void +init_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + src->start_of_file = TRUE; + src->skip_next = 0; +} + + +static void +term_source (j_decompress_ptr cinfo) +{ + /* XXXX - probably should scream something has happened */ +} + + +/* for progressive loading (called "I/O Suspension" by libjpeg docs) */ +/* we do nothing except return "FALSE" */ +static boolean +fill_input_buffer (j_decompress_ptr cinfo) +{ + return FALSE; +} + + +static void +skip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + long num_can_do; + + /* move as far as we can into current buffer */ + /* then set skip_next to catch the rest */ + if (num_bytes > 0) { + num_can_do = MIN (src->pub.bytes_in_buffer, num_bytes); + src->pub.next_input_byte += (size_t) num_can_do; + src->pub.bytes_in_buffer -= (size_t) num_can_do; + + src->skip_next = num_bytes - num_can_do; + } +} + + +/* + * func - called when we have pixmap created (but no image data) + * user_data - passed as arg 1 to func + * return context (opaque to user) + */ + +gpointer +image_begin_load (ModulePreparedNotifyFunc func, gpointer user_data) +{ + JpegProgContext *context; + my_source_mgr *src; + + context = g_new (JpegProgContext, 1); + context->notify_func = func; + context->notify_user_data = user_data; + context->pixbuf = NULL; + context->got_header = FALSE; + context->did_prescan = FALSE; + context->src_initialized = FALSE; + + /* create libjpeg structures */ + jpeg_create_decompress (&context->cinfo); + + context->cinfo.src = (struct jpeg_source_mgr *) g_new (my_source_mgr, 1); + src = (my_src_ptr) context->cinfo.src; + src->buffer = g_malloc (JPEG_PROG_BUF_SIZE); + + context->cinfo.err = jpeg_std_error (&context->jerr.pub); + + src = (my_src_ptr) context->cinfo.src; + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; + src->pub.term_source = term_source; + src->pub.bytes_in_buffer = 0; + src->pub.next_input_byte = NULL; + + return (gpointer) context; +} + +/* + * context - returned from image_begin_load + * + * free context, unref gdk_pixbuf + */ +void +image_stop_load (gpointer data) +{ + JpegProgContext *context = (JpegProgContext *) data; + g_return_if_fail (context != NULL); + + /* XXXX - is this all I have to do to cleanup? */ + if (context->pixbuf) + gdk_pixbuf_unref (context->pixbuf); + + if (context->cinfo.src) { + my_src_ptr src = (my_src_ptr) context->cinfo.src; + + if (src->buffer) + g_free (src->buffer); + g_free (src); + } + + jpeg_finish_decompress(&context->cinfo); + jpeg_destroy_decompress(&context->cinfo); + + g_free (context); +} + + + + +/* + * context - from image_begin_load + * buf - new image data + * size - length of new image data + * + * append image data onto inrecrementally built output image + */ +gboolean +image_load_increment (gpointer data, guchar *buf, guint size) +{ + JpegProgContext *context = (JpegProgContext *)data; + struct jpeg_decompress_struct *cinfo; + my_src_ptr src; + guint num_left, num_copy; + guchar *nextptr; + + g_return_val_if_fail (context != NULL, FALSE); + g_return_val_if_fail (buf != NULL, FALSE); + + src = (my_src_ptr) context->cinfo.src; + cinfo = &context->cinfo; + + /* skip over data if requested, handle unsigned int sizes cleanly */ + /* only can happen if we've already called jpeg_get_header once */ + if (context->src_initialized && src->skip_next) { + if (src->skip_next > size) { + src->skip_next -= size; + return TRUE; + } else { + num_left = size - src->skip_next; + src->skip_next = 0; + } + } else { + num_left = size; + } + + while (num_left > 0) { + /* copy as much data into buffer as possible */ + num_copy = MIN (JPEG_PROG_BUF_SIZE - src->pub.bytes_in_buffer, + size); + + if (num_copy == 0) + g_assert ("Buffer overflow!\n"); + + nextptr = src->buffer + src->pub.bytes_in_buffer; + memcpy (nextptr, buf, num_copy); + + if (src->pub.next_input_byte == NULL || + src->pub.bytes_in_buffer == 0) + src->pub.next_input_byte = src->buffer; + + src->pub.bytes_in_buffer += num_copy; + + num_left -= num_copy; + + /* try to load jpeg header */ + /* XXXX - bad - assume we always have enough data */ + /* to determine header info in first */ + /* invocation of this function */ + if (!context->got_header) { + int rc; + + rc = jpeg_read_header (cinfo, TRUE); + context->src_initialized = TRUE; + if (rc == JPEG_SUSPENDED) + continue; + + context->got_header = TRUE; + + if (jpeg_has_multiple_scans (cinfo)) { + g_print ("io-jpeg.c: Does not currently " + "support progressive jpeg files.\n"); + return FALSE; + } + + context->pixbuf = gdk_pixbuf_new(ART_PIX_RGB, + /*have_alpha*/ FALSE, + 8, + cinfo->image_width, + cinfo->image_height); + + if (context->pixbuf == NULL) { + /* Failed to allocate memory */ + g_assert ("Couldn't allocate gdkpixbuf\n"); + } + + /* Use pixbuf pixel buffer - BUT NOT SURE */ + /* we handle rowstride correctly!!!! */ + context->dptr = context->pixbuf->art_pixbuf->pixels; + + /* Notify the client that we are ready to go */ + + if (context->notify_func) + (* context->notify_func) (context->pixbuf, + context->notify_user_data); + + src->start_of_file = FALSE; + } else if (!context->did_prescan) { + int rc; + + /* start decompression */ + rc = jpeg_start_decompress (cinfo); + cinfo->do_fancy_upsampling = FALSE; + cinfo->do_block_smoothing = FALSE; + + if (rc == JPEG_SUSPENDED) + continue; + + context->did_prescan = TRUE; + } else { + /* we're decompressing so feed jpeg lib scanlines */ + guchar *lines[4]; + guchar **lptr; + guchar *rowptr; + gint nlines, i; + gint start_scanline; + + /* THIS COULD BE BROKEN */ + /* Assumes rowstride of gdkpixbuf pixel buffer */ + /* is same as incoming jpeg data! */ + + while (cinfo->output_scanline < cinfo->output_height) { + start_scanline = cinfo->output_scanline; + lptr = lines; + rowptr = context->dptr; + for (i=0; i < cinfo->rec_outbuf_height; i++) { + *lptr++ = rowptr; + rowptr += cinfo->image_width * 3; + } + + nlines = jpeg_read_scanlines (cinfo, lines, + cinfo->rec_outbuf_height); + if (nlines == 0) + break; + + /* handle gray */ + if (cinfo->output_components == 1) + explode_gray_into_buf (cinfo, lines); + + context->dptr += nlines * cinfo->image_width * 3; +#ifdef DEBUG_JPEG_PROGRESSIVE + + if (start_scanline != cinfo->output_scanline) + g_print("jpeg: Input pass=%2d, next input scanline=%3d," + " emitted %3d - %3d\n", + cinfo->input_scan_number, cinfo->input_iMCU_row * 16, + start_scanline, cinfo->output_scanline - 1); + + + + g_print ("Scanline %d of %d - ", + cinfo->output_scanline, + cinfo->output_height); +/* g_print ("rec_height %d -", cinfo->rec_outbuf_height); */ + g_print ("Processed %d lines - bytes left = %d\n", + nlines, cinfo->src->bytes_in_buffer); +#endif + } + } + } + + return TRUE; +}