[gimp/gimp-2-10] plug-ins: Add layer support to TIFF writing



commit 9335c26b6858d88679fcdeb60f45bb3437d5b950
Author: Tobias Ellinghaus <me houz org>
Date:   Tue Apr 16 10:04:16 2019 +0200

    plug-ins: Add layer support to TIFF writing
    
    (cherry picked from commit 7584969453ae39a79905f61930ef7a432319fe07)

 plug-ins/file-tiff/file-tiff-io.c   |  28 ++-
 plug-ins/file-tiff/file-tiff-load.c | 113 +++++----
 plug-ins/file-tiff/file-tiff-load.h |   1 +
 plug-ins/file-tiff/file-tiff-save.c | 466 ++++++++++++++++++++++++++----------
 plug-ins/file-tiff/file-tiff-save.h |  18 +-
 plug-ins/file-tiff/file-tiff.c      |  91 +++----
 plug-ins/ui/plug-in-file-tiff.ui    |  26 +-
 7 files changed, 503 insertions(+), 240 deletions(-)
---
diff --git a/plug-ins/file-tiff/file-tiff-io.c b/plug-ins/file-tiff/file-tiff-io.c
index ebe2d84f4f..62b991b84f 100644
--- a/plug-ins/file-tiff/file-tiff-io.c
+++ b/plug-ins/file-tiff/file-tiff-io.c
@@ -87,7 +87,7 @@ tiff_open (GFile        *file,
 
       tiff_io.stream = G_OBJECT (tiff_io.input);
     }
-  else
+  else if(! strcmp (mode, "w"))
     {
       tiff_io.output = G_OUTPUT_STREAM (g_file_replace (file,
                                                         NULL, FALSE,
@@ -98,6 +98,21 @@ tiff_open (GFile        *file,
 
       tiff_io.stream = G_OBJECT (tiff_io.output);
     }
+  else if(! strcmp (mode, "a"))
+    {
+      GIOStream *iostream = G_IO_STREAM (g_file_open_readwrite (file, NULL,
+                                                                error));
+      if (! iostream)
+        return NULL;
+
+      tiff_io.input  = g_io_stream_get_input_stream (iostream);
+      tiff_io.output = g_io_stream_get_output_stream (iostream);
+      tiff_io.stream = G_OBJECT (iostream);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
 
 #if 0
 #warning FIXME !can_seek code is broken
@@ -383,7 +398,7 @@ tiff_io_close (thandle_t handle)
   GError   *error  = NULL;
   gboolean  closed = FALSE;
 
-  if (io->input)
+  if (io->input && ! io->output)
     {
       closed = g_input_stream_close (io->input, NULL, &error);
     }
@@ -401,7 +416,14 @@ tiff_io_close (thandle_t handle)
             }
         }
 
-      closed = g_output_stream_close (io->output, NULL, &error);
+      if (io->input)
+        {
+          closed = g_io_stream_close (G_IO_STREAM (io->stream), NULL, &error);
+        }
+      else
+        {
+          closed = g_output_stream_close (io->output, NULL, &error);
+        }
     }
 
   if (! closed)
diff --git a/plug-ins/file-tiff/file-tiff-load.c b/plug-ins/file-tiff/file-tiff-load.c
index d71b22224b..123bac68de 100644
--- a/plug-ins/file-tiff/file-tiff-load.c
+++ b/plug-ins/file-tiff/file-tiff-load.c
@@ -98,7 +98,11 @@ static void               load_separate    (TIFF         *tif,
                                             gboolean      is_bw,
                                             gint          extra);
 static void               load_paths       (TIFF         *tif,
-                                            gint          image);
+                                            gint          image,
+                                            gint          width,
+                                            gint          height,
+                                            gint          offset_x,
+                                            gint          offset_y);
 
 static void               fill_bit2byte    (void);
 static void               convert_bit2byte (const guchar *src,
@@ -137,6 +141,7 @@ load_dialog (TIFF              *tif,
   GtkWidget  *dialog;
   GtkWidget  *vbox;
   GtkWidget  *selector;
+  GtkWidget  *crop_option;
   gint        i;
   gboolean    run;
 
@@ -160,7 +165,6 @@ load_dialog (TIFF              *tif,
   gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                       vbox, TRUE, TRUE, 0);
-  gtk_widget_show (vbox);
 
   /* Page Selector */
   selector = gimp_page_selector_new ();
@@ -186,10 +190,17 @@ load_dialog (TIFF              *tif,
                             G_CALLBACK (gtk_window_activate_default),
                             dialog);
 
-  gtk_widget_show (selector);
+  /* Option to shrink the loaded image to its bounding box
+     or keep as much empty space as possible.
+     Note that there seems to be no way to keep the empty
+     space on the right and bottom. */
+  crop_option = gtk_check_button_new_with_label (_("Keep empty space around imported layers"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (crop_option),
+                                pages->keep_empty_space);
+  gtk_box_pack_start (GTK_BOX (vbox), crop_option, TRUE, TRUE, 0);
 
   /* Setup done; display the dialog */
-  gtk_widget_show (dialog);
+  gtk_widget_show_all (dialog);
 
   /* run the dialog */
   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
@@ -203,6 +214,9 @@ load_dialog (TIFF              *tif,
         gimp_page_selector_get_selected_pages (GIMP_PAGE_SELECTOR (selector),
                                                &pages->n_pages);
 
+      pages->keep_empty_space =
+        gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crop_option));
+
       /* select all if none selected */
       if (pages->n_pages == 0)
         {
@@ -900,7 +914,11 @@ load_image (GFile              *file,
           gimp_image_set_colormap (image, cmap, (1 << bps));
         }
 
-      load_paths (tif, image);
+      if (pages->target != GIMP_PAGE_SELECTOR_TARGET_IMAGES)
+        load_paths (tif, image, cols, rows,
+                    layer_offset_x_pixel, layer_offset_y_pixel);
+      else
+        load_paths (tif, image, cols, rows, 0, 0);
 
       /* Allocate ChannelData for all channels, even the background layer */
       channel = g_new0 (ChannelData, extra + 1);
@@ -1031,29 +1049,27 @@ load_image (GFile              *file,
       g_free (channel);
       channel = NULL;
 
-
-      /* TODO: in GIMP 2.6, use a dialog to selectively enable the
-       * following code, as the save plug-in will then save layer offsets
-       * as well.
-       */
-
-      /* compute bounding box of all layers read so far */
-      if (min_col > layer_offset_x_pixel)
-        min_col = layer_offset_x_pixel;
-      if (min_row > layer_offset_y_pixel)
-        min_row = layer_offset_y_pixel;
-
-      if (max_col < layer_offset_x_pixel + cols)
-        max_col = layer_offset_x_pixel + cols;
-      if (max_row < layer_offset_y_pixel + rows)
-        max_row = layer_offset_y_pixel + rows;
-
-      /* position the layer */
-      if (layer_offset_x_pixel > 0 ||
-          layer_offset_y_pixel > 0)
+      if (pages->target != GIMP_PAGE_SELECTOR_TARGET_IMAGES)
         {
-          gimp_layer_set_offsets (layer,
-                                  layer_offset_x_pixel, layer_offset_y_pixel);
+          /* compute bounding box of all layers read so far */
+          if (min_col > layer_offset_x_pixel)
+            min_col = layer_offset_x_pixel;
+          if (min_row > layer_offset_y_pixel)
+            min_row = layer_offset_y_pixel;
+
+          if (max_col < layer_offset_x_pixel + cols)
+            max_col = layer_offset_x_pixel + cols;
+          if (max_row < layer_offset_y_pixel + rows)
+            max_row = layer_offset_y_pixel + rows;
+
+          /* position the layer */
+          if (layer_offset_x_pixel > 0 ||
+              layer_offset_y_pixel > 0)
+            {
+              gimp_layer_set_offsets (layer,
+                                      layer_offset_x_pixel,
+                                      layer_offset_y_pixel);
+            }
         }
 
       gimp_image_insert_layer (image, layer, -1, -1);
@@ -1067,16 +1083,7 @@ load_image (GFile              *file,
       gimp_progress_update (1.0);
     }
 
-  if (pages->target != GIMP_PAGE_SELECTOR_TARGET_IMAGES)
-    {
-      /* resize image to bounding box of all layers */
-      gimp_image_resize (image,
-                         max_col - min_col, max_row - min_row,
-                         -min_col, -min_row);
-
-      gimp_image_undo_enable (image);
-    }
-  else
+  if (pages->target == GIMP_PAGE_SELECTOR_TARGET_IMAGES)
     {
       GList *list = images_list;
 
@@ -1094,6 +1101,22 @@ load_image (GFile              *file,
 
       g_list_free (images_list);
     }
+  else
+    {
+      if (pages->keep_empty_space)
+        {
+          /* unfortunately we have no idea about empty space
+             at the bottom/right of layers */
+          min_col = 0;
+          min_row = 0;
+        }
+      /* resize image to bounding box of all layers */
+      gimp_image_resize (image,
+                         max_col - min_col, max_row - min_row,
+                         -min_col, -min_row);
+
+      gimp_image_undo_enable (image);
+    }
 
   return image;
 }
@@ -1170,18 +1193,17 @@ load_rgba (TIFF        *tif,
 
 static void
 load_paths (TIFF *tif,
-            gint  image)
+            gint  image,
+            gint  width,
+            gint  height,
+            gint  offset_x,
+            gint  offset_y)
 {
-  gint   width;
-  gint   height;
   gsize  n_bytes;
   gchar *bytes;
   gint   path_index;
   gsize  pos;
 
-  width  = gimp_image_width (image);
-  height = gimp_image_height (image);
-
   if (! TIFFGetField (tif, TIFFTAG_PHOTOSHOP, &n_bytes, &bytes))
     return;
 
@@ -1305,6 +1327,9 @@ load_paths (TIFF *tif,
                           gdouble f;
                           guint32 coord;
 
+                          const gint size = j % 2 ? width : height;
+                          const gint offset = j % 2 ? offset_x : offset_y;
+
                           val32 = (guint32 *) (bytes + rec + 2 + j * 4);
                           coord = GUINT32_FROM_BE (*val32);
 
@@ -1316,7 +1341,7 @@ load_paths (TIFF *tif,
                            * first, gimp expects the horizontal
                            * component first. Sigh.
                            */
-                          points[pointcount * 6 + (j ^ 1)] = f * (j % 2 ? width : height);
+                          points[pointcount * 6 + (j ^ 1)] = f * size + offset;
                         }
 
                       pointcount++;
diff --git a/plug-ins/file-tiff/file-tiff-load.h b/plug-ins/file-tiff/file-tiff-load.h
index 372af71f53..956ea43a77 100644
--- a/plug-ins/file-tiff/file-tiff-load.h
+++ b/plug-ins/file-tiff/file-tiff-load.h
@@ -29,6 +29,7 @@ typedef struct
   gint                    n_pages;
   gint                   *pages;
   GimpPageSelectorTarget  target;
+  gboolean                keep_empty_space;
 } TiffSelectedPages;
 
 
diff --git a/plug-ins/file-tiff/file-tiff-save.c b/plug-ins/file-tiff/file-tiff-save.c
index 79d00a596c..8581725e92 100644
--- a/plug-ins/file-tiff/file-tiff-save.c
+++ b/plug-ins/file-tiff/file-tiff-save.c
@@ -48,6 +48,7 @@
 #include <string.h>
 
 #include <tiffio.h>
+#include <gexiv2/gexiv2.h>
 
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
@@ -62,7 +63,11 @@
 
 
 static gboolean  save_paths             (TIFF          *tif,
-                                         gint32         image);
+                                         gint32         image,
+                                         gdouble        width,
+                                         gdouble        height,
+                                         gint           offset_x,
+                                         gint           offset_y);
 
 static void      comment_entry_callback (GtkWidget     *widget,
                                          gchar        **comment);
@@ -72,6 +77,10 @@ static void      byte2bit               (const guchar  *byteline,
                                          guchar        *bitline,
                                          gboolean       invert);
 
+static void      save_thumbnail         (TiffSaveVals  *tsvals,
+                                         gint32         image,
+                                         TIFF          *tif);
+
 
 static void
 double_to_psd_fixed (gdouble  value,
@@ -97,17 +106,18 @@ double_to_psd_fixed (gdouble  value,
 }
 
 static gboolean
-save_paths (TIFF   *tif,
-            gint32  image)
+save_paths (TIFF    *tif,
+            gint32   image,
+            gdouble  width,
+            gdouble  height,
+            gint     offset_x,
+            gint     offset_y)
 {
   gint id = 2000; /* Photoshop paths have IDs >= 2000 */
   gint num_vectors, *vectors, v;
   gint num_strokes, *strokes, s;
-  gdouble width, height;
   GString *ps_tag;
 
-  width = gimp_image_width (image);
-  height = gimp_image_height (image);
   vectors = gimp_image_get_vectors (image, &num_vectors);
 
   if (num_vectors <= 0)
@@ -203,12 +213,12 @@ save_paths (TIFF   *tif,
             {
               pointrecord[1] = closed ? 2 : 5;
 
-              double_to_psd_fixed (points[p+1] / height, pointrecord + 2);
-              double_to_psd_fixed (points[p+0] / width,  pointrecord + 6);
-              double_to_psd_fixed (points[p+3] / height, pointrecord + 10);
-              double_to_psd_fixed (points[p+2] / width,  pointrecord + 14);
-              double_to_psd_fixed (points[p+5] / height, pointrecord + 18);
-              double_to_psd_fixed (points[p+4] / width,  pointrecord + 22);
+              double_to_psd_fixed ((points[p+1] - offset_y) / height, pointrecord + 2);
+              double_to_psd_fixed ((points[p+0] - offset_x) / width,  pointrecord + 6);
+              double_to_psd_fixed ((points[p+3] - offset_y) / height, pointrecord + 10);
+              double_to_psd_fixed ((points[p+2] - offset_x) / width,  pointrecord + 14);
+              double_to_psd_fixed ((points[p+5] - offset_y) / height, pointrecord + 18);
+              double_to_psd_fixed ((points[p+4] - offset_x) / width,  pointrecord + 22);
 
               g_string_append_len (data, pointrecord, 26);
             }
@@ -254,18 +264,19 @@ save_paths (TIFF   *tif,
  * other special, indirect and consequential damages.
  */
 
-gboolean
-save_image (GFile        *file,
+static gboolean
+save_layer (TIFF         *tif,
             TiffSaveVals *tsvals,
             gint32        image,
             gint32        layer,
-            gint32        orig_image,  /* the export function might have */
-            const gchar  *image_comment,
+            gint32        page,
+            gint32        num_pages,
+            gint32        orig_image, /* the export function might */
+                                      /* have created a duplicate  */
             gint         *saved_bpp,
-            GError      **error)       /* created a duplicate            */
+            GError      **error)
 {
   gboolean          status = FALSE;
-  TIFF             *tif;
   gushort           red[256];
   gushort           grn[256];
   gushort           blu[256];
@@ -297,11 +308,18 @@ save_image (GFile        *file,
   gboolean          invert   = TRUE;
   const guchar      bw_map[] = { 0, 0, 0, 255, 255, 255 };
   const guchar      wb_map[] = { 255, 255, 255, 0, 0, 0 };
-  gint              number_of_sub_IFDs = 1;
-  toff_t            sub_IFDs_offsets[1] = { 0UL };
+  gchar            *layer_name = NULL;
+  const gdouble     progress_base = (gdouble) page / (gdouble) num_pages;
+  const gdouble     progress_fraction = 1.0 / (gdouble) num_pages;
+  gdouble           xresolution;
+  gdouble           yresolution;
+  gushort           save_unit = RESUNIT_INCH;
+  gint              offset_x, offset_y;
 
   compression = tsvals->compression;
 
+  layer_name = gimp_item_get_name (layer);
+
   /* Disabled because this isn't in older releases of libtiff, and it
    * wasn't helping much anyway
    */
@@ -314,14 +332,6 @@ save_image (GFile        *file,
   tile_height = gimp_tile_height ();
   rowsperstrip = tile_height;
 
-  gimp_progress_init_printf (_("Exporting '%s'"),
-                             gimp_file_get_utf8_name (file));
-
-#ifdef TIFFTAG_ICCPROFILE
-  if (tsvals->save_profile)
-    profile = gimp_image_get_effective_color_profile (orig_image);
-#endif
-
   drawable_type = gimp_drawable_type (layer);
   buffer        = gimp_drawable_get_buffer (layer);
 
@@ -633,21 +643,14 @@ save_image (GFile        *file,
         }
     }
 
-  tif = tiff_open (file, "w", error);
 
-  if (! tif)
+  /* Set TIFF parameters. */
+  if (num_pages > 1)
     {
-      if (! error)
-        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
-                     _("Could not open '%s' for writing: %s"),
-                     gimp_file_get_utf8_name (file), g_strerror (errno));
-      goto out;
+      TIFFSetField (tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE);
+      TIFFSetField (tif, TIFFTAG_PAGENUMBER, page, num_pages);
     }
-
-  /* Set TIFF parameters. */
-  if (tsvals->save_thumbnail)
-    TIFFSetField (tif, TIFFTAG_SUBIFD, number_of_sub_IFDs, sub_IFDs_offsets);
-  TIFFSetField (tif, TIFFTAG_SUBFILETYPE, 0);
+  TIFFSetField (tif, TIFFTAG_PAGENAME, layer_name);
   TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, cols);
   TIFFSetField (tif, TIFFTAG_IMAGELENGTH, rows);
   TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bitspersample);
@@ -680,105 +683,45 @@ save_image (GFile        *file,
     }
 
   TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, photometric);
-  TIFFSetField (tif, TIFFTAG_DOCUMENTNAME, g_file_get_path (file));
   TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
   TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
   /* TIFFSetField( tif, TIFFTAG_STRIPBYTECOUNTS, rows / rowsperstrip ); */
   TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
 
   /* resolution fields */
-  {
-    gdouble  xresolution;
-    gdouble  yresolution;
-    gushort  save_unit = RESUNIT_INCH;
-
-    gimp_image_get_resolution (orig_image, &xresolution, &yresolution);
-
-    if (gimp_unit_is_metric (gimp_image_get_unit (orig_image)))
-      {
-        save_unit = RESUNIT_CENTIMETER;
-        xresolution /= 2.54;
-        yresolution /= 2.54;
-      }
-
-    if (xresolution > 1e-5 && yresolution > 1e-5)
-      {
-        TIFFSetField (tif, TIFFTAG_XRESOLUTION, xresolution);
-        TIFFSetField (tif, TIFFTAG_YRESOLUTION, yresolution);
-        TIFFSetField (tif, TIFFTAG_RESOLUTIONUNIT, save_unit);
-      }
+  gimp_image_get_resolution (orig_image, &xresolution, &yresolution);
 
-#if 0
-    /* TODO: enable in 2.6 */
-
-    gint     offset_x, offset_y;
-
-    gimp_drawable_offsets (layer, &offset_x, &offset_y);
-
-    if (offset_x || offset_y)
-      {
-        TIFFSetField (tif, TIFFTAG_XPOSITION, offset_x / xresolution);
-        TIFFSetField (tif, TIFFTAG_YPOSITION, offset_y / yresolution);
-      }
-#endif
-  }
-
-  /* The TIFF spec explicitly says ASCII for the image description. */
-  if (image_comment)
+  if (gimp_unit_is_metric (gimp_image_get_unit (orig_image)))
     {
-      const gchar *c = image_comment;
-      gint         len;
-
-      for (len = strlen (c); len; c++, len--)
-        {
-          if ((guchar) *c > 127)
-            {
-              g_message (_("The TIFF format only supports comments in\n"
-                           "7bit ASCII encoding. No comment is saved."));
-              image_comment = NULL;
-
-              break;
-            }
-        }
+      save_unit = RESUNIT_CENTIMETER;
+      xresolution /= 2.54;
+      yresolution /= 2.54;
     }
 
-  /* do we have a comment?  If so, create a new parasite to hold it,
-   * and attach it to the image. The attach function automatically
-   * detaches a previous incarnation of the parasite. */
-  if (image_comment && *image_comment)
+  if (xresolution > 1e-5 && yresolution > 1e-5)
     {
-      GimpParasite *parasite;
-
-      TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, image_comment);
-      parasite = gimp_parasite_new ("gimp-comment",
-                                    GIMP_PARASITE_PERSISTENT,
-                                    strlen (image_comment) + 1, image_comment);
-      gimp_image_attach_parasite (orig_image, parasite);
-      gimp_parasite_free (parasite);
+      TIFFSetField (tif, TIFFTAG_XRESOLUTION, xresolution);
+      TIFFSetField (tif, TIFFTAG_YRESOLUTION, yresolution);
+      TIFFSetField (tif, TIFFTAG_RESOLUTIONUNIT, save_unit);
     }
 
-  /* do we have an ICC profile? If so, write it to the TIFF file */
-#ifdef TIFFTAG_ICCPROFILE
-  if (profile)
-    {
-      const guint8 *icc_data;
-      gsize         icc_length;
-
-      icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
+  gimp_drawable_offsets (layer, &offset_x, &offset_y);
 
-      TIFFSetField (tif, TIFFTAG_ICCPROFILE, icc_length, icc_data);
-
-      g_object_unref (profile);
+  if (offset_x || offset_y)
+    {
+      TIFFSetField (tif, TIFFTAG_XPOSITION, offset_x / xresolution);
+      TIFFSetField (tif, TIFFTAG_YPOSITION, offset_y / yresolution);
     }
-#endif
-
-  /* save path data */
-  save_paths (tif, orig_image);
 
   if (! is_bw &&
       (drawable_type == GIMP_INDEXED_IMAGE || drawable_type == GIMP_INDEXEDA_IMAGE))
     TIFFSetField (tif, TIFFTAG_COLORMAP, red, grn, blu);
 
+  /* save path data. we need layer information for that,
+    * so we have to do this in here. :-( */
+  if (page == 0)
+    save_paths (tif, orig_image, cols, rows, offset_x, offset_y);
+
   /* array to rearrange data */
   src  = g_new (guchar, bytesperrow * tile_height);
   data = g_new (guchar, bytesperrow);
@@ -833,11 +776,32 @@ save_image (GFile        *file,
         }
 
       if ((row % 32) == 0)
-        gimp_progress_update ((gdouble) row / (gdouble) rows);
+        gimp_progress_update (progress_base + progress_fraction
+                              * (gdouble) row / (gdouble) rows);
     }
 
   TIFFWriteDirectory (tif);
 
+  gimp_progress_update (progress_base + progress_fraction);
+
+  status = TRUE;
+
+out:
+  if (buffer)
+    g_object_unref (buffer);
+
+  g_free (data);
+  g_free (src);
+  g_free (layer_name);
+
+  return status;
+}
+
+static void
+save_thumbnail (TiffSaveVals *tsvals,
+                gint32        image,
+                TIFF         *tif)
+{
   /* now switch IFD and write thumbnail
    *
    * the thumbnail must be saved in a subimage of the image.
@@ -913,21 +877,262 @@ save_image (GFile        *file,
 
       g_object_unref (thumb_pixbuf);
     }
+}
+
+static void
+save_metadata (GFile                 *file,
+               TiffSaveVals          *tsvals,
+               gint32                 image,
+               GimpMetadata          *metadata,
+               GimpMetadataSaveFlags  metadata_flags,
+               gint                   saved_bpp)
+{
+  gchar **exif_tags;
+
+  /* See bug 758909: clear TIFFTAG_MIN/MAXSAMPLEVALUE because
+   * exiv2 saves them with wrong type and the original values
+   * could be invalid, see also bug 761823.
+   * we also clear some other tags that were only meaningful
+   * for the original imported image.
+   */
+  static const gchar *exif_tags_to_remove[] = {
+    "Exif.Image.0x0118",
+    "Exif.Image.0x0119",
+    "Exif.Image.0x011d",
+    "Exif.Image.Compression",
+    "Exif.Image.FillOrder",
+    "Exif.Image.NewSubfileType",
+    "Exif.Image.PageNumber",
+    "Exif.Image.PhotometricInterpretation",
+    "Exif.Image.PlanarConfiguration",
+    "Exif.Image.Predictor",
+    "Exif.Image.RowsPerStrip",
+    "Exif.Image.SampleFormat",
+    "Exif.Image.SamplesPerPixel",
+    "Exif.Image.StripByteCounts",
+    "Exif.Image.StripOffsets"
+  };
+  static const guint n_keys = G_N_ELEMENTS(exif_tags_to_remove);
+
+  for (int k = 0; k < n_keys; k++)
+    {
+      gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
+                                 exif_tags_to_remove[k]);
+    }
+
+  /* get rid of all the EXIF tags for anything but the first sub image. */
+  exif_tags = gexiv2_metadata_get_exif_tags (GEXIV2_METADATA(metadata));
+  for (char **tag = exif_tags; *tag; tag++)
+    {
+      if (g_str_has_prefix (*tag, "Exif.Image")
+          && (*tag)[strlen ("Exif.Image")] >= '0'
+          && (*tag)[strlen ("Exif.Image")] <= '9')
+        gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata), *tag);
+    }
 
+  gimp_metadata_set_bits_per_sample (metadata, saved_bpp);
+
+  if (tsvals->save_exif)
+    metadata_flags |= GIMP_METADATA_SAVE_EXIF;
+  else
+    metadata_flags &= ~GIMP_METADATA_SAVE_EXIF;
+
+  if (tsvals->save_xmp)
+    metadata_flags |= GIMP_METADATA_SAVE_XMP;
+  else
+    metadata_flags &= ~GIMP_METADATA_SAVE_XMP;
+
+  if (tsvals->save_iptc)
+    metadata_flags |= GIMP_METADATA_SAVE_IPTC;
+  else
+    metadata_flags &= ~GIMP_METADATA_SAVE_IPTC;
+
+  /* never save metadata thumbnails for TIFF, see bug #729952 */
+  metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL;
+
+  if (tsvals->save_profile)
+    metadata_flags |= GIMP_METADATA_SAVE_COLOR_PROFILE;
+  else
+    metadata_flags &= ~GIMP_METADATA_SAVE_COLOR_PROFILE;
+
+  gimp_image_metadata_save_finish (image,
+                                   "image/tiff",
+                                   metadata, metadata_flags,
+                                   file, NULL);
+}
+
+gboolean
+save_image (GFile                  *file,
+            TiffSaveVals           *tsvals,
+            gint32                  image,
+            gint32                  orig_image,    /* the export function */
+                                                   /* might have created  */
+                                                   /* a duplicate         */
+            const gchar            *image_comment,
+            gint                   *saved_bpp,
+            GimpMetadata           *metadata,
+            GimpMetadataSaveFlags   metadata_flags,
+            GError                **error)
+{
+  gboolean          status = FALSE;
+  TIFF             *tif;
+  GimpColorProfile *profile = NULL;
+  gint              number_of_sub_IFDs = 1;
+  toff_t            sub_IFDs_offsets[1] = { 0UL };
+  gint32            num_layers, *layers, current_layer = 0;
+
+  layers = gimp_image_get_layers (image, &num_layers);
+
+  gimp_progress_init_printf (_("Exporting '%s'"),
+                             gimp_file_get_utf8_name (file));
+
+  /* Open file and write some gloabl data */
+  tif = tiff_open (file, "w", error);
+
+  if (! tif)
+    {
+      if (! error)
+        g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+                     _("Could not open '%s' for writing: %s"),
+                     gimp_file_get_utf8_name (file), g_strerror (errno));
+      goto out;
+    }
+
+  TIFFSetField (tif, TIFFTAG_DOCUMENTNAME, g_file_get_path (file));
+
+  /* The TIFF spec explicitly says ASCII for the image description. */
+  if (image_comment)
+    {
+      const gchar *c = image_comment;
+      gint         len;
+
+      for (len = strlen (c); len; c++, len--)
+        {
+          if ((guchar) *c > 127)
+            {
+              g_message (_("The TIFF format only supports comments in\n"
+                           "7bit ASCII encoding. No comment is saved."));
+              image_comment = NULL;
+
+              break;
+            }
+        }
+    }
+
+  /* do we have a comment?  If so, create a new parasite to hold it,
+   * and attach it to the image. The attach function automatically
+   * detaches a previous incarnation of the parasite. */
+  if (image_comment && *image_comment)
+    {
+      GimpParasite *parasite;
+
+      TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, image_comment);
+      parasite = gimp_parasite_new ("gimp-comment",
+                                    GIMP_PARASITE_PERSISTENT,
+                                    strlen (image_comment) + 1, image_comment);
+      gimp_image_attach_parasite (orig_image, parasite);
+      gimp_parasite_free (parasite);
+    }
+
+  /* do we have an ICC profile? If so, write it to the TIFF file */
+#ifdef TIFFTAG_ICCPROFILE
+  if (tsvals->save_profile)
+    {
+      profile = gimp_image_get_effective_color_profile (orig_image);
+
+      if (profile)
+        {
+          const guint8 *icc_data;
+          gsize         icc_length;
+
+          icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
+
+          TIFFSetField (tif, TIFFTAG_ICCPROFILE, icc_length, icc_data);
+
+          g_object_unref (profile);
+        }
+    }
+#endif
+
+  /* we put the whole file's thumbnail into the first IFD (i.e., page) */
+  if (tsvals->save_thumbnail)
+    TIFFSetField (tif, TIFFTAG_SUBIFD, number_of_sub_IFDs, sub_IFDs_offsets);
+
+  /* write last layer as first page. */
+  if (! save_layer (tif,  tsvals, image,
+                    layers[num_layers - current_layer - 1],
+                    current_layer, num_layers,
+                    orig_image, saved_bpp, error))
+    {
+      goto out;
+    }
+  current_layer++;
+
+  /* write thumbnail */
+  if (tsvals->save_thumbnail)
+    save_thumbnail (tsvals, image, tif);
+
+  /* close file so we can savely let exiv2 work on it to write metadata.
+   * this can be simplified once multi page TIFF is supported by exiv2
+   */
   TIFFFlushData (tif);
   TIFFClose (tif);
+  tif = NULL;
+  if (metadata)
+    save_metadata (file, tsvals, image, metadata, metadata_flags, *saved_bpp);
 
-  gimp_progress_update (1.0);
+  /* write the remaining layers */
+  if (num_layers > 1)
+    {
+      tif = tiff_open (file, "a", error);
 
-  status = TRUE;
+      if (! tif)
+        {
+          if (! error)
+            g_set_error (error, G_FILE_ERROR,
+                          g_file_error_from_errno (errno),
+                          _("Could not open '%s' for writing: %s"),
+                          gimp_file_get_utf8_name (file),
+                          g_strerror (errno));
+          goto out;
+        }
 
- out:
-  if (buffer)
-    g_object_unref (buffer);
+      for (; current_layer < num_layers; current_layer++)
+        {
+          gint tmp_saved_bpp;
+          if (! save_layer (tif,  tsvals, image,
+                            layers[num_layers - current_layer - 1],
+                            current_layer, num_layers, orig_image,
+                            &tmp_saved_bpp, error))
+            {
+              goto out;
+            }
+          if (tmp_saved_bpp != *saved_bpp)
+            {
+              /* this should never happen.
+               * if it does, decide if it's really an error.
+               */
+              g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                           _("Writing pages with different bit depth "
+                             "is strange."));
+              goto out;
+            }
+          gimp_progress_update ((gdouble) (current_layer + 1) / num_layers);
+        }
+    }
 
-  g_free (data);
-  g_free (src);
+  /* close the file for good */
+  if (tif)
+    {
+      TIFFFlushData (tif);
+      TIFFClose (tif);
+    }
+
+  gimp_progress_update (1.0);
 
+  status = TRUE;
+
+out:
   return status;
 }
 
@@ -1069,6 +1274,13 @@ save_dialog (TiffSaveVals  *tsvals,
   gtk_widget_hide (toggle);
 #endif
 
+  toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-layers"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+                                tsvals->save_layers);
+  g_signal_connect (toggle, "toggled",
+                    G_CALLBACK (gimp_toggle_button_update),
+                    &tsvals->save_layers);
+
   gtk_widget_show (dialog);
 
   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
diff --git a/plug-ins/file-tiff/file-tiff-save.h b/plug-ins/file-tiff/file-tiff-save.h
index 04c6ddc402..24e4f85ba1 100644
--- a/plug-ins/file-tiff/file-tiff-save.h
+++ b/plug-ins/file-tiff/file-tiff-save.h
@@ -33,17 +33,19 @@ typedef struct
   gboolean  save_iptc;
   gboolean  save_thumbnail;
   gboolean  save_profile;
+  gboolean  save_layers;
 } TiffSaveVals;
 
 
-gboolean  save_image  (GFile        *file,
-                       TiffSaveVals *tsvals,
-                       gint32        image,
-                       gint32        drawable,
-                       gint32        orig_image,
-                       const gchar  *image_comment,
-                       gint         *saved_bpp,
-                       GError      **error);
+gboolean  save_image  (GFile                  *file,
+                       TiffSaveVals           *tsvals,
+                       gint32                  image,
+                       gint32                  orig_image,
+                       const gchar            *image_comment,
+                       gint                   *saved_bpp,
+                       GimpMetadata           *metadata,
+                       GimpMetadataSaveFlags   metadata_flags,
+                       GError                **error);
 
 gboolean  save_dialog (TiffSaveVals *tsvals,
                        const gchar  *help_id,
diff --git a/plug-ins/file-tiff/file-tiff.c b/plug-ins/file-tiff/file-tiff.c
index 3c50a00567..dbaa6a153d 100644
--- a/plug-ins/file-tiff/file-tiff.c
+++ b/plug-ins/file-tiff/file-tiff.c
@@ -45,7 +45,6 @@
 #include "config.h"
 
 #include <tiffio.h>
-#include <gexiv2/gexiv2.h>
 
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
@@ -90,7 +89,8 @@ static TiffSaveVals tsvals =
   FALSE,               /*  save xmp            */
   FALSE,               /*  save iptc           */
   TRUE,                /*  save thumbnail      */
-  TRUE                 /*  save profile        */
+  TRUE,                /*  save profile        */
+  TRUE                 /*  save layer          */
 };
 
 static gchar *image_comment = NULL;
@@ -221,7 +221,12 @@ run (const gchar      *name,
 
           pages.target = GIMP_PAGE_SELECTOR_TARGET_LAYERS;
 
-          gimp_get_data (LOAD_PROC, &pages.target);
+          gimp_get_data (LOAD_PROC "-target", &pages.target);
+
+          pages.keep_empty_space = TRUE;
+
+          gimp_get_data (LOAD_PROC "-keep-empty-space",
+                         &pages.keep_empty_space);
 
           pages.n_pages = pages.o_pages = TIFFNumberOfDirectories (tif);
 
@@ -268,9 +273,13 @@ run (const gchar      *name,
                   gint32    image;
                   gboolean  resolution_loaded = FALSE;
 
-                  gimp_set_data (LOAD_PROC,
+                  gimp_set_data (LOAD_PROC "-target",
                                  &pages.target, sizeof (pages.target));
 
+                  gimp_set_data (LOAD_PROC "-keep-empty-space",
+                                 &pages.keep_empty_space,
+                                 sizeof (pages.keep_empty_space));
+
                   image = load_image (file, tif, &pages,
                                       &resolution_loaded,
                                       &error);
@@ -347,7 +356,8 @@ run (const gchar      *name,
                                       GIMP_EXPORT_CAN_HANDLE_RGB     |
                                       GIMP_EXPORT_CAN_HANDLE_GRAY    |
                                       GIMP_EXPORT_CAN_HANDLE_INDEXED |
-                                      GIMP_EXPORT_CAN_HANDLE_ALPHA);
+                                      GIMP_EXPORT_CAN_HANDLE_ALPHA   |
+                                      GIMP_EXPORT_CAN_HANDLE_LAYERS);
 
           if (export == GIMP_EXPORT_CANCEL)
             {
@@ -462,55 +472,32 @@ run (const gchar      *name,
 
           file = g_file_new_for_uri (param[3].data.d_string);
 
-          if (save_image (file, &tsvals,
-                          image, drawable, orig_image, image_comment,
-                          &saved_bpp, &error))
+          /* saving with layers is not supporting blend modes, so people might
+           * prefer to save a flat copy. */
+          if (! tsvals.save_layers)
             {
-              if (metadata)
-                {
-
-                  /* See bug 758909: clear TIFFTAG_MIN/MAXSAMPLEVALUE because
-                   * exiv2 saves them with wrong type and the original values
-                   * could be invalid, see also bug 761823
-                   */
-                  gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
-                                             "Exif.Image.0x0118");
-                  gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
-                                             "Exif.Image.0x0119");
-                  gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
-                                             "Exif.Image.PageNumber");
-
-                  gimp_metadata_set_bits_per_sample (metadata, saved_bpp);
-
-                  if (tsvals.save_exif)
-                    metadata_flags |= GIMP_METADATA_SAVE_EXIF;
-                  else
-                    metadata_flags &= ~GIMP_METADATA_SAVE_EXIF;
-
-                  if (tsvals.save_xmp)
-                    metadata_flags |= GIMP_METADATA_SAVE_XMP;
-                  else
-                    metadata_flags &= ~GIMP_METADATA_SAVE_XMP;
-
-                  if (tsvals.save_iptc)
-                    metadata_flags |= GIMP_METADATA_SAVE_IPTC;
-                  else
-                    metadata_flags &= ~GIMP_METADATA_SAVE_IPTC;
-
-                  /* never save metadata thumbnails for TIFF, see bug #729952 */
-                  metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL;
-
-                  if (tsvals.save_profile)
-                    metadata_flags |= GIMP_METADATA_SAVE_COLOR_PROFILE;
-                  else
-                    metadata_flags &= ~GIMP_METADATA_SAVE_COLOR_PROFILE;
-
-                  gimp_image_metadata_save_finish (image,
-                                                   "image/tiff",
-                                                   metadata, metadata_flags,
-                                                   file, NULL);
-                }
+              gint32 transp;
+
+              /* FIXME: Do we have to update drawable, too? */
+              image = gimp_image_duplicate (image);
+
+              /* borrowed from ./libgimp/gimpexport.c:export_merge()
+               * this makes sure that the exported file size is correct. */
+              transp = gimp_layer_new (image, "-",
+                                       gimp_image_width (image),
+                                       gimp_image_height (image),
+                                       gimp_drawable_type (drawable) | 1,
+                                       100.0, GIMP_LAYER_MODE_NORMAL);
+              gimp_image_insert_layer (image, transp, -1, 1);
+              gimp_selection_none (image);
+              gimp_drawable_edit_clear (transp);
+
+              gimp_image_merge_visible_layers (image, GIMP_CLIP_TO_IMAGE);
+            }
 
+          if (save_image (file, &tsvals, image, orig_image, image_comment,
+                          &saved_bpp, metadata, metadata_flags, &error))
+            {
               /*  Store mvals data  */
               gimp_set_data (SAVE_PROC, &tsvals, sizeof (TiffSaveVals));
             }
diff --git a/plug-ins/ui/plug-in-file-tiff.ui b/plug-ins/ui/plug-in-file-tiff.ui
index c1dec4e4d3..f8c19f2a64 100644
--- a/plug-ins/ui/plug-in-file-tiff.ui
+++ b/plug-ins/ui/plug-in-file-tiff.ui
@@ -151,6 +151,20 @@
                 <property name="position">1</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkCheckButton" id="save-iptc">
+                <property name="label" translatable="yes">Save IPTC data</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">False</property>
+                <property name="draw_indicator">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="expand">True</property>
@@ -163,8 +177,8 @@
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <child>
-              <object class="GtkCheckButton" id="save-iptc">
-                <property name="label" translatable="yes">Save IPTC data</property>
+              <object class="GtkCheckButton" id="save-thumbnail">
+                <property name="label" translatable="yes">Save thumbnail</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
@@ -177,8 +191,8 @@
               </packing>
             </child>
             <child>
-              <object class="GtkCheckButton" id="save-thumbnail">
-                <property name="label" translatable="yes">Save thumbnail</property>
+              <object class="GtkCheckButton" id="save-color-profile">
+                <property name="label" translatable="yes">Save color profile</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
@@ -191,8 +205,8 @@
               </packing>
             </child>
             <child>
-              <object class="GtkCheckButton" id="save-color-profile">
-                <property name="label" translatable="yes">Save color profile</property>
+              <object class="GtkCheckButton" id="save-layers">
+                <property name="label" translatable="yes">Save layers</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]