[gimp/gimp-2-10] plug-ins: add support for exporting 16-bit PSDs



commit 7e673f4e5231612f4308d92aec0aaaada5f3f82e
Author: Ell <ell_se yahoo com>
Date:   Thu Apr 9 00:58:30 2020 +0300

    plug-ins: add support for exporting 16-bit PSDs
    
    In file-psd, add support for exporting high bit-depth images.  This
    is currently limited to 16-bit images, since 32-bit images seem to
    have a different structure (our loading code can successfully load
    32-bit images exported by the plug-in, but not actual 32-bit PSD
    files saved in Photoshop.)  Higher bit-depth images are saved as
    16-bit for now.
    
    Note also that when saving a linear image with a built-in linear
    profile the result is wrong (the image is exported with a linear-
    TRC profile, but the data is perceptual), but this is a general
    problem we have to fix, not restricted to the PSD plug-in.
    
    (cherry picked from commit 9099f317bc7496c30466dc54ef8bf7bc0ddd6c93)

 plug-ins/file-psd/psd-save.c | 195 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 146 insertions(+), 49 deletions(-)
---
diff --git a/plug-ins/file-psd/psd-save.c b/plug-ins/file-psd/psd-save.c
index 087273bee4..52eab07387 100644
--- a/plug-ins/file-psd/psd-save.c
+++ b/plug-ins/file-psd/psd-save.c
@@ -196,6 +196,7 @@ static void          write_pixel_data     (FILE          *fd,
 
 static gint32        create_merged_image  (gint32         imageID);
 
+static gint          get_bpc              (gint32         imageID);
 static const Babl  * get_pixel_format     (gint32         drawableID);
 static const Babl  * get_channel_format   (gint32         drawableID);
 static const Babl  * get_mask_format      (gint32         drawableID);
@@ -387,7 +388,6 @@ write_datablock_luni (FILE        *fd,
 static gint32
 pack_pb_line (guchar *start,
               gint32  length,
-              gint32  stride,
               guchar *dest_ptr)
 {
   gint32  remaining = length;
@@ -401,7 +401,7 @@ pack_pb_line (guchar *start,
       i = 0;
       while ((i < 128) &&
              (remaining - i > 0) &&
-             (start[0] == start[i*stride]))
+             (start[0] == start[i]))
         i++;
 
       if (i > 1)              /* Match found */
@@ -410,17 +410,17 @@ pack_pb_line (guchar *start,
           *dest_ptr++ = -(i - 1);
           *dest_ptr++ = *start;
 
-          start += i*stride;
+          start += i;
           remaining -= i;
           length += 2;
         }
       else       /* Look for characters different from the previous */
         {
           i = 0;
-          while ((i < 128)             &&
+          while ((i < 128)                 &&
                  (remaining - (i + 1) > 0) &&
-                 (start[i*stride] != start[(i + 1)*stride] ||
-                  remaining - (i + 2) <= 0  || start[i*stride] != start[(i+2)*stride]))
+                 (start[i] != start[i + 1] ||
+                  remaining - (i + 2) <= 0 || start[i] != start[i+2]))
             i++;
 
           /* If there's only 1 remaining, the previous WHILE stmt doesn't
@@ -436,9 +436,9 @@ pack_pb_line (guchar *start,
               *dest_ptr++ = i - 1;
               for (j = 0; j < i; j++)
                 {
-                  *dest_ptr++ = start[j*stride];
+                  *dest_ptr++ = start[j];
                 }
-              start += i*stride;
+              start += i;
               remaining -= i;
               length += i + 1;
             }
@@ -535,7 +535,7 @@ save_header (FILE   *fd,
                 "channels");
   write_gint32 (fd, PSDImageData.image_height, "rows");
   write_gint32 (fd, PSDImageData.image_width, "columns");
-  write_gint16 (fd, 8, "depth");  /* Exporting can only be done in 8 bits at the moment. */
+  write_gint16 (fd, 8 * get_bpc (image_id), "depth");
   write_gint16 (fd, gimpBaseTypeToPsdMode (PSDImageData.baseType), "mode");
 }
 
@@ -837,6 +837,7 @@ get_compress_channel_data (guchar  *channel_data,
                            gint32   channel_cols,
                            gint32   channel_rows,
                            gint32   stride,
+                           gint32   bpc,
                            gint16  *LengthsTable,
                            guchar  *remdata)
 {
@@ -844,16 +845,73 @@ get_compress_channel_data (guchar  *channel_data,
   gint32  len;                 /* Length of compressed data */
   guchar *start;               /* Starting position of a row in channel_data */
 
+  stride /= bpc;
+
+  /* Pack channel data, and perform byte-order conversion */
+  switch (bpc)
+    {
+    case 1:
+      {
+        if (stride > 1)
+          {
+            const guint8 *src  = (const guint8 *) channel_data;
+            guint8       *dest = (guint8       *) channel_data;
+
+            for (i = 0; i < channel_rows * channel_cols; i++)
+              {
+                *dest = *src;
+
+                dest++;
+                src += stride;
+              }
+          }
+      }
+      break;
+
+    case 2:
+      {
+        const guint16 *src  = (const guint16 *) channel_data;
+        guint16       *dest = (guint16       *) channel_data;
+
+        for (i = 0; i < channel_rows * channel_cols; i++)
+          {
+            *dest = GUINT16_TO_BE (*src);
+
+            dest++;
+            src += stride;
+          }
+      }
+      break;
+
+    case 4:
+      {
+        const guint32 *src  = (const guint32 *) channel_data;
+        guint32       *dest = (guint32       *) channel_data;
+
+        for (i = 0; i < channel_rows * channel_cols; i++)
+          {
+            *dest = GUINT32_TO_BE (*src);
+
+            dest++;
+            src += stride;
+          }
+      }
+      break;
+
+    default:
+      g_return_val_if_reached (0);
+    }
+
   /* For every row in the channel */
 
   len = 0;
   for (i = 0; i < channel_rows; i++)
     {
-      start = channel_data + (i * channel_cols * stride);
+      start = channel_data + i * channel_cols * bpc;
 
       /* Create packed data for this row */
-      LengthsTable[i] = pack_pb_line (start, channel_cols, stride,
-                                       &remdata[len]);
+      LengthsTable[i] = pack_pb_line (start, channel_cols * bpc,
+                                      &remdata[len]);
       len += LengthsTable[i];
     }
 
@@ -879,6 +937,7 @@ save_layer_and_mask (FILE   *fd,
   gchar        *layerName;              /* Layer name */
   gint          mask;                   /* Layer mask */
   gint          depth;                  /* Layer group nesting depth */
+  gint          bpc;                    /* Image BPC */
 
   glong         eof_pos;                /* Position: End of file */
   glong         ExtraDataPos;           /* Position: Extra data length */
@@ -912,6 +971,8 @@ save_layer_and_mask (FILE   *fd,
 
   depth = 0;
 
+  bpc = get_bpc (image_id);
+
   /* Layer records section */
   /* GIMP layers must be written in reverse order */
 
@@ -992,7 +1053,7 @@ save_layer_and_mask (FILE   *fd,
              will modify it later when writing data.  */
 
           ChannelLengthPos[i][j] = ftell (fd);
-          ChanSize = sizeof (gint16) + (layerWidth * layerHeight);
+          ChanSize = sizeof (gint16) + (layerWidth * layerHeight * bpc);
 
           write_gint32 (fd, ChanSize, "Channel Size");
           IFDBG printf ("\t\t\tLength: %d\n", ChanSize);
@@ -1192,6 +1253,8 @@ write_pixel_data (FILE     *fd,
   gint32        height = gegl_buffer_get_height (buffer);
   gint32        width  = gegl_buffer_get_width (buffer);
   gint32        bytes;
+  gint32        components;
+  gint32        bpc;
   gint32        colors;
   gint32        y;
   gint32        len;                  /* Length of compressed data */
@@ -1221,9 +1284,11 @@ write_pixel_data (FILE     *fd,
   else
     format = get_pixel_format (drawableID);
 
-  bytes = babl_format_get_bytes_per_pixel (format);
+  bytes      = babl_format_get_bytes_per_pixel (format);
+  components = babl_format_get_n_components    (format);
+  bpc        = bytes / components;
 
-  colors = bytes;
+  colors = components;
 
   if (gimp_drawable_has_alpha  (drawableID) &&
       ! gimp_drawable_is_indexed (drawableID))
@@ -1231,7 +1296,7 @@ write_pixel_data (FILE     *fd,
 
   LengthsTable = g_new (gint16, height);
   rledata = g_new (guchar, (MIN (height, tile_height) *
-                            (width + 10 + (width / 100))));
+                            (width + 10 + (width / 100))) * bpc);
 
   data = g_new (guchar, MIN (height, tile_height) * width * bytes);
 
@@ -1242,17 +1307,17 @@ write_pixel_data (FILE     *fd,
       height = 0;
     }
 
-  for (i = 0; i < bytes; i++)
+  for (i = 0; i < components; i++)
     {
       gint chan;
 
       len = 0;
 
-      if (bytes != colors && ltable_offset == 0) /* Need to write alpha channel first, except in image data 
section */
+      if (components != colors && ltable_offset == 0) /* Need to write alpha channel first, except in image 
data section */
         {
           if (i == 0)
             {
-              chan = bytes - 1;
+              chan = components - 1;
             }
           else
             {
@@ -1293,12 +1358,12 @@ write_pixel_data (FILE     *fd,
                                            MIN (height - y, tile_height)),
                            1.0, format, data,
                            GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
-          tlen = get_compress_channel_data (&data[chan],
-                                             width,
-                                             MIN(height - y, tile_height),
-                                             bytes,
-                                             &LengthsTable[y],
-                                             rledata);
+          tlen = get_compress_channel_data (&data[chan * bpc],
+                                            width,
+                                            MIN(height - y, tile_height),
+                                            bytes, bpc,
+                                            &LengthsTable[y],
+                                            rledata);
           len += tlen;
           xfwrite (fd, rledata, tlen, "Compressed pixel data");
           IF_DEEP_DBG printf ("\t\t\t\t. Writing compressed pixels, stream of %d\n", tlen);
@@ -1339,7 +1404,7 @@ write_pixel_data (FILE     *fd,
 
       if (ltable_offset > 0)
         {
-          length_table_pos = ltable_offset + 2 * (bytes+1) * height;
+          length_table_pos = ltable_offset + 2 * (components+1) * height;
           IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld\n",
                               length_table_pos);
         }
@@ -1366,7 +1431,7 @@ write_pixel_data (FILE     *fd,
           tlen = get_compress_channel_data (&data[0],
                                             width,
                                             MIN(height - y, tile_height),
-                                            1,
+                                            bpc, bpc,
                                             &LengthsTable[y],
                                             rledata);
           len += tlen;
@@ -1635,64 +1700,96 @@ save_image (const gchar  *filename,
   return TRUE;
 }
 
+static gint
+get_bpc (gint32 image_id)
+{
+  switch (gimp_image_get_precision (image_id))
+    {
+    case GIMP_PRECISION_U8_LINEAR:
+    case GIMP_PRECISION_U8_GAMMA:
+      return 1;
+
+    case GIMP_PRECISION_U16_LINEAR:
+    case GIMP_PRECISION_U16_GAMMA:
+    case GIMP_PRECISION_HALF_LINEAR:
+    case GIMP_PRECISION_HALF_GAMMA:
+      return 2;
+
+    case GIMP_PRECISION_U32_LINEAR:
+    case GIMP_PRECISION_U32_GAMMA:
+    case GIMP_PRECISION_FLOAT_LINEAR:
+    case GIMP_PRECISION_FLOAT_GAMMA:
+    default:
+      /* FIXME: we *should* encode the image as u32 in this case, but simply
+       * using the same code as for the other cases produces invalid psd files
+       * (they're rejected by photoshop, although they can be read by the
+       * corresponding psd-load.c code, which in turn can't actually read
+       * photoshop-generated u32 files.)
+       *
+       * simply encode the image as u16 for now.
+       */
+      /* return 4; */
+      return 2;
+    }
+}
+
 static const Babl *
 get_pixel_format (gint32 drawableID)
 {
-  const Babl *format;
+  const gchar *model;
+  gint         bpc;
+  gchar        format[32];
 
   switch (gimp_drawable_type (drawableID))
     {
     case GIMP_GRAY_IMAGE:
-      format = babl_format ("Y' u8");
+      model = "Y'";
       break;
 
     case GIMP_GRAYA_IMAGE:
-      format = babl_format ("Y'A u8");
+      model = "Y'A";
       break;
 
     case GIMP_RGB_IMAGE:
-      format = babl_format ("R'G'B' u8");
+      model = "R'G'B'";
       break;
 
     case GIMP_RGBA_IMAGE:
-      format = babl_format ("R'G'B'A u8");
+      model = "R'G'B'A";
       break;
 
     case GIMP_INDEXED_IMAGE:
     case GIMP_INDEXEDA_IMAGE:
-      format = gimp_drawable_get_format(drawableID);
-      break;
+      return gimp_drawable_get_format (drawableID);
 
     default:
-      return NULL;
-      break;
+      g_return_val_if_reached (NULL);
     }
 
-  return format;
+  bpc = get_bpc (gimp_item_get_image (drawableID));
+
+  sprintf (format, "%s u%d", model, 8 * bpc);
+
+  return babl_format (format);
 }
 
 static const Babl *
 get_channel_format (gint32 drawableID)
 {
-  const Babl *format;
+  gint  bpc;
+  gchar format[32];
+
+  bpc = get_bpc (gimp_item_get_image (drawableID));
 
-  /* eventually we'll put a switch statement for bit depth here to
-   * support higher depth exports */
-  format = babl_format ("Y u8");
+  sprintf (format, "Y u%d", 8 * bpc);
 
-  return format;
+  return babl_format (format);
 }
 
 static const Babl *
 get_mask_format (gint32 drawableID)
 {
-  const Babl *format;
-
-  /* eventually we'll put a switch statement for bit depth here to
-   * support higher depth exports */
-  format = babl_format ("Y u8");
-
-  return format;
+  return get_channel_format (drawableID);
 }
 
 static void


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