From: Michael J. Chudobiak Date: Fri, 18 May 2007 14:19:47 +0000 (+0000) Subject: Bump libtiff requirement to 3.6.0, by requiring presence of X-Git-Url: http://pileus.org/git/?p=~andy%2Fgtk;a=commitdiff_plain;h=d11535037e623356289068d15fa24a64c33d96b5 Bump libtiff requirement to 3.6.0, by requiring presence of 2007-05-18 Michael J. Chudobiak * INSTALL.in: * README.in: * configure.in: Bump libtiff requirement to 3.6.0, by requiring presence of TIFFReadRGBAImageOriented. * gdk-pixbuf-scaled-anim.c: (get_scaled_pixbuf): Preserve pixbuf options when generating a new scaled pixbuf. * io-jpeg.c: (get_orientation), (gdk_pixbuf__jpeg_image_load), (gdk_pixbuf__jpeg_image_load_increment): Read the exif orientation tag and associate it with the "orientation" pixbuf option. Renders libexif unnecessary in some applications. * io-tiff.c: (tiff_image_parse): Read the tiff orientation tag, compensate for the partial rotations performed by libtiff, and generate an "orientation" option for the pixbuf. svn path=/trunk/; revision=17863 --- diff --git a/ChangeLog b/ChangeLog index fa4a83404..5fee42cbf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2007-05-18 Michael J. Chudobiak + + * INSTALL.in: + * README.in: + * configure.in: Bump libtiff requirement to 3.6.0, by requiring + presence of TIFFReadRGBAImageOriented. + + * gdk-pixbuf-scaled-anim.c: (get_scaled_pixbuf): + Preserve pixbuf options when generating a new scaled pixbuf. + + * io-jpeg.c: (get_orientation), (gdk_pixbuf__jpeg_image_load), + (gdk_pixbuf__jpeg_image_load_increment): Read the exif + orientation tag and associate it with the "orientation" pixbuf + option. Renders libexif unnecessary in some applications. + + * io-tiff.c: (tiff_image_parse): Read the tiff orientation tag, + compensate for the partial rotations performed by libtiff, + and generate an "orientation" option for the pixbuf. + 2007-05-18 Carlos Garnacho * gtk/gtkexpander.c: remove c++ style comment. diff --git a/INSTALL b/INSTALL index 2be180e9a..cf80c2fae 100644 --- a/INSTALL +++ b/INSTALL @@ -15,6 +15,8 @@ GTK+ requires the following packages: http://www.libpng.org/ http://www.ijg.org/ + libtiff must be version 3.6.0 or higher. + Simple install procedure ======================== diff --git a/INSTALL.in b/INSTALL.in index 9d149238d..742d78280 100644 --- a/INSTALL.in +++ b/INSTALL.in @@ -15,6 +15,8 @@ GTK+ requires the following packages: http://www.libpng.org/ http://www.ijg.org/ + libtiff must be version 3.6.0 or higher. + Simple install procedure ======================== diff --git a/README.in b/README.in index cf6b373bc..302696e4a 100644 --- a/README.in +++ b/README.in @@ -37,6 +37,8 @@ Release notes for 2.12 Release notes for 2.10 ====================== +* The tiff loader now requires libtiff 3.6.0 or later. + * The hexadecimal Unicode input feature has been reworked. It no longer blocks the use of the sixteen Ctrl-Shift- key sequences. Now it only uses Ctrl-Shift-u. diff --git a/configure.in b/configure.in index 99371439c..f95973663 100644 --- a/configure.in +++ b/configure.in @@ -747,7 +747,7 @@ AC_ARG_WITH(libtiff, dnl Test for libtiff if test x$with_libtiff != xno && test -z "$LIBTIFF"; then - AC_CHECK_LIB(tiff, TIFFReadScanline, + AC_CHECK_LIB(tiff, TIFFReadRGBAImageOriented, [AC_CHECK_HEADER(tiffio.h, TIFF='tiff'; LIBTIFF='-ltiff', AC_MSG_WARN(*** TIFF loader will not be built (TIFF header files not found) ***))], diff --git a/gdk-pixbuf/ChangeLog b/gdk-pixbuf/ChangeLog index df7c7461c..029793c14 100644 --- a/gdk-pixbuf/ChangeLog +++ b/gdk-pixbuf/ChangeLog @@ -1,3 +1,17 @@ +2007-05-18 Michael J. Chudobiak + + * gdk-pixbuf-scaled-anim.c: (get_scaled_pixbuf): + Preserve pixbuf options when generating a new scaled pixbuf. + + * io-jpeg.c: (get_orientation), (gdk_pixbuf__jpeg_image_load), + (gdk_pixbuf__jpeg_image_load_increment): Read the exif + orientation tag and associate it with the "orientation" pixbuf + option. Renders libexif unnecessary in some applications. + + * io-tiff.c: (tiff_image_parse): Read the tiff orientation tag, + compensate for the partial rotations performed by libtiff, + and generate an "orientation" option for the pixbuf. + 2007-04-29 Matthias Clasen * io-jpeg.c: Remove a pointless check from the previous diff --git a/gdk-pixbuf/gdk-pixbuf-scaled-anim.c b/gdk-pixbuf/gdk-pixbuf-scaled-anim.c index 1a848f3c6..26f105bd3 100644 --- a/gdk-pixbuf/gdk-pixbuf-scaled-anim.c +++ b/gdk-pixbuf/gdk-pixbuf-scaled-anim.c @@ -121,14 +121,32 @@ static GdkPixbuf * get_scaled_pixbuf (GdkPixbufScaledAnim *scaled, GdkPixbuf *pixbuf) { + GQuark quark; + gchar **options; + if (scaled->current) g_object_unref (scaled->current); + /* Preserve the options associated with the original pixbuf + (if present), mostly so that client programs can use the + "orientation" option (if present) to rotate the image + appropriately. gdk_pixbuf_scale_simple (and most other + gdk transform operations) does not preserve the attached + options when returning a new pixbuf. */ + + quark = g_quark_from_static_string ("gdk_pixbuf_options"); + options = g_object_get_qdata (G_OBJECT (pixbuf), quark); + + /* Get a new scaled pixbuf */ scaled->current = gdk_pixbuf_scale_simple (pixbuf, (int) (gdk_pixbuf_get_width (pixbuf) * scaled->xscale), (int) (gdk_pixbuf_get_height (pixbuf) * scaled->yscale), GDK_INTERP_BILINEAR); + /* Copy the original pixbuf options to the scaled pixbuf */ + if (options && scaled->current) + g_object_set_qdata (G_OBJECT (scaled->current), quark, g_strdupv (options)); + return scaled->current; } diff --git a/gdk-pixbuf/io-jpeg.c b/gdk-pixbuf/io-jpeg.c index 1df2b813c..6a1fde0b1 100644 --- a/gdk-pixbuf/io-jpeg.c +++ b/gdk-pixbuf/io-jpeg.c @@ -277,11 +277,161 @@ colorspace_name (const J_COLOR_SPACE jpeg_color_space) } } + +const char leth[] = {0x49, 0x49, 0x2a, 0x00}; // Little endian TIFF header +const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; // Big endian TIFF header +const char types[] = {0x00, 0x01, 0x01, 0x02, 0x04, 0x08, 0x00, + 0x08, 0x00, 0x04, 0x08}; // size in bytes for EXIF types + +#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) + +#define EXIF_JPEG_MARKER JPEG_APP0+1 +#define EXIF_IDENT_STRING "Exif\000\000" + +static gint +get_orientation (j_decompress_ptr cinfo) +{ + /* This function looks through the meta data in the libjpeg decompress structure to + determine if an EXIF Orientation tag is present and if so return its value (1-8). + If no EXIF Orientation tag is found 0 (zero) is returned. */ + + guint i; /* index into working buffer */ + guint orient_tag_id; /* endianed version of orientation tag ID */ + guint ret; /* Return value */ + guint offset; /* de-endianed offset in various situations */ + guint tags; /* number of tags in current ifd */ + guint type; /* de-endianed type of tag used as index into types[] */ + guint count; /* de-endianed count of elements in a tag */ + guint tiff = 0; /* offset to active tiff header */ + guint endian = 0; /* detected endian of data */ + + jpeg_saved_marker_ptr exif_marker; /* Location of the Exif APP1 marker */ + jpeg_saved_marker_ptr cmarker; /* Location to check for Exif APP1 marker */ + + /* check for Exif marker (also called the APP1 marker) */ + exif_marker = NULL; + cmarker = cinfo->marker_list; + while (cmarker) { + if (cmarker->marker == EXIF_JPEG_MARKER) { + /* The Exif APP1 marker should contain a unique + identification string ("Exif\0\0"). Check for it. */ + if (!memcmp (cmarker->data, EXIF_IDENT_STRING, 6)) { + exif_marker = cmarker; + } + } + cmarker = cmarker->next; + } + + /* Did we find the Exif APP1 marker? */ + if (exif_marker == NULL) + return 0; + + /* Do we have enough data? */ + if (exif_marker->data_length < 32) + return 0; + + /* Check for TIFF header and catch endianess */ + i = 0; + + /* 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 + + exif_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. + */ + + while (i < 16) { + + /* Little endian TIFF header */ + if (memcmp (&exif_marker->data[i], leth, 4) == 0){ + endian = G_LITTLE_ENDIAN; + } + + /* Big endian TIFF header */ + else if (memcmp (&exif_marker->data[i], beth, 4) == 0){ + endian = G_BIG_ENDIAN; + } + + /* Keep looking through buffer */ + else { + i++; + continue; + } + /* We have found either big or little endian TIFF header */ + tiff = i; + break; + } + + /* So did we find a TIFF header or did we just hit end of buffer? */ + if (tiff == 0) + return 0; + + /* Endian the orientation tag ID, to locate it more easily */ + orient_tag_id = ENDIAN16_IT(0x112); + + /* Read out the offset pointer to IFD0 */ + offset = DE_ENDIAN32(*((unsigned long*) (&exif_marker->data[i] + 4))); + i = i + offset; + + /* Check that we still are within the buffer and can read the tag count */ + if ((i + 2) > exif_marker->data_length) + return 0; + + /* 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_ENDIAN16(*((unsigned short*) (&exif_marker->data[i]))); + i = i + 2; + + /* 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. */ + if ((i + tags * 12) > exif_marker->data_length) + return 0; + + /* Check through IFD0 for tags of interest */ + while (tags--){ + type = DE_ENDIAN16(*((unsigned short*)(&exif_marker->data[i + 2]))); + count = DE_ENDIAN32(*((unsigned long*) (&exif_marker->data[i + 4]))); + + /* Is this the orientation tag? */ + if (memcmp (&exif_marker->data[i], (char *) &orient_tag_id, 2) == 0){ + + /* Check that type and count fields are OK. The orientation field + will consist of a single (count=1) 2-byte integer (type=3). */ + if (type != 3 || count != 1) return 0; + + /* Return the orientation value. Within the 12-byte block, the + pointer to the actual data is at offset 8. */ + ret = DE_ENDIAN16(*((unsigned short*) (&exif_marker->data[i + 8]))); + return ret <= 8 ? ret : 0; + } + /* move the pointer to the next 12-byte tag field. */ + i = i + 12; + } + + return 0; /* No EXIF Orientation tag found */ +} + + /* Shared library entry point */ static GdkPixbuf * gdk_pixbuf__jpeg_image_load (FILE *f, GError **error) { - gint i; + gint i; + int is_otag; + char otag_str[5]; GdkPixbuf * volatile pixbuf = NULL; guchar *dptr; guchar *lines[4]; /* Used to expand rows, via rec_outbuf_height, @@ -332,7 +482,12 @@ gdk_pixbuf__jpeg_image_load (FILE *f, GError **error) 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, EXIF_JPEG_MARKER, 0xffff); jpeg_read_header (&cinfo, TRUE); + + /* check for orientation tag */ + is_otag = get_orientation (&cinfo); + jpeg_start_decompress (&cinfo); cinfo.do_fancy_upsampling = FALSE; cinfo.do_block_smoothing = FALSE; @@ -357,6 +512,13 @@ gdk_pixbuf__jpeg_image_load (FILE *f, GError **error) return NULL; } + /* if orientation tag was found set an option to remember its value */ + if (is_otag) { + snprintf (otag_str, sizeof (otag_str), "%d", is_otag); + gdk_pixbuf_set_option (pixbuf, "orientation", otag_str); + } + + dptr = pixbuf->pixels; /* decompress all the lines, a few at a time */ @@ -626,14 +788,16 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data, GError **error) { JpegProgContext *context = (JpegProgContext *)data; - struct jpeg_decompress_struct *cinfo; - my_src_ptr src; - guint num_left, num_copy; - guint last_bytes_left; - guint spinguard; - gboolean first; - const guchar *bufhd; - gint width, height; + struct jpeg_decompress_struct *cinfo; + my_src_ptr src; + guint num_left, num_copy; + guint last_bytes_left; + guint spinguard; + gboolean first; + const guchar *bufhd; + gint width, height; + int is_otag; + char otag_str[5]; g_return_val_if_fail (context != NULL, FALSE); g_return_val_if_fail (buf != NULL, FALSE); @@ -707,7 +871,8 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data, /* try to load jpeg header */ if (!context->got_header) { int rc; - + + jpeg_save_markers (cinfo, EXIF_JPEG_MARKER, 0xffff); rc = jpeg_read_header (cinfo, TRUE); context->src_initialized = TRUE; @@ -715,7 +880,10 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data, continue; context->got_header = TRUE; - + + /* check for orientation tag */ + is_otag = get_orientation (cinfo); + width = cinfo->image_width; height = cinfo->image_height; if (context->size_func) { @@ -751,7 +919,13 @@ gdk_pixbuf__jpeg_image_load_increment (gpointer data, _("Couldn't allocate memory for loading JPEG file")); return FALSE; } - + + /* if orientation tag was found set an option to remember its value */ + if (is_otag) { + snprintf (otag_str, sizeof (otag_str), "%d", is_otag); + gdk_pixbuf_set_option (context->pixbuf, "orientation", otag_str); + } + /* Use pixbuf buffer to store decompressed data */ context->dptr = context->pixbuf->pixels; diff --git a/gdk-pixbuf/io-tiff.c b/gdk-pixbuf/io-tiff.c index 79e8ef2c6..a9863e6cf 100644 --- a/gdk-pixbuf/io-tiff.c +++ b/gdk-pixbuf/io-tiff.c @@ -141,27 +141,14 @@ static void free_buffer (guchar *pixels, gpointer data) g_free (pixels); } -#if TIFFLIB_VERSION >= 20031226 -static gboolean tifflibversion (int *major, int *minor, int *revision) -{ - if (sscanf (TIFFGetVersion(), - "LIBTIFF, Version %d.%d.%d", - major, minor, revision) < 3) - return FALSE; - - return TRUE; -} -#endif - static GdkPixbuf * tiff_image_parse (TIFF *tiff, TiffContext *context, GError **error) { guchar *pixels = NULL; gint width, height, rowstride, bytes; GdkPixbuf *pixbuf; -#if TIFFLIB_VERSION >= 20031226 - gint major, minor, revision; -#endif + uint16 orientation = 0; + uint16 transform = 0; /* We're called with the lock held. */ @@ -238,95 +225,73 @@ tiff_image_parse (TIFF *tiff, TiffContext *context, GError **error) return NULL; } + /* Set the "orientation" key associated with this image. libtiff + orientation handling is odd, so further processing is required + by higher-level functions based on this tag. If the embedded + orientation tag is 1-4, libtiff flips/mirrors the image as + required, and no client processing is required - so we report + no orientation. Orientations 5-8 require rotations which would + swap the width and height of the image. libtiff does not do this. + Instead it interprets orientations 5-8 the same as 1-4. + See http://bugzilla.remotesensing.org/show_bug.cgi?id=1548. + To correct for this, the client must apply the transform normally + used for orientation 5 to both orientations 5 and 7, and apply + the transform normally used for orientation 7 for both + orientations 6 and 8. Then everythings works out OK! */ + + TIFFGetField (tiff, TIFFTAG_ORIENTATION, &orientation); + + switch (orientation) { + case 5: + case 7: + transform = 5; + break; + case 6: + case 8: + transform = 7; + break; + default: + transform = 0; + break; + } + + if (transform > 0 ) { + gchar str[5]; + snprintf (str, sizeof (str), "%d", transform); + gdk_pixbuf_set_option (pixbuf, "orientation", str); + } + if (context && context->prepare_func) (* context->prepare_func) (pixbuf, NULL, context->user_data); -#if TIFFLIB_VERSION >= 20031226 - if (tifflibversion(&major, &minor, &revision) && major == 3 && - (minor > 6 || (minor == 6 && revision > 0))) { - if (!TIFFReadRGBAImageOriented (tiff, width, height, (uint32 *)pixels, ORIENTATION_TOPLEFT, 1) || global_error) - { - tiff_set_error (error, - GDK_PIXBUF_ERROR_FAILED, - _("Failed to load RGB data from TIFF file")); - g_object_unref (pixbuf); - return NULL; - } + if (!TIFFReadRGBAImageOriented (tiff, width, height, (uint32 *)pixels, ORIENTATION_TOPLEFT, 1) || global_error) { + tiff_set_error (error, + GDK_PIXBUF_ERROR_FAILED, + _("Failed to load RGB data from TIFF file")); + g_object_unref (pixbuf); + return NULL; + } #if G_BYTE_ORDER == G_BIG_ENDIAN - /* Turns out that the packing used by TIFFRGBAImage depends on - * the host byte order... - */ - while (pixels < pixbuf->pixels + bytes) { - uint32 pixel = *(uint32 *)pixels; - int r = TIFFGetR(pixel); - int g = TIFFGetG(pixel); - int b = TIFFGetB(pixel); - int a = TIFFGetA(pixel); - *pixels++ = r; - *pixels++ = g; - *pixels++ = b; - *pixels++ = a; - } -#endif - } - else + /* Turns out that the packing used by TIFFRGBAImage depends on + * the host byte order... + */ + while (pixels < pixbuf->pixels + bytes) { + uint32 pixel = *(uint32 *)pixels; + int r = TIFFGetR(pixel); + int g = TIFFGetG(pixel); + int b = TIFFGetB(pixel); + int a = TIFFGetA(pixel); + *pixels++ = r; + *pixels++ = g; + *pixels++ = b; + *pixels++ = a; + } #endif - { - uint32 *rast, *tmp_rast; - gint x, y; - guchar *tmppix; - - /* Yes, it needs to be _TIFFMalloc... */ - rast = (uint32 *) _TIFFmalloc (width * height * sizeof (uint32)); - if (!rast) { - g_set_error (error, - GDK_PIXBUF_ERROR, - GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, - _("Insufficient memory to open TIFF file")); - g_object_unref (pixbuf); - - return NULL; - } - if (!TIFFReadRGBAImage (tiff, width, height, rast, 1) || global_error) { - tiff_set_error (error, - GDK_PIXBUF_ERROR_FAILED, - _("Failed to load RGB data from TIFF file")); - g_object_unref (pixbuf); - _TIFFfree (rast); - - return NULL; - } - - pixels = gdk_pixbuf_get_pixels (pixbuf); - - g_assert (pixels); - - tmppix = pixels; - - for (y = 0; y < height; y++) { - /* Unexplainable...are tiffs backwards? */ - /* Also looking at the GIMP plugin, this - * whole reading thing can be a bit more - * robust. - */ - tmp_rast = rast + ((height - y - 1) * width); - for (x = 0; x < width; x++) { - tmppix[0] = TIFFGetR (*tmp_rast); - tmppix[1] = TIFFGetG (*tmp_rast); - tmppix[2] = TIFFGetB (*tmp_rast); - tmppix[3] = TIFFGetA (*tmp_rast); - tmp_rast++; - tmppix += 4; - } - } - - _TIFFfree (rast); - } if (context && context->update_func) (* context->update_func) (pixbuf, 0, 0, width, height, context->user_data); - + return pixbuf; }