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 9099f317bc
)
This commit is contained in:
@ -196,6 +196,7 @@ static void write_pixel_data (FILE *fd,
|
|||||||
|
|
||||||
static gint32 create_merged_image (gint32 imageID);
|
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_pixel_format (gint32 drawableID);
|
||||||
static const Babl * get_channel_format (gint32 drawableID);
|
static const Babl * get_channel_format (gint32 drawableID);
|
||||||
static const Babl * get_mask_format (gint32 drawableID);
|
static const Babl * get_mask_format (gint32 drawableID);
|
||||||
@ -387,7 +388,6 @@ write_datablock_luni (FILE *fd,
|
|||||||
static gint32
|
static gint32
|
||||||
pack_pb_line (guchar *start,
|
pack_pb_line (guchar *start,
|
||||||
gint32 length,
|
gint32 length,
|
||||||
gint32 stride,
|
|
||||||
guchar *dest_ptr)
|
guchar *dest_ptr)
|
||||||
{
|
{
|
||||||
gint32 remaining = length;
|
gint32 remaining = length;
|
||||||
@ -401,7 +401,7 @@ pack_pb_line (guchar *start,
|
|||||||
i = 0;
|
i = 0;
|
||||||
while ((i < 128) &&
|
while ((i < 128) &&
|
||||||
(remaining - i > 0) &&
|
(remaining - i > 0) &&
|
||||||
(start[0] == start[i*stride]))
|
(start[0] == start[i]))
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
if (i > 1) /* Match found */
|
if (i > 1) /* Match found */
|
||||||
@ -410,7 +410,7 @@ pack_pb_line (guchar *start,
|
|||||||
*dest_ptr++ = -(i - 1);
|
*dest_ptr++ = -(i - 1);
|
||||||
*dest_ptr++ = *start;
|
*dest_ptr++ = *start;
|
||||||
|
|
||||||
start += i*stride;
|
start += i;
|
||||||
remaining -= i;
|
remaining -= i;
|
||||||
length += 2;
|
length += 2;
|
||||||
}
|
}
|
||||||
@ -419,8 +419,8 @@ pack_pb_line (guchar *start,
|
|||||||
i = 0;
|
i = 0;
|
||||||
while ((i < 128) &&
|
while ((i < 128) &&
|
||||||
(remaining - (i + 1) > 0) &&
|
(remaining - (i + 1) > 0) &&
|
||||||
(start[i*stride] != start[(i + 1)*stride] ||
|
(start[i] != start[i + 1] ||
|
||||||
remaining - (i + 2) <= 0 || start[i*stride] != start[(i+2)*stride]))
|
remaining - (i + 2) <= 0 || start[i] != start[i+2]))
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
/* If there's only 1 remaining, the previous WHILE stmt doesn't
|
/* 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;
|
*dest_ptr++ = i - 1;
|
||||||
for (j = 0; j < i; j++)
|
for (j = 0; j < i; j++)
|
||||||
{
|
{
|
||||||
*dest_ptr++ = start[j*stride];
|
*dest_ptr++ = start[j];
|
||||||
}
|
}
|
||||||
start += i*stride;
|
start += i;
|
||||||
remaining -= i;
|
remaining -= i;
|
||||||
length += i + 1;
|
length += i + 1;
|
||||||
}
|
}
|
||||||
@ -535,7 +535,7 @@ save_header (FILE *fd,
|
|||||||
"channels");
|
"channels");
|
||||||
write_gint32 (fd, PSDImageData.image_height, "rows");
|
write_gint32 (fd, PSDImageData.image_height, "rows");
|
||||||
write_gint32 (fd, PSDImageData.image_width, "columns");
|
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");
|
write_gint16 (fd, gimpBaseTypeToPsdMode (PSDImageData.baseType), "mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,6 +837,7 @@ get_compress_channel_data (guchar *channel_data,
|
|||||||
gint32 channel_cols,
|
gint32 channel_cols,
|
||||||
gint32 channel_rows,
|
gint32 channel_rows,
|
||||||
gint32 stride,
|
gint32 stride,
|
||||||
|
gint32 bpc,
|
||||||
gint16 *LengthsTable,
|
gint16 *LengthsTable,
|
||||||
guchar *remdata)
|
guchar *remdata)
|
||||||
{
|
{
|
||||||
@ -844,15 +845,72 @@ get_compress_channel_data (guchar *channel_data,
|
|||||||
gint32 len; /* Length of compressed data */
|
gint32 len; /* Length of compressed data */
|
||||||
guchar *start; /* Starting position of a row in channel_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 */
|
/* For every row in the channel */
|
||||||
|
|
||||||
len = 0;
|
len = 0;
|
||||||
for (i = 0; i < channel_rows; i++)
|
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 */
|
/* Create packed data for this row */
|
||||||
LengthsTable[i] = pack_pb_line (start, channel_cols, stride,
|
LengthsTable[i] = pack_pb_line (start, channel_cols * bpc,
|
||||||
&remdata[len]);
|
&remdata[len]);
|
||||||
len += LengthsTable[i];
|
len += LengthsTable[i];
|
||||||
}
|
}
|
||||||
@ -879,6 +937,7 @@ save_layer_and_mask (FILE *fd,
|
|||||||
gchar *layerName; /* Layer name */
|
gchar *layerName; /* Layer name */
|
||||||
gint mask; /* Layer mask */
|
gint mask; /* Layer mask */
|
||||||
gint depth; /* Layer group nesting depth */
|
gint depth; /* Layer group nesting depth */
|
||||||
|
gint bpc; /* Image BPC */
|
||||||
|
|
||||||
glong eof_pos; /* Position: End of file */
|
glong eof_pos; /* Position: End of file */
|
||||||
glong ExtraDataPos; /* Position: Extra data length */
|
glong ExtraDataPos; /* Position: Extra data length */
|
||||||
@ -912,6 +971,8 @@ save_layer_and_mask (FILE *fd,
|
|||||||
|
|
||||||
depth = 0;
|
depth = 0;
|
||||||
|
|
||||||
|
bpc = get_bpc (image_id);
|
||||||
|
|
||||||
/* Layer records section */
|
/* Layer records section */
|
||||||
/* GIMP layers must be written in reverse order */
|
/* 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. */
|
will modify it later when writing data. */
|
||||||
|
|
||||||
ChannelLengthPos[i][j] = ftell (fd);
|
ChannelLengthPos[i][j] = ftell (fd);
|
||||||
ChanSize = sizeof (gint16) + (layerWidth * layerHeight);
|
ChanSize = sizeof (gint16) + (layerWidth * layerHeight * bpc);
|
||||||
|
|
||||||
write_gint32 (fd, ChanSize, "Channel Size");
|
write_gint32 (fd, ChanSize, "Channel Size");
|
||||||
IFDBG printf ("\t\t\tLength: %d\n", ChanSize);
|
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 height = gegl_buffer_get_height (buffer);
|
||||||
gint32 width = gegl_buffer_get_width (buffer);
|
gint32 width = gegl_buffer_get_width (buffer);
|
||||||
gint32 bytes;
|
gint32 bytes;
|
||||||
|
gint32 components;
|
||||||
|
gint32 bpc;
|
||||||
gint32 colors;
|
gint32 colors;
|
||||||
gint32 y;
|
gint32 y;
|
||||||
gint32 len; /* Length of compressed data */
|
gint32 len; /* Length of compressed data */
|
||||||
@ -1222,8 +1285,10 @@ write_pixel_data (FILE *fd,
|
|||||||
format = get_pixel_format (drawableID);
|
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) &&
|
if (gimp_drawable_has_alpha (drawableID) &&
|
||||||
! gimp_drawable_is_indexed (drawableID))
|
! gimp_drawable_is_indexed (drawableID))
|
||||||
@ -1231,7 +1296,7 @@ write_pixel_data (FILE *fd,
|
|||||||
|
|
||||||
LengthsTable = g_new (gint16, height);
|
LengthsTable = g_new (gint16, height);
|
||||||
rledata = g_new (guchar, (MIN (height, tile_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);
|
data = g_new (guchar, MIN (height, tile_height) * width * bytes);
|
||||||
|
|
||||||
@ -1242,17 +1307,17 @@ write_pixel_data (FILE *fd,
|
|||||||
height = 0;
|
height = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < bytes; i++)
|
for (i = 0; i < components; i++)
|
||||||
{
|
{
|
||||||
gint chan;
|
gint chan;
|
||||||
|
|
||||||
len = 0;
|
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)
|
if (i == 0)
|
||||||
{
|
{
|
||||||
chan = bytes - 1;
|
chan = components - 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1293,10 +1358,10 @@ write_pixel_data (FILE *fd,
|
|||||||
MIN (height - y, tile_height)),
|
MIN (height - y, tile_height)),
|
||||||
1.0, format, data,
|
1.0, format, data,
|
||||||
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
||||||
tlen = get_compress_channel_data (&data[chan],
|
tlen = get_compress_channel_data (&data[chan * bpc],
|
||||||
width,
|
width,
|
||||||
MIN(height - y, tile_height),
|
MIN(height - y, tile_height),
|
||||||
bytes,
|
bytes, bpc,
|
||||||
&LengthsTable[y],
|
&LengthsTable[y],
|
||||||
rledata);
|
rledata);
|
||||||
len += tlen;
|
len += tlen;
|
||||||
@ -1339,7 +1404,7 @@ write_pixel_data (FILE *fd,
|
|||||||
|
|
||||||
if (ltable_offset > 0)
|
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",
|
IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld\n",
|
||||||
length_table_pos);
|
length_table_pos);
|
||||||
}
|
}
|
||||||
@ -1366,7 +1431,7 @@ write_pixel_data (FILE *fd,
|
|||||||
tlen = get_compress_channel_data (&data[0],
|
tlen = get_compress_channel_data (&data[0],
|
||||||
width,
|
width,
|
||||||
MIN(height - y, tile_height),
|
MIN(height - y, tile_height),
|
||||||
1,
|
bpc, bpc,
|
||||||
&LengthsTable[y],
|
&LengthsTable[y],
|
||||||
rledata);
|
rledata);
|
||||||
len += tlen;
|
len += tlen;
|
||||||
@ -1635,64 +1700,96 @@ save_image (const gchar *filename,
|
|||||||
return TRUE;
|
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 *
|
static const Babl *
|
||||||
get_pixel_format (gint32 drawableID)
|
get_pixel_format (gint32 drawableID)
|
||||||
{
|
{
|
||||||
const Babl *format;
|
const gchar *model;
|
||||||
|
gint bpc;
|
||||||
|
gchar format[32];
|
||||||
|
|
||||||
switch (gimp_drawable_type (drawableID))
|
switch (gimp_drawable_type (drawableID))
|
||||||
{
|
{
|
||||||
case GIMP_GRAY_IMAGE:
|
case GIMP_GRAY_IMAGE:
|
||||||
format = babl_format ("Y' u8");
|
model = "Y'";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GIMP_GRAYA_IMAGE:
|
case GIMP_GRAYA_IMAGE:
|
||||||
format = babl_format ("Y'A u8");
|
model = "Y'A";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GIMP_RGB_IMAGE:
|
case GIMP_RGB_IMAGE:
|
||||||
format = babl_format ("R'G'B' u8");
|
model = "R'G'B'";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GIMP_RGBA_IMAGE:
|
case GIMP_RGBA_IMAGE:
|
||||||
format = babl_format ("R'G'B'A u8");
|
model = "R'G'B'A";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GIMP_INDEXED_IMAGE:
|
case GIMP_INDEXED_IMAGE:
|
||||||
case GIMP_INDEXEDA_IMAGE:
|
case GIMP_INDEXEDA_IMAGE:
|
||||||
format = gimp_drawable_get_format(drawableID);
|
return gimp_drawable_get_format (drawableID);
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return NULL;
|
g_return_val_if_reached (NULL);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 *
|
static const Babl *
|
||||||
get_channel_format (gint32 drawableID)
|
get_channel_format (gint32 drawableID)
|
||||||
{
|
{
|
||||||
const Babl *format;
|
gint bpc;
|
||||||
|
gchar format[32];
|
||||||
|
|
||||||
/* eventually we'll put a switch statement for bit depth here to
|
bpc = get_bpc (gimp_item_get_image (drawableID));
|
||||||
* support higher depth exports */
|
|
||||||
format = babl_format ("Y u8");
|
|
||||||
|
|
||||||
return format;
|
sprintf (format, "Y u%d", 8 * bpc);
|
||||||
|
|
||||||
|
return babl_format (format);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Babl *
|
static const Babl *
|
||||||
get_mask_format (gint32 drawableID)
|
get_mask_format (gint32 drawableID)
|
||||||
{
|
{
|
||||||
const Babl *format;
|
return get_channel_format (drawableID);
|
||||||
|
|
||||||
/* eventually we'll put a switch statement for bit depth here to
|
|
||||||
* support higher depth exports */
|
|
||||||
format = babl_format ("Y u8");
|
|
||||||
|
|
||||||
return format;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
Reference in New Issue
Block a user