--- gimp.orig/plug-ins/common/jpeg.c	2004-10-29 16:15:25.000000000 +0100
+++ gimp/plug-ins/common/jpeg.c	2004-11-15 23:08:36.000000000 +0000
@@ -139,6 +139,14 @@
  * currently possible.
  */
 
+/*
+ * 15-NOV-04 - add support for EXIF JPEG thumbnail reading
+ * - S. Mukund <muks@mukund.org>
+ *
+ * Digital cameras store a TIFF APP1 marker that contains various
+ * parameters of the shot along with a thumbnail image.
+ */
+
 #include "config.h"   /* configure cares about HAVE_PROGRESSIVE_JPEG */
 
 #include <glib.h>     /* We want glib.h first because of some
@@ -147,6 +155,7 @@
 #include <errno.h>
 #include <setjmp.h>
 #include <signal.h>
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -239,6 +248,13 @@
 static gint32    load_image          (const gchar      *filename,
                                       GimpRunMode       runmode,
                                       gboolean          preview);
+#ifdef HAVE_EXIF
+
+static gint32    load_thumbnail_image(const gchar      *filename,
+                                      gint             *width,
+                                      gint             *height);
+#endif /* HAVE_EXIF */
+
 static gboolean  save_image          (const gchar      *filename,
                                       gint32            image_ID,
                                       gint32            drawable_ID,
@@ -301,6 +317,22 @@
     { GIMP_PDB_IMAGE,   "image",         "Output image" }
   };
 
+#ifdef HAVE_EXIF
+
+  static GimpParamDef thumb_args[] =
+  {
+    { GIMP_PDB_STRING, "filename",     "The name of the file to load"  },
+    { GIMP_PDB_INT32,  "thumb_size",   "Preferred thumbnail size"      }
+  };
+  static GimpParamDef thumb_return_vals[] =
+  {
+    { GIMP_PDB_IMAGE,  "image",        "Thumbnail image"               },
+    { GIMP_PDB_INT32,  "image_width",  "Width of full-sized image"     },
+    { GIMP_PDB_INT32,  "image_height", "Height of full-sized image"    }
+  };
+
+#endif /* HAVE_EXIF */
+
   static GimpParamDef save_args[] =
   {
     { GIMP_PDB_INT32,    "run_mode",     "Interactive, non-interactive" },
@@ -338,6 +370,25 @@
                                     "",
                                     "6,string,JFIF,6,string,Exif");
 
+#ifdef HAVE_EXIF
+
+  gimp_install_procedure ("file_jpeg_load_thumb",
+                          "Loads a thumbnail from a JPEG image",
+                          "Loads a thumbnail from a JPEG image (only if it exists)",
+                          "S. Mukund <muks@mukund.org>, Sven Neumann <sven@gimp.org>",
+                          "S. Mukund <muks@mukund.org>, Sven Neumann <sven@gimp.org>",
+                          "November 15, 2004",
+			  N_("JPEG image"),
+			  NULL,
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS (thumb_args),
+                          G_N_ELEMENTS (thumb_return_vals),
+                          thumb_args, thumb_return_vals);
+
+  gimp_register_thumbnail_loader ("file_jpeg_load", "file_jpeg_load_thumb");
+
+#endif /* HAVE_EXIF */
+
   gimp_install_procedure ("file_jpeg_save",
                           "saves files in the JPEG file format",
                           "saves files in the lossy, widely supported JPEG format",
@@ -396,6 +447,43 @@
           status = GIMP_PDB_EXECUTION_ERROR;
         }
     }
+
+#ifdef HAVE_EXIF
+
+  else if (strcmp (name, "file_jpeg_load_thumb") == 0)
+    {
+      if (nparams < 2)
+        {
+          status = GIMP_PDB_CALLING_ERROR;
+        }
+      else
+        {
+          const gchar *filename = param[0].data.d_string;
+          gint         width    = 0;
+          gint         height   = 0;
+          gint32       image_ID;
+
+          image_ID = load_thumbnail_image (filename, &width, &height);
+
+          if (image_ID != -1)
+            {
+	      *nreturn_vals = 4;
+	      values[1].type         = GIMP_PDB_IMAGE;
+	      values[1].data.d_image = image_ID;
+	      values[2].type         = GIMP_PDB_INT32;
+	      values[2].data.d_int32 = width;
+	      values[3].type         = GIMP_PDB_INT32;
+	      values[3].data.d_int32 = height;
+            }
+          else
+            {
+              status = GIMP_PDB_EXECUTION_ERROR;
+            }
+        }
+    }
+
+#endif /* HAVE_EXIF */
+
   else if (strcmp (name, "file_jpeg_save") == 0)
     {
       image_ID = orig_image_ID = param[1].data.d_int32;
@@ -2038,3 +2126,425 @@
 
   make_preview ();
 }
+
+#ifdef HAVE_EXIF
+
+typedef struct {
+	unsigned char        *buffer;
+	int                  filled;
+} buffer_struct;
+
+typedef struct {
+	struct jpeg_source_mgr pub;   /* public fields */
+
+	char	*buffer;
+	int		size;
+	JOCTET	terminal[2];
+} my_source_mgr;
+
+typedef my_source_mgr * my_src_ptr;
+
+static void init_source(j_decompress_ptr cinfo)
+{
+}
+
+
+static boolean fill_input_buffer(j_decompress_ptr cinfo)
+{
+	my_src_ptr src = (my_src_ptr) cinfo->src;
+
+	/* Since we have given all we have got already
+	* we simply fake an end of file
+	*/
+
+	src->pub.next_input_byte = src->terminal;
+	src->pub.bytes_in_buffer = 2;
+	src->terminal[0] = (JOCTET) 0xFF;
+	src->terminal[1] = (JOCTET) JPEG_EOI;
+
+	return TRUE;
+}
+
+
+static void skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+{
+	my_src_ptr src = (my_src_ptr) cinfo->src;
+
+	src->pub.next_input_byte = src->pub.next_input_byte + num_bytes;
+}
+
+
+static void term_source(j_decompress_ptr cinfo)
+{
+}
+
+static gint32
+load_thumbnail_image (const gchar *filename,
+            gint     *width,
+            gint     *height)
+{
+  gint32 volatile  image_ID;
+  ExifData *exif_data;
+  GimpPixelRgn     pixel_rgn;
+  GimpDrawable    *drawable;
+  gint32           layer_ID;
+  struct jpeg_decompress_struct cinfo;
+  struct my_error_mgr           jerr;
+  guchar  *buf;
+  guchar  * volatile padded_buf = NULL;
+  guchar **rowbuf;
+  gint     image_type;
+  gint     layer_type;
+  gint     tile_height;
+  gint     scanlines;
+  gint     i, start, end;
+  my_src_ptr src;
+  FILE *infile;
+
+  image_ID = -1;
+  exif_data = exif_data_new_from_file (filename);
+
+ if ((exif_data) && (exif_data->data) && (exif_data->size > 0))
+ {
+
+  cinfo.err = jpeg_std_error (&jerr.pub);
+  jerr.pub.error_exit = my_error_exit;
+
+  cinfo.client_data = GINT_TO_POINTER (FALSE);
+
+  jerr.pub.emit_message   = my_emit_message;
+  jerr.pub.output_message = my_output_message;
+
+  gchar *name = g_strdup_printf (_("Opening thumbnail for '%s'..."),
+                                     gimp_filename_to_utf8 (filename));
+
+  gimp_progress_init (name);
+  g_free (name);
+
+  /* Establish the setjmp return context for my_error_exit to use. */
+  if (setjmp (jerr.setjmp_buffer))
+    {
+
+      /* If we get here, the JPEG code has signaled an error.
+       * We need to clean up the JPEG object, close the input file, and return.
+       */
+
+      jpeg_destroy_decompress (&cinfo);
+
+      if (image_ID != -1)
+        gimp_image_delete (image_ID);
+
+      if (exif_data)
+      {
+          exif_data_unref (exif_data);
+          exif_data = NULL;
+      }
+
+      return -1;
+    }
+
+  /* Now we can initialize the JPEG decompression object. */
+  jpeg_create_decompress (&cinfo);
+
+  /* Step 2: specify data source (eg, a file) */
+  
+  if (cinfo.src == NULL)
+           cinfo.src = (struct jpeg_source_mgr *)(*cinfo.mem->alloc_small)
+                                    	((j_common_ptr) &cinfo, JPOOL_PERMANENT,
+                                    	 sizeof(my_source_mgr));
+
+  src = (my_src_ptr) 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	 = exif_data->size;
+  src->pub.next_input_byte	 = exif_data->data;
+
+  src->buffer = exif_data->data;
+  src->size = exif_data->size;
+  
+
+  /* Step 3: read file parameters with jpeg_read_header() */
+
+  (void) jpeg_read_header (&cinfo, TRUE);
+
+  /* Step 4: set parameters for decompression */
+
+  /* In this example, we don't need to change any of the defaults set by
+   * jpeg_read_header(), so we do nothing here.
+   */
+
+  /* Step 5: Start decompressor */
+
+  jpeg_start_decompress (&cinfo);
+
+  /* We may need to do some setup of our own at this point before reading
+   * the data.  After jpeg_start_decompress() we have the correct scaled
+   * output image dimensions available, as well as the output colormap
+   * if we asked for color quantization.
+   * In this example, we need to make an output work buffer of the right size.
+   */
+  /* temporary buffer */
+  tile_height = gimp_tile_height ();
+  buf = g_new (guchar,
+               tile_height * cinfo.output_width * cinfo.output_components);
+
+  rowbuf = g_new (guchar*, tile_height);
+
+  for (i = 0; i < tile_height; i++)
+    rowbuf[i] = buf + cinfo.output_width * cinfo.output_components * i;
+
+  /* Create a new image of the proper size and associate the filename with it.
+
+     Preview layers, not being on the bottom of a layer stack, MUST HAVE
+     AN ALPHA CHANNEL!
+   */
+  switch (cinfo.output_components)
+    {
+    case 1:
+      image_type = GIMP_GRAY;
+      layer_type = GIMP_GRAY_IMAGE;
+      break;
+
+    case 3:
+      image_type = GIMP_RGB;
+      layer_type = GIMP_RGB_IMAGE;
+      break;
+
+    case 4:
+      if (cinfo.out_color_space == JCS_CMYK)
+        {
+          image_type = GIMP_RGB;
+          layer_type = GIMP_RGB_IMAGE;
+          break;
+        }
+      /*fallthrough*/
+
+    default:
+      g_message ("Don't know how to load JPEGs\n"
+                 "with %d color channels\n"
+                 "using colorspace %d (%d)",
+                 cinfo.output_components, cinfo.out_color_space,
+                 cinfo.jpeg_color_space);
+
+      if (exif_data)
+      {
+          exif_data_unref (exif_data);
+          exif_data = NULL;
+      }
+
+      return -1;
+      break;
+    }
+
+  if (cinfo.out_color_space == JCS_CMYK)
+    padded_buf = g_new (guchar, tile_height * cinfo.output_width * 3);
+  else
+    padded_buf = NULL;
+
+  image_ID = gimp_image_new (cinfo.output_width, cinfo.output_height,
+                                 image_type);
+  gimp_image_set_filename (image_ID, filename);
+
+  layer_ID = gimp_layer_new (image_ID, _("Background"),
+                                 cinfo.output_width,
+                                 cinfo.output_height,
+                                 layer_type, 100, GIMP_NORMAL_MODE);
+
+  drawable_global = drawable = gimp_drawable_get (layer_ID);
+  gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0,
+                       drawable->width, drawable->height, TRUE, FALSE);
+
+  /* Step 5.1: if the file had resolution information, set it on the image */
+  if (cinfo.saw_JFIF_marker)
+    {
+      gdouble xresolution;
+      gdouble yresolution;
+      gdouble asymmetry;
+
+      xresolution = cinfo.X_density;
+      yresolution = cinfo.Y_density;
+
+      switch (cinfo.density_unit)
+        {
+        case 0: /* unknown -> set the aspect ratio but use the default
+                *  image resolution
+                */
+          if (cinfo.Y_density != 0)
+            asymmetry = xresolution / yresolution;
+          else
+            asymmetry = 1.0;
+
+          gimp_image_get_resolution (image_ID, &xresolution, &yresolution);
+          xresolution *= asymmetry;
+          break;
+
+        case 1: /* dots per inch */
+          break;
+
+        case 2: /* dots per cm */
+          xresolution *= 2.54;
+          yresolution *= 2.54;
+          gimp_image_set_unit (image_ID, GIMP_UNIT_MM);
+          break;
+
+        default:
+          g_message ("Unknown density unit %d\nassuming dots per inch",
+                     cinfo.density_unit);
+          break;
+        }
+
+      gimp_image_set_resolution (image_ID, xresolution, yresolution);
+    }
+
+  /* Step 6: while (scan lines remain to be read) */
+  /*           jpeg_read_scanlines(...); */
+
+  /* Here we use the library's state variable cinfo.output_scanline as the
+   * loop counter, so that we don't have to keep track ourselves.
+   */
+  while (cinfo.output_scanline < cinfo.output_height)
+    {
+      start = cinfo.output_scanline;
+      end   = cinfo.output_scanline + tile_height;
+      end   = MIN (end, cinfo.output_height);
+      scanlines = end - start;
+
+      for (i = 0; i < scanlines; i++)
+        jpeg_read_scanlines (&cinfo, (JSAMPARRAY) &rowbuf[i], 1);
+
+      if (cinfo.out_color_space == JCS_CMYK) /* buf-> RGB in padded_buf */
+        {
+          guchar *dest = padded_buf;
+          guchar *src  = buf;
+          gint    num  = drawable->width * scanlines;
+
+          for (i = 0; i < num; i++)
+            {
+              guint r_c, g_m, b_y, a_k;
+
+              r_c = *(src++);
+              g_m = *(src++);
+              b_y = *(src++);
+              a_k = *(src++);
+              *(dest++) = (r_c * a_k) / 255;
+              *(dest++) = (g_m * a_k) / 255;
+              *(dest++) = (b_y * a_k) / 255;
+            }
+        }
+
+      gimp_pixel_rgn_set_rect (&pixel_rgn, padded_buf ? padded_buf : buf,
+                               0, start, drawable->width, scanlines);
+
+      gimp_progress_update ((gdouble) cinfo.output_scanline /
+                              (gdouble) cinfo.output_height);
+    }
+
+  /* Step 7: Finish decompression */
+
+  jpeg_finish_decompress (&cinfo);
+  /* We can ignore the return value since suspension is not possible
+   * with the stdio data source.
+   */
+
+  /* Step 8: Release JPEG decompression object */
+
+  /* This is an important step since it will release a good deal of memory. */
+  jpeg_destroy_decompress (&cinfo);
+
+  /* free up the temporary buffers */
+  g_free (rowbuf);
+  g_free (buf);
+  g_free (padded_buf);
+
+  /* At this point you may want to check to see whether any corrupt-data
+   * warnings occurred (test whether jerr.num_warnings is nonzero).
+   */
+
+  gimp_image_add_layer (image_ID, layer_ID, 0);
+
+
+  /* NOW to get the dimensions of the actual image to return the calling app */
+
+  cinfo.err = jpeg_std_error (&jerr.pub);
+  jerr.pub.error_exit = my_error_exit;
+
+  cinfo.client_data = GINT_TO_POINTER (FALSE);
+
+  jerr.pub.emit_message   = my_emit_message;
+  jerr.pub.output_message = my_output_message;
+
+  if ((infile = fopen (filename, "rb")) == NULL)
+    {
+      g_message (_("Could not open '%s' for reading: %s"),
+                 gimp_filename_to_utf8 (filename), g_strerror (errno));
+
+      if (exif_data)
+      {
+          exif_data_unref (exif_data);
+          exif_data = NULL;
+      }
+      
+      return -1;
+    }
+
+  /* Establish the setjmp return context for my_error_exit to use. */
+  if (setjmp (jerr.setjmp_buffer))
+    {
+
+      /* If we get here, the JPEG code has signaled an error.
+       * We need to clean up the JPEG object, close the input file, and return.
+       */
+
+      jpeg_destroy_decompress (&cinfo);
+
+      if (image_ID != -1)
+        gimp_image_delete (image_ID);
+
+      if (exif_data)
+      {
+          exif_data_unref (exif_data);
+          exif_data = NULL;
+      }
+
+      return -1;
+    }
+
+  /* Now we can initialize the JPEG decompression object. */
+  jpeg_create_decompress (&cinfo);
+
+  /* Step 2: specify data source (eg, a file) */
+  
+  jpeg_stdio_src (&cinfo, infile);
+
+  /* Step 3: read file parameters with jpeg_read_header() */
+
+  (void) jpeg_read_header (&cinfo, TRUE);
+
+  jpeg_start_decompress (&cinfo);
+
+  *width = cinfo.output_width;
+  *height = cinfo.output_height;
+
+  /* Step 4: Release JPEG decompression object */
+
+  /* This is an important step since it will release a good deal of memory. */
+  jpeg_destroy_decompress (&cinfo);
+
+  if (exif_data)
+  {
+      exif_data_unref (exif_data);
+      exif_data = NULL;
+  }
+
+ }
+ 
+  return image_ID;
+}
+
+#endif /* HAVE_EXIF */
+
