Author: benny
Date: 2007-01-14 23:48:20 +0000 (Sun, 14 Jan 2007)
New Revision: 24475
Modified:
thunar/trunk/ChangeLog
thunar/trunk/thunar-vfs/thunar-vfs-thumb-jpeg.c
thunar/trunk/thunar-vfs/thunar-vfs-thumb.c
Log:
2007-01-15 Benedikt Meurer <[EMAIL PROTECTED]>
* thunar-vfs/thunar-vfs-thumb-jpeg.c: Use Exif embedded thumbnails
if available, which reduces the amount of data that must be
retrieved from a camera to around 50k instead of the whole JPEG
image, which can be up to several mega bytes.
* thunar-vfs/thunar-vfs-thumb.c
(thunar_vfs_thumb_factory_generate_thumbnail): Do not scale down
thumbnails extracted from JPEG images that use the rather common
size 160x120 pixels, to save some time and avoid messing up the
thumbnail.
Modified: thunar/trunk/ChangeLog
===================================================================
--- thunar/trunk/ChangeLog 2007-01-14 21:49:25 UTC (rev 24474)
+++ thunar/trunk/ChangeLog 2007-01-14 23:48:20 UTC (rev 24475)
@@ -1,3 +1,15 @@
+2007-01-15 Benedikt Meurer <[EMAIL PROTECTED]>
+
+ * thunar-vfs/thunar-vfs-thumb-jpeg.c: Use Exif embedded thumbnails
+ if available, which reduces the amount of data that must be
+ retrieved from a camera to around 50k instead of the whole JPEG
+ image, which can be up to several mega bytes.
+ * thunar-vfs/thunar-vfs-thumb.c
+ (thunar_vfs_thumb_factory_generate_thumbnail): Do not scale down
+ thumbnails extracted from JPEG images that use the rather common
+ size 160x120 pixels, to save some time and avoid messing up the
+ thumbnail.
+
2007-01-14 Benedikt Meurer <[EMAIL PROTECTED]>
* thunar-vfs/thunar-vfs-exec.c: Improve startup notification handling,
Modified: thunar/trunk/thunar-vfs/thunar-vfs-thumb-jpeg.c
===================================================================
--- thunar/trunk/thunar-vfs/thunar-vfs-thumb-jpeg.c 2007-01-14 21:49:25 UTC
(rev 24474)
+++ thunar/trunk/thunar-vfs/thunar-vfs-thumb-jpeg.c 2007-01-14 23:48:20 UTC
(rev 24475)
@@ -1,6 +1,6 @@
/* $Id$ */
/*-
- * Copyright (c) 2005 Benedikt Meurer <[EMAIL PROTECTED]>
+ * Copyright (c) 2005-2007 Benedikt Meurer <[EMAIL PROTECTED]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -38,6 +38,9 @@
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
#ifdef HAVE_SETJMP_H
#include <setjmp.h>
#endif
@@ -45,6 +48,9 @@
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
@@ -169,57 +175,24 @@
p[3] = 255;
}
}
-#endif
-
-/**
- * thunar_vfs_thumb_jpeg_load:
- * @path
- * @size
- *
- * Return value:
- **/
-GdkPixbuf*
-thunar_vfs_thumb_jpeg_load (const gchar *path,
- gint size)
+
+static GdkPixbuf*
+tvtj_jpeg_load (const JOCTET *content,
+ gsize length,
+ gint size)
{
-#if defined(HAVE_LIBJPEG) && defined(HAVE_MMAP)
struct jpeg_decompress_struct cinfo;
struct jpeg_source_mgr source;
TvtjErrorHandler handler;
- struct stat statb;
- JOCTET *content;
guchar *lines[1];
guchar *buffer = NULL;
guchar *pixels = NULL;
guchar *p;
gint out_num_components;
- gint fd;
gint n;
- /* try to open the file at the given path */
- fd = open (path, O_RDONLY);
- if (G_UNLIKELY (fd < 0))
- return NULL;
-
- /* determine the status of the file */
- if (G_UNLIKELY (fstat (fd, &statb) < 0 || statb.st_size == 0))
- {
- close (fd);
- return NULL;
- }
-
- /* try to mmap the file */
- content = mmap (NULL, statb.st_size, PROT_READ, MAP_SHARED, fd, 0);
-
- /* we can safely close the file now */
- close (fd);
-
- /* verify that the mmap was successful */
- if (G_UNLIKELY (content == (JOCTET *) MAP_FAILED))
- return NULL;
-
/* setup JPEG error handling */
cinfo.err = jpeg_std_error (&handler.mgr);
handler.mgr.error_exit = fatal_error_handler;
@@ -228,7 +201,7 @@
goto error;
/* setup the source */
- source.bytes_in_buffer = statb.st_size;
+ source.bytes_in_buffer = length;
source.next_input_byte = content;
source.init_source = (gpointer) exo_noop;
source.fill_input_buffer = tvtj_fill_input_buffer;
@@ -311,9 +284,6 @@
jpeg_finish_decompress (&cinfo);
jpeg_destroy_decompress (&cinfo);
- /* unmap the file content */
- munmap (content, statb.st_size);
-
/* generate a pixbuf for the pixel data */
return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB,
(cinfo.out_color_components == 4), 8,
@@ -323,11 +293,353 @@
error:
jpeg_destroy_decompress (&cinfo);
- munmap (content, statb.st_size);
g_free (buffer);
g_free (pixels);
+ return NULL;
+}
+
+
+
+typedef struct
+{
+ const guchar *data_ptr;
+ guint data_len;
+
+ guint thumb_compression;
+ union
+ {
+ struct /* thumbnail JPEG */
+ {
+ guint thumb_jpeg_length;
+ guint thumb_jpeg_offset;
+ };
+ struct /* thumbnail TIFF */
+ {
+ guint thumb_tiff_length;
+ guint thumb_tiff_offset;
+ guint thumb_tiff_interp;
+ guint thumb_tiff_height;
+ guint thumb_tiff_width;
+ };
+ };
+
+ gboolean big_endian;
+} TvtjExif;
+
+
+
+static guint
+tvtj_exif_get_ushort (const TvtjExif *exif,
+ const gchar *data)
+{
+ if (G_UNLIKELY (exif->big_endian))
+ return GUINT16_FROM_BE (*((const guint16 *) data));
+ else
+ return GUINT16_FROM_LE (*((const guint16 *) data));
+}
+
+
+
+static guint
+tvtj_exif_get_ulong (const TvtjExif *exif,
+ const gchar *data)
+{
+ if (G_UNLIKELY (exif->big_endian))
+ return GUINT32_FROM_BE (*((const guint32 *) data));
+ else
+ return GUINT32_FROM_LE (*((const guint32 *) data));
+}
+
+
+
+static void
+tvtj_exif_parse_ifd (TvtjExif *exif,
+ const guchar *ifd_ptr,
+ guint ifd_len)
+{
+ const guchar *subifd_ptr;
+ guint subifd_off;
+ guint value;
+ guint tag;
+ guint n;
+
+ /* make sure we have a valid IFD here */
+ if (G_UNLIKELY (ifd_len < 2))
+ return;
+
+ /* determine the number of entries */
+ n = tvtj_exif_get_ushort (exif, ifd_ptr);
+
+ /* advance to the IFD content */
+ ifd_ptr += 2;
+ ifd_len -= 2;
+
+ /* validate the number of entries */
+ if (G_UNLIKELY (n * 12 > ifd_len))
+ n = ifd_len / 12;
+
+ /* process all IFD entries */
+ for (; n > 0; ifd_ptr += 12, --n)
+ {
+ /* determine the tag of this entry */
+ tag = tvtj_exif_get_ushort (exif, ifd_ptr);
+ if (tag == 0x8769 || tag == 0xa005)
+ {
+ /* check if we have a valid sub IFD offset here */
+ subifd_off = tvtj_exif_get_ulong (exif, ifd_ptr + 8);
+ subifd_ptr = exif->data_ptr + subifd_off;
+ if (G_LIKELY (subifd_off < exif->data_len))
+ {
+ /* process the sub IFD recursively */
+ tvtj_exif_parse_ifd (exif, subifd_ptr, exif->data_len -
subifd_off);
+ }
+ }
+ else if (tag == 0x0103)
+ {
+ /* verify that we have an ushort here (format 3) */
+ if (tvtj_exif_get_ushort (exif, ifd_ptr + 2) == 3)
+ {
+ /* determine the thumbnail compression */
+ exif->thumb_compression = tvtj_exif_get_ushort (exif, ifd_ptr +
8);
+ }
+ }
+ else if (tag == 0x0100 || tag == 0x0101 || tag == 0x0106 || tag ==
0x0111 || tag == 0x0117)
+ {
+ /* this can be either ushort or ulong */
+ if (tvtj_exif_get_ushort (exif, ifd_ptr + 2) == 3)
+ value = tvtj_exif_get_ushort (exif, ifd_ptr + 8);
+ else if (tvtj_exif_get_ushort (exif, ifd_ptr + 2) == 4)
+ value = tvtj_exif_get_ulong (exif, ifd_ptr + 8);
+ else
+ value = 0;
+
+ /* and remember it appropriately */
+ if (tag == 0x0100)
+ exif->thumb_tiff_width = value;
+ else if (tag == 0x0100)
+ exif->thumb_tiff_height = value;
+ else if (tag == 0x0106)
+ exif->thumb_tiff_interp = value;
+ else if (tag == 0x0111)
+ exif->thumb_tiff_offset = value;
+ else
+ exif->thumb_tiff_length = value;
+ }
+ else if (tag == 0x0201 || tag == 0x0202)
+ {
+ /* verify that we have an ulong here (format 4) */
+ if (tvtj_exif_get_ushort (exif, ifd_ptr + 2) == 4)
+ {
+ /* determine the value (thumbnail JPEG offset or length) */
+ value = tvtj_exif_get_ulong (exif, ifd_ptr + 8);
+
+ /* and remember it appropriately */
+ if (G_LIKELY (tag == 0x201))
+ exif->thumb_jpeg_offset = value;
+ else
+ exif->thumb_jpeg_length = value;
+ }
+ }
+ }
+
+ /* check for link to next IFD */
+ subifd_off = tvtj_exif_get_ulong (exif, ifd_ptr);
+ if (subifd_off != 0 && subifd_off < exif->data_len)
+ {
+ /* parse next IFD recursively as well */
+ tvtj_exif_parse_ifd (exif, exif->data_ptr + subifd_off, exif->data_len -
subifd_off);
+ }
+}
+
+
+
+static GdkPixbuf*
+tvtj_exif_extract_thumbnail (const guchar *data,
+ guint length,
+ gint size)
+{
+ TvtjExif exif;
+ guint offset;
+
+ /* make sure we have enough data */
+ if (G_UNLIKELY (length < 6 + 8))
+ return NULL;
+
+ /* validate Exif header */
+ if (memcmp (data, "Exif\0\0", 6) != 0)
+ return NULL;
+
+ /* advance to TIFF header */
+ data += 6;
+ length -= 6;
+
+ /* setup Exif data struct */
+ memset (&exif, 0, sizeof (exif));
+ exif.data_ptr = data;
+ exif.data_len = length;
+
+ /* determine byte order */
+ if (memcmp (data, "II", 2) == 0)
+ exif.big_endian = FALSE;
+ else if (memcmp (data, "MM", 2) == 0)
+ exif.big_endian = TRUE;
+ else
+ return NULL;
+
+ /* validate the TIFF header */
+ if (tvtj_exif_get_ushort (&exif, data + 2) != 0x2a)
+ return NULL;
+
+ /* determine the first IFD offset */
+ offset = tvtj_exif_get_ulong (&exif, data + 4);
+
+ /* validate the offset */
+ if (G_LIKELY (offset < length))
+ {
+ /* parse the first IFD (recursively parses the remaining...) */
+ tvtj_exif_parse_ifd (&exif, data + offset, length - offset);
+
+ /* check thumbnail compression type */
+ if (G_LIKELY (exif.thumb_compression == 6)) /* JPEG */
+ {
+ /* check if we have a valid thumbnail JPEG */
+ if (exif.thumb_jpeg_offset > 0 && exif.thumb_jpeg_length > 0
+ && exif.thumb_jpeg_offset + exif.thumb_jpeg_length <= length)
+ {
+ /* try to load the embedded thumbnail JPEG */
+ return tvtj_jpeg_load (data + exif.thumb_jpeg_offset,
exif.thumb_jpeg_length, size);
+ }
+ }
+ else if (exif.thumb_compression == 1) /* Uncompressed */
+ {
+ /* check if we have a valid thumbnail (current only RGB
interpretations) */
+ if (G_LIKELY (exif.thumb_tiff_interp == 2)
+ && exif.thumb_tiff_offset > 0 && exif.thumb_tiff_length > 0
+ && exif.thumb_tiff_offset + exif.thumb_tiff_length <= length
+ && exif.thumb_tiff_height * exif.thumb_tiff_width ==
exif.thumb_tiff_length)
+ {
+ /* plain RGB data, just what we need for a GdkPixbuf */
+ return gdk_pixbuf_new_from_data (g_memdup (data +
exif.thumb_tiff_offset, exif.thumb_tiff_length),
+ GDK_COLORSPACE_RGB, FALSE, 8,
exif.thumb_tiff_width,
+ exif.thumb_tiff_height,
exif.thumb_tiff_width,
+ (GdkPixbufDestroyNotify)
g_free, NULL);
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+
+static GdkPixbuf*
+tvtj_jpeg_load_thumbnail (const JOCTET *content,
+ gsize length,
+ gint size)
+{
+ guint marker_len;
+ guint marker;
+ gsize n;
+
+ /* valid JPEG headers begin with SOI (Start Of Image) */
+ if (G_LIKELY (length >= 2 && content[0] == 0xff && content[1] == 0xd8))
+ {
+ /* search for an EXIF marker */
+ for (length -= 2, n = 2; n < length; )
+ {
+ /* check for valid marker start */
+ if (G_UNLIKELY (content[n++] != 0xff))
+ break;
+
+ /* determine the next marker */
+ marker = content[n];
+
+ /* skip additional padding */
+ if (G_UNLIKELY (marker == 0xff))
+ continue;
+
+ /* stop at SOS marker */
+ if (marker == 0xda)
+ break;
+
+ /* advance */
+ ++n;
+
+ /* check if valid */
+ if (G_UNLIKELY (n + 2 >= length))
+ break;
+
+ /* determine the marker length */
+ marker_len = (content[n] << 8) | content[n + 1];
+
+ /* check if we have an exif marker here */
+ if (marker == 0xe1 && n + marker_len <= length)
+ {
+ /* try to extract the Exif thumbnail */
+ return tvtj_exif_extract_thumbnail (content + n + 2, marker_len
- 2, size);
+ }
+
+ /* try next one then */
+ n += marker_len;
+ }
+ }
+
+ return NULL;
+}
#endif
+
+
+/**
+ * thunar_vfs_thumb_jpeg_load:
+ * @path
+ * @size
+ *
+ * Return value:
+ **/
+GdkPixbuf*
+thunar_vfs_thumb_jpeg_load (const gchar *path,
+ gint size)
+{
+#if defined(HAVE_LIBJPEG) && defined(HAVE_MMAP)
+ struct stat statb;
+ GdkPixbuf *pixbuf = NULL;
+ JOCTET *content;
+ gint fd;
+
+ /* try to open the file at the given path */
+ fd = open (path, O_RDONLY);
+ if (G_LIKELY (fd >= 0))
+ {
+ /* determine the status of the file */
+ if (G_LIKELY (fstat (fd, &statb) == 0 && statb.st_size > 0))
+ {
+ /* try to mmap the file */
+ content = mmap (NULL, statb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+
+ /* verify that the mmap was successful */
+ if (G_LIKELY (content != (JOCTET *) MAP_FAILED))
+ {
+ /* try to load an embedded thumbnail first */
+ pixbuf = tvtj_jpeg_load_thumbnail (content, statb.st_size, size);
+ if (G_LIKELY (pixbuf == NULL))
+ {
+ /* fall back to loading and scaling the image itself */
+ pixbuf = tvtj_jpeg_load (content, statb.st_size, size);
+ }
+ }
+
+ /* unmap the file content */
+ munmap (content, statb.st_size);
+ }
+
+ /* close the file */
+ close (fd);
+ }
+
+ return pixbuf;
+#endif
+
return NULL;
}
Modified: thunar/trunk/thunar-vfs/thunar-vfs-thumb.c
===================================================================
--- thunar/trunk/thunar-vfs/thunar-vfs-thumb.c 2007-01-14 21:49:25 UTC (rev
24474)
+++ thunar/trunk/thunar-vfs/thunar-vfs-thumb.c 2007-01-14 23:48:20 UTC (rev
24475)
@@ -1,6 +1,6 @@
/* $Id$ */
/*-
- * Copyright (c) 2004-2006 Benedikt Meurer <[EMAIL PROTECTED]>
+ * Copyright (c) 2004-2007 Benedikt Meurer <[EMAIL PROTECTED]>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
@@ -914,7 +914,20 @@
/* try the fast JPEG thumbnailer */
if (G_LIKELY (name_len == 10 && memcmp (name, "image/jpeg", 10) == 0))
- pixbuf = thunar_vfs_thumb_jpeg_load (path, size);
+ {
+ /* try loading the JPEG using our builtin thumbnailer */
+ pixbuf = thunar_vfs_thumb_jpeg_load (path, size);
+ if (G_LIKELY (pixbuf != NULL)
+ && gdk_pixbuf_get_width (pixbuf) <= 160
+ && gdk_pixbuf_get_height (pixbuf) <= 120)
+ {
+ /* since most embedded thumbnails are at size 160x120,
+ * we avoid scaling down the thumbnail again here and
+ * simply accept that even for "normal" thumbnails
+ */
+ goto done;
+ }
+ }
/* otherwise check if we have a thumbnailer script in the cache */
if (pixbuf == NULL && thunar_vfs_thumb_factory_cache_lookup (factory, name,
name_len, &script))
@@ -953,6 +966,7 @@
pixbuf = scaled;
}
+done:
/* cleanup */
g_free (path);
_______________________________________________
Xfce4-commits mailing list
[email protected]
http://foo-projects.org/mailman/listinfo/xfce4-commits