/* -*- mode: C; c-file-style: "linux" -*- */ /* GdkPixbuf library - JPEG image loader * * 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 Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #include "config.h" #include #include #include #include #include #include #include #include "gdk-pixbuf-private.h" #include "fallback-c89.c" #ifndef HAVE_SIGSETJMP #define sigjmp_buf jmp_buf #define sigsetjmp(jb, x) setjmp(jb) #define siglongjmp longjmp #endif /* we are a "source manager" as far as libjpeg is concerned */ #define JPEG_PROG_BUF_SIZE 65536 typedef struct { struct jpeg_source_mgr pub; /* public fields */ JOCTET buffer[JPEG_PROG_BUF_SIZE]; /* start of buffer */ 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; GError **error; }; /* progressive loader context */ typedef struct { GdkPixbufModuleSizeFunc size_func; GdkPixbufModuleUpdatedFunc updated_func; GdkPixbufModulePreparedFunc prepared_func; gpointer 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 */ gboolean in_output; /* did we get suspended in an output pass? */ struct jpeg_decompress_struct cinfo; struct error_handler_data jerr; } JpegProgContext; /* EXIF context */ typedef struct { gint orientation; gchar *icc_profile; gsize icc_profile_size; gsize icc_profile_size_allocated; } JpegExifContext; static GdkPixbuf *gdk_pixbuf__jpeg_image_load (FILE *f, GError **error); static gpointer gdk_pixbuf__jpeg_image_begin_load (GdkPixbufModuleSizeFunc func0, GdkPixbufModulePreparedFunc func1, GdkPixbufModuleUpdatedFunc func2, gpointer user_data, GError **error); static gboolean gdk_pixbuf__jpeg_image_stop_load (gpointer context, GError **error); static gboolean gdk_pixbuf__jpeg_image_load_increment(gpointer context, const guchar *buf, guint size, GError **error); static void fatal_error_handler (j_common_ptr cinfo) { struct error_handler_data *errmgr; char buffer[JMSG_LENGTH_MAX]; errmgr = (struct error_handler_data *) cinfo->err; /* Create the message */ (* cinfo->err->format_message) (cinfo, buffer); /* broken check for *error == NULL for robustness against * crappy JPEG library */ if (errmgr->error && *errmgr->error == NULL) { g_set_error (errmgr->error, GDK_PIXBUF_ERROR, cinfo->err->msg_code == JERR_OUT_OF_MEMORY ? GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY : GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Error interpreting JPEG image file (%s)"), buffer); } siglongjmp (errmgr->setjmp_buffer, 1); g_assert_not_reached (); } static void output_message_handler (j_common_ptr cinfo) { /* This method keeps libjpeg from dumping crap to stderr */ /* do nothing */ } /* 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); g_return_if_fail (cinfo->out_color_space == JCS_GRAYSCALE); /* Expand grey->colour. Expand from the end of the * memory down, so we can use the same buffer. */ w = cinfo->output_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[0]; to[2] = from[0]; to -= 3; from--; } } } static void convert_cmyk_to_rgb (struct jpeg_decompress_struct *cinfo, guchar **lines) { gint i, j; g_return_if_fail (cinfo != NULL); g_return_if_fail (cinfo->output_components == 4); g_return_if_fail (cinfo->out_color_space == JCS_CMYK); for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) { guchar *p; p = lines[i]; for (j = 0; j < cinfo->output_width; j++) { int c, m, y, k; c = p[0]; m = p[1]; y = p[2]; k = p[3]; /* We now assume that all CMYK JPEG files * use inverted CMYK, as Photoshop does * See https://bugzilla.gnome.org/show_bug.cgi?id=618096 */ p[0] = k*c / 255; p[1] = k*m / 255; p[2] = k*y / 255; p[3] = 255; p += 4; } } } typedef struct { struct jpeg_source_mgr pub; /* public fields */ FILE * infile; /* source stream */ JOCTET * buffer; /* start of buffer */ boolean start_of_file; /* have we gotten any data yet? */ } stdio_source_mgr; typedef stdio_source_mgr * stdio_src_ptr; static void stdio_init_source (j_decompress_ptr cinfo) { stdio_src_ptr src = (stdio_src_ptr)cinfo->src; src->start_of_file = FALSE; } static boolean stdio_fill_input_buffer (j_decompress_ptr cinfo) { stdio_src_ptr src = (stdio_src_ptr) cinfo->src; size_t nbytes; nbytes = fread (src->buffer, 1, JPEG_PROG_BUF_SIZE, src->infile); if (nbytes <= 0) { #if 0 if (src->start_of_file) /* Treat empty input file as fatal error */ ERREXIT(cinfo, JERR_INPUT_EMPTY); WARNMS(cinfo, JWRN_JPEG_EOF); #endif /* Insert a fake EOI marker */ src->buffer[0] = (JOCTET) 0xFF; src->buffer[1] = (JOCTET) JPEG_EOI; nbytes = 2; } src->pub.next_input_byte = src->buffer; src->pub.bytes_in_buffer = nbytes; src->start_of_file = FALSE; return TRUE; } static void stdio_skip_input_data (j_decompress_ptr cinfo, long num_bytes) { stdio_src_ptr src = (stdio_src_ptr) cinfo->src; if (num_bytes > 0) { while (num_bytes > (long) src->pub.bytes_in_buffer) { num_bytes -= (long) src->pub.bytes_in_buffer; (void)stdio_fill_input_buffer(cinfo); } src->pub.next_input_byte += (size_t) num_bytes; src->pub.bytes_in_buffer -= (size_t) num_bytes; } } static void stdio_term_source (j_decompress_ptr cinfo) { } static gchar * colorspace_name (const J_COLOR_SPACE jpeg_color_space) { switch (jpeg_color_space) { case JCS_UNKNOWN: return "UNKNOWN"; case JCS_GRAYSCALE: return "GRAYSCALE"; case JCS_RGB: return "RGB"; case JCS_YCbCr: return "YCbCr"; case JCS_CMYK: return "CMYK"; case JCS_YCCK: return "YCCK"; default: return "invalid"; } } #define DE_ENDIAN16(val) endian == G_BIG_ENDIAN ? GUINT16_FROM_BE(val) : GUINT16_FROM_LE(val) #define DE_ENDIAN32(val) endian == G_BIG_ENDIAN ? GUINT32_FROM_BE(val) : GUINT32_FROM_LE(val) #define ENDIAN16_IT(val) endian == G_BIG_ENDIAN ? GUINT16_TO_BE(val) : GUINT16_TO_LE(val) #define ENDIAN32_IT(val) endian == G_BIG_ENDIAN ? GUINT32_TO_BE(val) : GUINT32_TO_LE(val) static unsigned short de_get16(void *ptr, guint endian) { unsigned short val; memcpy(&val, ptr, sizeof(val)); val = DE_ENDIAN16(val); return val; } static unsigned int de_get32(void *ptr, guint endian) { unsigned int val; memcpy(&val, ptr, sizeof(val)); val = DE_ENDIAN32(val); return val; } /* application specific data segment */ static gboolean jpeg_parse_exif_app2_segment (JpegExifContext *context, jpeg_saved_marker_ptr marker) { guint ret = FALSE; guint sequence_number; guint number_of_chunks; guint chunk_size; guint offset; /* do we have enough data? */ if (marker->data_length < 16) goto out; /* unique identification string */ if (memcmp (marker->data, "ICC_PROFILE\0", 12) != 0) goto out; /* get data about this segment */ sequence_number = marker->data[12]; number_of_chunks = marker->data[13]; /* this is invalid, the base offset is 1 */ if (sequence_number == 0) goto out; /* this is invalid, the base offset is 1 */ if (sequence_number > number_of_chunks) goto out; /* size includes the id (12 bytes), length field (1 byte), and sequence field (1 byte) */ chunk_size = marker->data_length - 14; offset = (sequence_number - 1) * 0xffef; /* Deal with the trivial profile (99% of images) to avoid allocating * 64kb when we might only use a few kb. */ if (number_of_chunks == 1) { if (context->icc_profile_size_allocated > 0) goto out; context->icc_profile_size = chunk_size; context->icc_profile_size_allocated = chunk_size; context->icc_profile = g_new (gchar, chunk_size); /* copy the segment data to the profile space */ memcpy (context->icc_profile, marker->data + 14, chunk_size); goto out; } /* There is no promise the APP2 segments are going to be in order, so we * have to allocate a huge swathe of memory and fill in the gaps when * (if) we get the segment. * Theoretically this could be as much as 16Mb, but display profiles are * vary rarely above 100kb, and printer profiles are usually less than * 2Mb */ if (context->icc_profile_size_allocated == 0) { context->icc_profile_size_allocated = number_of_chunks * 0xffff; context->icc_profile = g_new0 (gchar, number_of_chunks * 0xffff); } /* check the data will fit in our previously allocated buffer */ if (offset + chunk_size > context->icc_profile_size_allocated) goto out; /* copy the segment data to the profile space */ memcpy (context->icc_profile + offset, marker->data + 14, chunk_size); /* it's now this big plus the new data we've just copied */ context->icc_profile_size += chunk_size; /* success */ ret = TRUE; out: return ret; } static gboolean jpeg_parse_exif_app1 (JpegExifContext *context, jpeg_saved_marker_ptr marker) { guint i; guint ret = FALSE; guint offset; guint tags; /* number of tags in current ifd */ guint endian = 0; /* detected endian of data */ const char leth[] = {0x49, 0x49, 0x2a, 0x00}; // Little endian TIFF header const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; // Big endian TIFF header /* do we have enough data? */ if (marker->data_length < 4) goto out; /* unique identification string */ if (memcmp (marker->data, "Exif", 4) != 0) goto out; /* do we have enough data? */ if (marker->data_length < 32) goto out; /* Just skip data until TIFF header - it should be within 16 bytes from marker start. Normal structure relative to APP1 marker - 0x0000: APP1 marker entry = 2 bytes 0x0002: APP1 length entry = 2 bytes 0x0004: Exif Identifier entry = 6 bytes 0x000A: Start of TIFF header (Byte order entry) - 4 bytes - This is what we look for, to determine endianess. 0x000E: 0th IFD offset pointer - 4 bytes marker->data points to the first data after the APP1 marker and length entries, which is the exif identification string. The TIFF header should thus normally be found at i=6, below, and the pointer to IFD0 will be at 6+4 = 10. */ for (i=0; i<16; i++) { /* little endian TIFF header */ if (memcmp (&marker->data[i], leth, 4) == 0) { endian = G_LITTLE_ENDIAN; ret = TRUE; break; } /* big endian TIFF header */ if (memcmp (&marker->data[i], beth, 4) == 0) { endian = G_BIG_ENDIAN; ret = TRUE; break; } } /* could not find header */ if (!ret) goto out; /* read out the offset pointer to IFD0 */ offset = de_get32(&marker->data[i] + 4, endian); i = i + offset; /* check that we still are within the buffer and can read the tag count */ { const size_t new_i = i + 2; if (new_i < i || new_i > marker->data_length) { ret = FALSE; goto out; } /* find out how many tags we have in IFD0. As per the TIFF spec, the first two bytes of the IFD contain a count of the number of tags. */ tags = de_get16(&marker->data[i], endian); i = new_i; } /* check that we still have enough data for all tags to check. The tags are listed in consecutive 12-byte blocks. The tag ID, type, size, and a pointer to the actual value, are packed into these 12 byte entries. */ { const size_t new_i = i + tags * 12; if (new_i < i || new_i > marker->data_length) { ret = FALSE; goto out; } } /* check through IFD0 for tags */ while (tags--) { size_t new_i; /* We check for integer overflow before the loop and * at the end of each iteration */ guint tag = de_get16(&marker->data[i + 0], endian); guint type = de_get16(&marker->data[i + 2], endian); guint count = de_get32(&marker->data[i + 4], endian); /* orientation tag? */ if (tag == 0x112){ /* The orientation field should consist of a single 2-byte integer, * but might be a signed long. * Values of types smaller than 4 bytes are stored directly in the * Value Offset field */ if (type == 0x3 && count == 1) { guint short_value = de_get16(&marker->data[i + 8], endian); context->orientation = short_value <= 8 ? short_value : 0; } else if (type == 0x9 && count == 1) { guint long_value = de_get32(&marker->data[i + 8], endian); context->orientation = long_value <= 8 ? long_value : 0; } } /* move the pointer to the next 12-byte tag field. */ new_i = i + 12; if (new_i < i || new_i > marker->data_length) { ret = FALSE; goto out; } i = new_i; } out: return ret; } static void jpeg_parse_exif (JpegExifContext *context, j_decompress_ptr cinfo) { jpeg_saved_marker_ptr cmarker; /* check for interesting Exif markers */ cmarker = cinfo->marker_list; while (cmarker != NULL) { if (cmarker->marker == JPEG_APP0+1) jpeg_parse_exif_app1 (context, cmarker); else if (cmarker->marker == JPEG_APP0+2) jpeg_parse_exif_app2_segment (context, cmarker); cmarker = cmarker->next; } } static gchar * jpeg_get_comment (j_decompress_ptr cinfo) { jpeg_saved_marker_ptr cmarker; cmarker = cinfo->marker_list; while (cmarker != NULL) { if (cmarker->marker == JPEG_COM) return g_strndup ((const gchar *) cmarker->data, cmarker->data_length); cmarker = cmarker->next; } return NULL; } static void jpeg_destroy_exif_context (JpegExifContext *context) { g_free (context->icc_profile); } /* Shared library entry point */ static GdkPixbuf * gdk_pixbuf__jpeg_image_load (FILE *f, GError **error) { gint i; char otag_str[5]; char *density_str; GdkPixbuf * volatile pixbuf = NULL; guchar *dptr; guchar *lines[4]; /* Used to expand rows, via rec_outbuf_height, * from the header file: * " Usually rec_outbuf_height will be 1 or 2, * at most 4." */ guchar **lptr; struct jpeg_decompress_struct cinfo; struct error_handler_data jerr; stdio_src_ptr src; gchar *icc_profile_base64; gchar *comment; JpegExifContext exif_context = { 0, }; /* setup error handler */ cinfo.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = fatal_error_handler; jerr.pub.output_message = output_message_handler; jerr.error = error; if (sigsetjmp (jerr.setjmp_buffer, 1)) { /* Whoops there was a jpeg error */ if (pixbuf) g_object_unref (pixbuf); jpeg_destroy_decompress (&cinfo); jpeg_destroy_exif_context (&exif_context); /* error should have been set by fatal_error_handler () */ return NULL; } /* load header, setup */ jpeg_create_decompress (&cinfo); cinfo.src = (struct jpeg_source_mgr *) (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof (stdio_source_mgr)); src = (stdio_src_ptr) cinfo.src; src->buffer = (JOCTET *) (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, JPEG_PROG_BUF_SIZE * sizeof (JOCTET)); src->pub.init_source = stdio_init_source; src->pub.fill_input_buffer = stdio_fill_input_buffer; src->pub.skip_input_data = stdio_skip_input_data; src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ src->pub.term_source = stdio_term_source; src->infile = f; src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ src->pub.next_input_byte = NULL; /* until buffer loaded */ jpeg_save_markers (&cinfo, JPEG_APP0+1, 0xffff); jpeg_save_markers (&cinfo, JPEG_APP0+2, 0xffff); jpeg_save_markers (&cinfo, JPEG_COM, 0xffff); jpeg_read_header (&cinfo, TRUE); /* parse exif data */ jpeg_parse_exif (&exif_context, &cinfo); jpeg_start_decompress (&cinfo); cinfo.do_fancy_upsampling = FALSE; cinfo.do_block_smoothing = FALSE; pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, cinfo.out_color_components == 4 ? TRUE : FALSE, 8, cinfo.output_width, cinfo.output_height); if (!pixbuf) { /* broken check for *error == NULL for robustness against * crappy JPEG library */ if (error && *error == NULL) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory to load image, try exiting some applications to free memory")); } goto out; } comment = jpeg_get_comment (&cinfo); if (comment != NULL) { gdk_pixbuf_set_option (pixbuf, "comment", comment); g_free (comment); } switch (cinfo.density_unit) { case 1: /* Dots per inch (no conversion required) */ density_str = g_strdup_printf ("%d", cinfo.X_density); gdk_pixbuf_set_option (pixbuf, "x-dpi", density_str); g_free (density_str); density_str = g_strdup_printf ("%d", cinfo.Y_density); gdk_pixbuf_set_option (pixbuf, "y-dpi", density_str); g_free (density_str); break; case 2: /* Dots per cm - convert into dpi */ density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo.X_density)); gdk_pixbuf_set_option (pixbuf, "x-dpi", density_str); g_free (density_str); density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo.Y_density)); gdk_pixbuf_set_option (pixbuf, "y-dpi", density_str); g_free (density_str); break; } /* if orientation tag was found */ if (exif_context.orientation != 0) { g_snprintf (otag_str, sizeof (otag_str), "%d", exif_context.orientation); gdk_pixbuf_set_option (pixbuf, "orientation", otag_str); } /* if icc profile was found */ if (exif_context.icc_profile != NULL) { icc_profile_base64 = g_base64_encode ((const guchar *) exif_context.icc_profile, exif_context.icc_profile_size); gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64); g_free (icc_profile_base64); } dptr = pixbuf->pixels; /* decompress all the lines, a few at a time */ while (cinfo.output_scanline < cinfo.output_height) { lptr = lines; for (i = 0; i < cinfo.rec_outbuf_height; i++) { *lptr++ = dptr; dptr += pixbuf->rowstride; } jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height); switch (cinfo.out_color_space) { case JCS_GRAYSCALE: explode_gray_into_buf (&cinfo, lines); break; case JCS_RGB: /* do nothing */ break; case JCS_CMYK: convert_cmyk_to_rgb (&cinfo, lines); break; default: g_clear_object (&pixbuf); g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("Unsupported JPEG color space (%s)"), colorspace_name (cinfo.out_color_space)); goto out; } } out: jpeg_finish_decompress (&cinfo); jpeg_destroy_decompress (&cinfo); jpeg_destroy_exif_context (&exif_context); 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->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) */ static gpointer gdk_pixbuf__jpeg_image_begin_load (GdkPixbufModuleSizeFunc size_func, GdkPixbufModulePreparedFunc prepared_func, GdkPixbufModuleUpdatedFunc updated_func, gpointer user_data, GError **error) { JpegProgContext *context; my_source_mgr *src; context = g_new0 (JpegProgContext, 1); context->size_func = size_func; context->prepared_func = prepared_func; context->updated_func = updated_func; context->user_data = user_data; context->pixbuf = NULL; context->got_header = FALSE; context->did_prescan = FALSE; context->src_initialized = FALSE; context->in_output = FALSE; /* From jpeglib.h: "NB: you must set up the error-manager * BEFORE calling jpeg_create_xxx". */ context->cinfo.err = jpeg_std_error (&context->jerr.pub); context->jerr.pub.error_exit = fatal_error_handler; context->jerr.pub.output_message = output_message_handler; context->jerr.error = error; if (sigsetjmp (context->jerr.setjmp_buffer, 1)) { jpeg_destroy_decompress (&context->cinfo); g_free(context); /* error should have been set by fatal_error_handler () */ return NULL; } /* create libjpeg structures */ jpeg_create_decompress (&context->cinfo); context->cinfo.src = (struct jpeg_source_mgr *) g_try_malloc (sizeof (my_source_mgr)); if (!context->cinfo.src) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Couldn't allocate memory for loading JPEG file")); return NULL; } memset (context->cinfo.src, 0, sizeof (my_source_mgr)); 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; context->jerr.error = NULL; return (gpointer) context; } /* * context - returned from image_begin_load * * free context, unref gdk_pixbuf */ static gboolean gdk_pixbuf__jpeg_image_stop_load (gpointer data, GError **error) { JpegProgContext *context = (JpegProgContext *) data; gboolean retval; g_return_val_if_fail (context != NULL, TRUE); /* FIXME this thing needs to report errors if * we have unused image data */ if (context->pixbuf) g_object_unref (context->pixbuf); /* if we have an error? */ context->jerr.error = error; if (sigsetjmp (context->jerr.setjmp_buffer, 1)) { retval = FALSE; } else { jpeg_finish_decompress (&context->cinfo); retval = TRUE; } jpeg_destroy_decompress (&context->cinfo); if (context->cinfo.src) { my_src_ptr src = (my_src_ptr) context->cinfo.src; g_free (src); } g_free (context); return retval; } static gboolean gdk_pixbuf__jpeg_image_load_lines (JpegProgContext *context, GError **error) { struct jpeg_decompress_struct *cinfo = &context->cinfo; guchar *lines[4]; guchar **lptr; guchar *rowptr; gint nlines, i; /* keep going until we've done all scanlines */ while (cinfo->output_scanline < cinfo->output_height) { lptr = lines; rowptr = context->dptr; for (i=0; i < cinfo->rec_outbuf_height; i++) { *lptr++ = rowptr; rowptr += context->pixbuf->rowstride; } nlines = jpeg_read_scanlines (cinfo, lines, cinfo->rec_outbuf_height); if (nlines == 0) break; switch (cinfo->out_color_space) { case JCS_GRAYSCALE: explode_gray_into_buf (cinfo, lines); break; case JCS_RGB: /* do nothing */ break; case JCS_CMYK: convert_cmyk_to_rgb (cinfo, lines); break; default: g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_UNKNOWN_TYPE, _("Unsupported JPEG color space (%s)"), colorspace_name (cinfo->out_color_space)); return FALSE; } context->dptr += (gsize)nlines * context->pixbuf->rowstride; /* send updated signal */ if (context->updated_func) (* context->updated_func) (context->pixbuf, 0, cinfo->output_scanline - 1, cinfo->image_width, nlines, context->user_data); } return TRUE; } /* * context - from image_begin_load * buf - new image data * size - length of new image data * * append image data onto inrecrementally built output image */ static gboolean gdk_pixbuf__jpeg_image_load_increment (gpointer data, const guchar *buf, guint size, GError **error) { JpegProgContext *context = (JpegProgContext *)data; struct jpeg_decompress_struct *cinfo; my_src_ptr src; guint num_left, num_copy; guint last_num_left, last_bytes_left; guint spinguard; gboolean first; const guchar *bufhd; gint width, height; char otag_str[5]; gchar *icc_profile_base64; char *density_str; JpegExifContext exif_context = { 0, }; gboolean retval; 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; context->jerr.error = error; /* check for fatal error */ if (sigsetjmp (context->jerr.setjmp_buffer, 1)) { retval = FALSE; goto out; } /* 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; retval = TRUE; goto out; } else { num_left = size - src->skip_next; bufhd = buf + src->skip_next; src->skip_next = 0; } } else { num_left = size; bufhd = buf; } if (num_left == 0) { retval = TRUE; goto out; } last_num_left = num_left; last_bytes_left = 0; spinguard = 0; first = TRUE; while (TRUE) { /* handle any data from caller we haven't processed yet */ if (num_left > 0) { if(src->pub.bytes_in_buffer && src->pub.next_input_byte != src->buffer) memmove(src->buffer, src->pub.next_input_byte, src->pub.bytes_in_buffer); num_copy = MIN (JPEG_PROG_BUF_SIZE - src->pub.bytes_in_buffer, num_left); memcpy(src->buffer + src->pub.bytes_in_buffer, bufhd,num_copy); src->pub.next_input_byte = src->buffer; src->pub.bytes_in_buffer += num_copy; bufhd += num_copy; num_left -= num_copy; } /* did anything change from last pass, if not return */ if (first) { last_bytes_left = src->pub.bytes_in_buffer; first = FALSE; } else if (src->pub.bytes_in_buffer == last_bytes_left && num_left == last_num_left) { spinguard++; } else { last_bytes_left = src->pub.bytes_in_buffer; last_num_left = num_left; } /* should not go through twice and not pull bytes out of buf */ if (spinguard > 2) { retval = TRUE; goto out; } /* try to load jpeg header */ if (!context->got_header) { int rc; gchar* comment; gboolean has_alpha; jpeg_save_markers (cinfo, JPEG_APP0+1, 0xffff); jpeg_save_markers (cinfo, JPEG_APP0+2, 0xffff); jpeg_save_markers (cinfo, JPEG_COM, 0xffff); rc = jpeg_read_header (cinfo, TRUE); context->src_initialized = TRUE; if (rc == JPEG_SUSPENDED) continue; context->got_header = TRUE; /* parse exif data */ jpeg_parse_exif (&exif_context, cinfo); width = cinfo->image_width; height = cinfo->image_height; if (context->size_func) { (* context->size_func) (&width, &height, context->user_data); if (width == 0 || height == 0) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Transformed JPEG has zero width or height.")); retval = FALSE; goto out; } } cinfo->scale_num = 1; for (cinfo->scale_denom = 2; cinfo->scale_denom <= 8; cinfo->scale_denom *= 2) { jpeg_calc_output_dimensions (cinfo); if (cinfo->output_width < width || cinfo->output_height < height) { cinfo->scale_denom /= 2; break; } } jpeg_calc_output_dimensions (cinfo); if (cinfo->output_components == 3) { has_alpha = FALSE; } else if (cinfo->output_components == 4) { has_alpha = TRUE; } else if (cinfo->output_components == 1 && cinfo->out_color_space == JCS_GRAYSCALE) { has_alpha = FALSE; } else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, _("Unsupported number of color components (%d)"), cinfo->output_components); retval = FALSE; goto out; } context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, has_alpha, 8, cinfo->output_width, cinfo->output_height); if (context->pixbuf == NULL) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Couldn't allocate memory for loading JPEG file")); retval = FALSE; goto out; } comment = jpeg_get_comment (cinfo); if (comment != NULL) { gdk_pixbuf_set_option (context->pixbuf, "comment", comment); g_free (comment); } switch (cinfo->density_unit) { case 1: /* Dots per inch (no conversion required) */ density_str = g_strdup_printf ("%d", cinfo->X_density); gdk_pixbuf_set_option (context->pixbuf, "x-dpi", density_str); g_free (density_str); density_str = g_strdup_printf ("%d", cinfo->Y_density); gdk_pixbuf_set_option (context->pixbuf, "y-dpi", density_str); g_free (density_str); break; case 2: /* Dots per cm - convert into dpi */ density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo->X_density)); gdk_pixbuf_set_option (context->pixbuf, "x-dpi", density_str); g_free (density_str); density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo->Y_density)); gdk_pixbuf_set_option (context->pixbuf, "y-dpi", density_str); g_free (density_str); break; } /* if orientation tag was found set an option to remember its value */ if (exif_context.orientation != 0) { g_snprintf (otag_str, sizeof (otag_str), "%d", exif_context.orientation); gdk_pixbuf_set_option (context->pixbuf, "orientation", otag_str); } /* if icc profile was found */ if (exif_context.icc_profile != NULL) { icc_profile_base64 = g_base64_encode ((const guchar *) exif_context.icc_profile, exif_context.icc_profile_size); gdk_pixbuf_set_option (context->pixbuf, "icc-profile", icc_profile_base64); g_free (icc_profile_base64); } /* Use pixbuf buffer to store decompressed data */ context->dptr = context->pixbuf->pixels; /* Notify the client that we are ready to go */ if (context->prepared_func) (* context->prepared_func) (context->pixbuf, NULL, context->user_data); } else if (!context->did_prescan) { int rc; /* start decompression */ cinfo->buffered_image = cinfo->progressive_mode; 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 if (!cinfo->buffered_image) { /* we're decompressing unbuffered so * simply get scanline by scanline from jpeg lib */ if (! gdk_pixbuf__jpeg_image_load_lines (context, error)) { retval = FALSE; goto out; } if (cinfo->output_scanline >= cinfo->output_height) { retval = TRUE; goto out; } } else { /* we're decompressing buffered (progressive) * so feed jpeg lib scanlines */ /* keep going until we've done all passes */ while (!jpeg_input_complete (cinfo)) { if (!context->in_output) { if (jpeg_start_output (cinfo, cinfo->input_scan_number)) { context->in_output = TRUE; context->dptr = context->pixbuf->pixels; } else break; } /* get scanlines from jpeg lib */ if (! gdk_pixbuf__jpeg_image_load_lines (context, error)) { retval = FALSE; goto out; } if (cinfo->output_scanline >= cinfo->output_height && jpeg_finish_output (cinfo)) context->in_output = FALSE; else break; } if (jpeg_input_complete (cinfo)) { /* did entire image */ retval = TRUE; goto out; } else continue; } } out: jpeg_destroy_exif_context (&exif_context); return retval; } /* Save */ #define TO_FUNCTION_BUF_SIZE 4096 typedef struct { struct jpeg_destination_mgr pub; JOCTET *buffer; GdkPixbufSaveFunc save_func; gpointer user_data; GError **error; } ToFunctionDestinationManager; void to_callback_init (j_compress_ptr cinfo) { ToFunctionDestinationManager *destmgr; destmgr = (ToFunctionDestinationManager*) cinfo->dest; destmgr->pub.next_output_byte = destmgr->buffer; destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE; } static void to_callback_do_write (j_compress_ptr cinfo, gsize length) { ToFunctionDestinationManager *destmgr; destmgr = (ToFunctionDestinationManager*) cinfo->dest; if (!destmgr->save_func ((gchar *)destmgr->buffer, length, destmgr->error, destmgr->user_data)) { struct error_handler_data *errmgr; errmgr = (struct error_handler_data *) cinfo->err; /* Use a default error message if the callback didn't set one, * which it should have. */ if (errmgr->error && *errmgr->error == NULL) { g_set_error_literal (errmgr->error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, "write function failed"); } siglongjmp (errmgr->setjmp_buffer, 1); g_assert_not_reached (); } } static boolean to_callback_empty_output_buffer (j_compress_ptr cinfo) { ToFunctionDestinationManager *destmgr; destmgr = (ToFunctionDestinationManager*) cinfo->dest; to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE); destmgr->pub.next_output_byte = destmgr->buffer; destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE; return TRUE; } void to_callback_terminate (j_compress_ptr cinfo) { ToFunctionDestinationManager *destmgr; destmgr = (ToFunctionDestinationManager*) cinfo->dest; to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE - destmgr->pub.free_in_buffer); } static gboolean real_save_jpeg (GdkPixbuf *pixbuf, gchar **keys, gchar **values, GError **error, gboolean to_callback, FILE *f, GdkPixbufSaveFunc save_func, gpointer user_data) { /* FIXME error handling is broken */ struct jpeg_compress_struct cinfo; guchar *buf = NULL; guchar *ptr; guchar *pixels = NULL; JSAMPROW *jbuf; int y = 0; volatile int quality = 75; /* default; must be between 0 and 100 */ int i, j; int w, h = 0; int rowstride = 0; int n_channels; struct error_handler_data jerr; ToFunctionDestinationManager to_callback_destmgr; int x_density = 0; int y_density = 0; gchar *icc_profile = NULL; gchar *data; gint retval = TRUE; gsize icc_profile_size = 0; to_callback_destmgr.buffer = NULL; if (keys && *keys) { gchar **kiter = keys; gchar **viter = values; while (*kiter) { if (strcmp (*kiter, "quality") == 0) { char *endptr = NULL; quality = strtol (*viter, &endptr, 10); if (endptr == *viter) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, _("JPEG quality must be a value between 0 and 100; value '%s' could not be parsed."), *viter); retval = FALSE; goto cleanup; } if (quality < 0 || quality > 100) { /* This is a user-visible error; * lets people skip the range-checking * in their app. */ g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, _("JPEG quality must be a value between 0 and 100; value '%d' is not allowed."), quality); retval = FALSE; goto cleanup; } } else if (strcmp (*kiter, "x-dpi") == 0) { char *endptr = NULL; x_density = strtol (*viter, &endptr, 10); if (endptr == *viter) x_density = -1; if (x_density <= 0 || x_density > 65535) { /* This is a user-visible error; * lets people skip the range-checking * in their app. */ g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, _("JPEG x-dpi must be a value between 1 and 65535; value '%s' is not allowed."), *viter); retval = FALSE; goto cleanup; } } else if (strcmp (*kiter, "y-dpi") == 0) { char *endptr = NULL; y_density = strtol (*viter, &endptr, 10); if (endptr == *viter) y_density = -1; if (y_density <= 0 || y_density > 65535) { /* This is a user-visible error; * lets people skip the range-checking * in their app. */ g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, _("JPEG y-dpi must be a value between 1 and 65535; value '%s' is not allowed."), *viter); retval = FALSE; goto cleanup; } } else if (strcmp (*kiter, "icc-profile") == 0) { /* decode from base64 */ icc_profile = (gchar*) g_base64_decode (*viter, &icc_profile_size); if (icc_profile_size < 127) { /* This is a user-visible error */ g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, _("Color profile has invalid length '%u'."), (guint) icc_profile_size); retval = FALSE; goto cleanup; } } else { g_warning ("Unrecognized parameter (%s) passed to JPEG saver.", *kiter); } ++kiter; ++viter; } } rowstride = gdk_pixbuf_get_rowstride (pixbuf); n_channels = gdk_pixbuf_get_n_channels (pixbuf); w = gdk_pixbuf_get_width (pixbuf); h = gdk_pixbuf_get_height (pixbuf); pixels = gdk_pixbuf_get_pixels (pixbuf); /* Guaranteed by the caller. */ g_assert (w >= 0); g_assert (h >= 0); g_assert (rowstride >= 0); g_assert (n_channels >= 0); /* Allocate a small buffer to convert image data, * and a larger buffer if doing to_callback save. */ buf = g_try_malloc (w * 3 * sizeof (guchar)); if (!buf) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Couldn't allocate memory for loading JPEG file")); retval = FALSE; goto cleanup; } if (to_callback) { to_callback_destmgr.buffer = g_try_malloc (TO_FUNCTION_BUF_SIZE); if (!to_callback_destmgr.buffer) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Couldn't allocate memory for loading JPEG file")); retval = FALSE; goto cleanup; } } /* set up error handling */ cinfo.err = jpeg_std_error (&(jerr.pub)); jerr.pub.error_exit = fatal_error_handler; jerr.pub.output_message = output_message_handler; jerr.error = error; if (sigsetjmp (jerr.setjmp_buffer, 1)) { jpeg_destroy_compress (&cinfo); retval = FALSE; goto cleanup; } /* setup compress params */ jpeg_create_compress (&cinfo); if (to_callback) { to_callback_destmgr.pub.init_destination = to_callback_init; to_callback_destmgr.pub.empty_output_buffer = to_callback_empty_output_buffer; to_callback_destmgr.pub.term_destination = to_callback_terminate; to_callback_destmgr.error = error; to_callback_destmgr.save_func = save_func; to_callback_destmgr.user_data = user_data; cinfo.dest = (struct jpeg_destination_mgr*) &to_callback_destmgr; } else { jpeg_stdio_dest (&cinfo, f); } cinfo.image_width = w; cinfo.image_height = h; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; /* set up jepg compression parameters */ jpeg_set_defaults (&cinfo); jpeg_set_quality (&cinfo, quality, TRUE); /* set density information */ if (x_density > 0 && y_density > 0) { cinfo.density_unit = 1; /* Dots per inch */ cinfo.X_density = x_density; cinfo.Y_density = y_density; } jpeg_start_compress (&cinfo, TRUE); /* write ICC profile data */ if (icc_profile != NULL) { /* optimise for the common case where only one APP2 segment is required */ if (icc_profile_size < 0xffef) { data = g_new (gchar, icc_profile_size + 14); memcpy (data, "ICC_PROFILE\000\001\001", 14); memcpy (data + 14, icc_profile, icc_profile_size); jpeg_write_marker (&cinfo, JPEG_APP0+2, (const JOCTET *) data, icc_profile_size + 14); g_free (data); } else { guint segments; guint size = 0xffef; guint offset; segments = (guint) ceilf ((gfloat) icc_profile_size / (gfloat) 0xffef); data = g_new (gchar, 0xffff); memcpy (data, "ICC_PROFILE\000", 12); data[13] = segments; for (i=0; i<=segments; i++) { data[12] = i; offset = 0xffef * i; /* last segment */ if (i == segments) size = icc_profile_size % 0xffef; memcpy (data + 14, icc_profile + offset, size); jpeg_write_marker (&cinfo, JPEG_APP0+2, (const JOCTET *) data, size + 14); } g_free (data); } } /* get the start pointer */ ptr = pixels; /* go one scanline at a time... and save */ i = 0; while (cinfo.next_scanline < cinfo.image_height) { /* convert scanline from ARGB to RGB packed */ for (j = 0; j < w; j++) memcpy (&(buf[j*3]), &(ptr[(gsize)i*rowstride + j*n_channels]), 3); /* write scanline */ jbuf = (JSAMPROW *)(&buf); if (jpeg_write_scanlines (&cinfo, jbuf, 1) == 0) { jpeg_destroy_compress (&cinfo); retval = FALSE; goto cleanup; } i++; y++; } /* finish off */ jpeg_finish_compress (&cinfo); jpeg_destroy_compress(&cinfo); cleanup: g_free (buf); g_free (to_callback_destmgr.buffer); g_free (icc_profile); return retval; } static gboolean gdk_pixbuf__jpeg_image_save (FILE *f, GdkPixbuf *pixbuf, gchar **keys, gchar **values, GError **error) { return real_save_jpeg (pixbuf, keys, values, error, FALSE, f, NULL, NULL); } static gboolean gdk_pixbuf__jpeg_image_save_to_callback (GdkPixbufSaveFunc save_func, gpointer user_data, GdkPixbuf *pixbuf, gchar **keys, gchar **values, GError **error) { return real_save_jpeg (pixbuf, keys, values, error, TRUE, NULL, save_func, user_data); } static gboolean gdk_pixbuf__jpeg_is_save_option_supported (const gchar *option_key) { if (g_strcmp0 (option_key, "quality") == 0 || g_strcmp0 (option_key, "icc-profile") == 0) return TRUE; return FALSE; } #ifndef INCLUDE_jpeg #define MODULE_ENTRY(function) G_MODULE_EXPORT void function #else #define MODULE_ENTRY(function) void _gdk_pixbuf__jpeg_ ## function #endif MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module) { module->load = gdk_pixbuf__jpeg_image_load; module->begin_load = gdk_pixbuf__jpeg_image_begin_load; module->stop_load = gdk_pixbuf__jpeg_image_stop_load; module->load_increment = gdk_pixbuf__jpeg_image_load_increment; module->save = gdk_pixbuf__jpeg_image_save; module->save_to_callback = gdk_pixbuf__jpeg_image_save_to_callback; module->is_save_option_supported = gdk_pixbuf__jpeg_is_save_option_supported; } MODULE_ENTRY (fill_info) (GdkPixbufFormat *info) { static const GdkPixbufModulePattern signature[] = { { "\xff\xd8", NULL, 100 }, { NULL, NULL, 0 } }; static const gchar *mime_types[] = { "image/jpeg", NULL }; static const gchar *extensions[] = { "jpeg", "jpe", "jpg", NULL }; info->name = "jpeg"; info->signature = (GdkPixbufModulePattern *) signature; info->description = NC_("image format", "JPEG"); info->mime_types = (gchar **) mime_types; info->extensions = (gchar **) extensions; info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE; info->license = "LGPL"; }