gimp/plug-ins/file-tiff/file-tiff-export.c

1433 lines
47 KiB
C
Raw Permalink Normal View History

/* tiff exporting for GIMP
* -Peter Mattis
*
* The TIFF loading code has been completely revamped by Nick Lamb
* njl195@zepler.org.uk -- 18 May 1998
* And it now gains support for tiles (and doubtless a zillion bugs)
* njl195@zepler.org.uk -- 12 June 1999
* LZW patent fuss continues :(
* njl195@zepler.org.uk -- 20 April 2000
* The code for this filter is based on "tifftopnm" and "pnmtotiff",
* 2 programs that are a part of the netpbm package.
* khk@khk.net -- 13 May 2000
* Added support for ICCPROFILE tiff tag. If this tag is present in a
* TIFF file, then a parasite is created and vice versa.
* peter@kirchgessner.net -- 29 Oct 2002
* Progress bar only when run interactive
* Added support for layer offsets - pablo.dangelo@web.de -- 7 Jan 2004
* Honor EXTRASAMPLES tag while loading images with alphachannel
* pablo.dangelo@web.de -- 16 Jan 2004
*/
/*
* tifftopnm.c - converts a Tagged Image File to a portable anymap
*
* Derived by Jef Poskanzer from tif2ras.c, which is:
*
* Copyright (c) 1990 by Sun Microsystems, Inc.
*
* Author: Patrick J. Naughton
* naughton@wind.sun.com
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation.
*
* This file is provided AS IS with no warranties of any kind. The author
* shall have no liability with respect to the infringement of copyrights,
* trade secrets or any patents by this file or any part thereof. In no
* event will the author be liable for any lost revenue or profits or
* other special, indirect and consequential damages.
*/
#include "config.h"
#include <errno.h>
#include <string.h>
#include <tiffio.h>
#include <gexiv2/gexiv2.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "file-tiff.h"
#include "file-tiff-io.h"
#include "file-tiff-export.h"
#include "libgimp/stdplugins-intl.h"
#define PLUG_IN_ROLE "gimp-file-tiff-export"
static gboolean save_paths (TIFF *tif,
GimpImage *image,
gdouble width,
gdouble height,
gint offset_x,
gint offset_y);
static void byte2bit (const guchar *byteline,
gint width,
guchar *bitline,
gboolean invert);
static void
double_to_psd_fixed (gdouble value,
gchar *target)
{
gdouble in, frac;
gint i, f;
frac = modf (value, &in);
if (frac < 0)
{
in -= 1;
frac += 1;
}
i = (gint) CLAMP (in, -16, 15);
f = CLAMP ((gint) (frac * 0xFFFFFF), 0, 0xFFFFFF);
target[0] = i & 0xFF;
target[1] = (f >> 16) & 0xFF;
target[2] = (f >> 8) & 0xFF;
target[3] = f & 0xFF;
}
static gboolean
save_paths (TIFF *tif,
GimpImage *image,
gdouble width,
gdouble height,
gint offset_x,
gint offset_y)
{
gint id = 2000; /* Photoshop paths have IDs >= 2000 */
GList *path;
GList *iter;
gint v;
gsize num_strokes;
gint *strokes, s;
GString *ps_tag;
path = gimp_image_list_paths (image);
if (! path)
return FALSE;
ps_tag = g_string_new ("");
/* Only up to 1000 paths supported */
for (iter = path, v = 0;
iter && v < 1000;
iter = g_list_next (iter), v++)
{
GString *data;
gchar *name, *nameend;
gsize len;
gint lenpos;
gchar pointrecord[26] = { 0, };
gchar *tmpname;
GError *err = NULL;
data = g_string_new ("8BIM");
g_string_append_c (data, id / 256);
g_string_append_c (data, id % 256);
/*
* - use iso8859-1 if possible
* - otherwise use UTF-8, prepended with \xef\xbb\xbf (Byte-Order-Mark)
*/
name = gimp_item_get_name (iter->data);
tmpname = g_convert (name, -1, "iso8859-1", "utf-8", NULL, &len, &err);
if (tmpname && err == NULL)
{
g_string_append_c (data, MIN (len, 255));
g_string_append_len (data, tmpname, MIN (len, 255));
g_free (tmpname);
}
else
{
/* conversion failed, we fall back to UTF-8 */
len = g_utf8_strlen (name, 255 - 3); /* need three marker-bytes */
nameend = g_utf8_offset_to_pointer (name, len);
len = nameend - name; /* in bytes */
g_assert (len + 3 <= 255);
g_string_append_c (data, len + 3);
g_string_append_len (data, "\xEF\xBB\xBF", 3); /* Unicode 0xfeff */
g_string_append_len (data, name, len);
if (tmpname)
g_free (tmpname);
}
if (data->len % 2) /* padding to even size */
g_string_append_c (data, 0);
g_free (name);
lenpos = data->len;
g_string_append_len (data, "\0\0\0\0", 4); /* will be filled in later */
len = data->len; /* to calculate the data size later */
pointrecord[1] = 6; /* fill rule record */
g_string_append_len (data, pointrecord, 26);
strokes = gimp_path_get_strokes (iter->data, &num_strokes);
for (s = 0; s < num_strokes; s++)
{
GimpPathStrokeType type;
gdouble *points;
gsize num_points;
gboolean closed;
gint p = 0;
type = gimp_path_stroke_get_points (iter->data, strokes[s],
&num_points, &points, &closed);
if (type != GIMP_PATH_STROKE_TYPE_BEZIER ||
num_points > 65535 ||
num_points % 6)
{
g_printerr ("tiff-export: unsupported stroke type: "
"%d (%" G_GSIZE_FORMAT " points)\n", type, num_points);
continue;
}
memset (pointrecord, 0, 26);
pointrecord[1] = closed ? 0 : 3;
pointrecord[2] = (num_points / 6) / 256;
pointrecord[3] = (num_points / 6) % 256;
g_string_append_len (data, pointrecord, 26);
for (p = 0; p < num_points; p += 6)
{
pointrecord[1] = closed ? 2 : 5;
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);
}
}
g_free (strokes);
/* fix up the length */
len = data->len - len;
data->str[lenpos + 0] = (len & 0xFF000000) >> 24;
data->str[lenpos + 1] = (len & 0x00FF0000) >> 16;
data->str[lenpos + 2] = (len & 0x0000FF00) >> 8;
data->str[lenpos + 3] = (len & 0x000000FF) >> 0;
g_string_append_len (ps_tag, data->str, data->len);
g_string_free (data, TRUE);
id ++;
}
TIFFSetField (tif, TIFFTAG_PHOTOSHOP, ps_tag->len, ps_tag->str);
g_string_free (ps_tag, TRUE);
g_list_free (path);
return TRUE;
}
/*
* pnmtotiff.c - converts a portable anymap to a Tagged Image File
*
* Derived by Jef Poskanzer from ras2tif.c, which is:
*
* Copyright (c) 1990 by Sun Microsystems, Inc.
*
* Author: Patrick J. Naughton
* naughton@wind.sun.com
*
* This file is provided AS IS with no warranties of any kind. The author
* shall have no liability with respect to the infringement of copyrights,
* trade secrets or any patents by this file or any part thereof. In no
* event will the author be liable for any lost revenue or profits or
* other special, indirect and consequential damages.
*/
static gboolean
save_layer (TIFF *tif,
GObject *config,
const Babl *space,
GimpImage *image,
GimpLayer *layer,
gint32 page,
gint32 num_pages,
GimpImage *orig_image, /* the export function might
* have created a duplicate */
gint origin_x,
gint origin_y,
gint *saved_bpp,
gboolean out_linear,
GError **error)
{
gboolean status = FALSE;
gushort red[256];
gushort grn[256];
gushort blu[256];
gint cols, rows, row, i;
glong rowsperstrip;
gushort compression;
gushort extra_samples[1];
gboolean alpha;
gshort predictor;
gshort photometric;
const Babl *format;
const Babl *type;
gshort samplesperpixel;
gshort bitspersample;
gshort sampleformat;
gint bytesperrow;
guchar *src = NULL;
guchar *data = NULL;
GimpPalette *palette;
guchar *cmap;
gint num_colors;
gint success;
GimpImageType drawable_type;
GeglBuffer *buffer = NULL;
gint tile_height;
gint y, yend;
gboolean is_bw = FALSE;
gboolean invert = TRUE;
const guchar bw_map[] = { 0, 0, 0, 255, 255, 255 };
const guchar wb_map[] = { 255, 255, 255, 0, 0, 0 };
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;
gint config_compression;
gchar *config_comment;
gboolean config_save_comment;
gboolean config_save_transp_pixels;
gboolean config_save_geotiff_tags;
gboolean config_save_profile;
gboolean config_cmyk;
g_object_get (config,
"gimp-comment", &config_comment,
"include-comment", &config_save_comment,
"save-transparent-pixels", &config_save_transp_pixels,
"save-geotiff", &config_save_geotiff_tags,
"include-color-profile", &config_save_profile,
"cmyk", &config_cmyk,
NULL);
config_compression = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), "compression");
compression = gimp_compression_to_tiff_compression (config_compression);
layer_name = gimp_item_get_name (GIMP_ITEM (layer));
/* Disabled because this isn't in older releases of libtiff, and it
* wasn't helping much anyway
*/
#if 0
if (TIFFFindCODEC((uint16) compression) == NULL)
compression = COMPRESSION_NONE; /* CODEC not available */
#endif
predictor = 0;
tile_height = gimp_tile_height ();
rowsperstrip = tile_height;
drawable_type = gimp_drawable_type (GIMP_DRAWABLE (layer));
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
format = gegl_buffer_get_format (buffer);
type = babl_format_get_type (format, 0);
switch (gimp_image_get_precision (image))
{
case GIMP_PRECISION_U8_LINEAR:
case GIMP_PRECISION_U8_NON_LINEAR:
case GIMP_PRECISION_U8_PERCEPTUAL:
/* Promote to 16-bit if storage and export TRC don't match. */
if ((gimp_image_get_precision (image) == GIMP_PRECISION_U8_LINEAR && out_linear) ||
(gimp_image_get_precision (image) != GIMP_PRECISION_U8_LINEAR && ! out_linear))
{
bitspersample = 8;
sampleformat = SAMPLEFORMAT_UINT;
}
else
{
bitspersample = 16;
sampleformat = SAMPLEFORMAT_UINT;
type = babl_type ("u16");
}
break;
case GIMP_PRECISION_U16_LINEAR:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
case GIMP_PRECISION_U16_NON_LINEAR:
case GIMP_PRECISION_U16_PERCEPTUAL:
bitspersample = 16;
sampleformat = SAMPLEFORMAT_UINT;
break;
case GIMP_PRECISION_U32_LINEAR:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
case GIMP_PRECISION_U32_NON_LINEAR:
case GIMP_PRECISION_U32_PERCEPTUAL:
bitspersample = 32;
sampleformat = SAMPLEFORMAT_UINT;
break;
case GIMP_PRECISION_HALF_LINEAR:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
case GIMP_PRECISION_HALF_NON_LINEAR:
case GIMP_PRECISION_HALF_PERCEPTUAL:
bitspersample = 16;
sampleformat = SAMPLEFORMAT_IEEEFP;
break;
default:
case GIMP_PRECISION_FLOAT_LINEAR:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
case GIMP_PRECISION_FLOAT_NON_LINEAR:
case GIMP_PRECISION_FLOAT_PERCEPTUAL:
bitspersample = 32;
sampleformat = SAMPLEFORMAT_IEEEFP;
break;
case GIMP_PRECISION_DOUBLE_LINEAR:
Initial space invasion commit in GIMP All babl formats now have a space equivalent to a color profile, determining the format's primaries and TRCs. This commit makes GIMP aware of this. libgimp: - enum GimpPrecision: rename GAMMA values to NON_LINEAR and keep GAMMA as deprecated aliases, add PERCEPTUAL values so we now have LINEAR, NON_LINEAR and PERCPTUAL for each encoding, matching the babl encoding variants RGB, R'G'B' and R~G~B~. - gimp_color_transform_can_gegl_copy() now returns TRUE if both profiles can return a babl space, increasing the amount of fast babl color conversions significantly. - TODO: no solution yet for getting libgimp drawable proxy buffers in the right format with space. plug-ins: - follow the GimpPrecision change. - TODO: everything else unchanged and partly broken or sub-optimal, like setting a new image's color profile too late. app: - add enum GimpTRCType { LINEAR, NON_LINEAR, PERCEPTUAL } as replacement for all "linear" booleans. - change gimp-babl functions to take babl spaces and GimpTRCType parameters and support all sorts of new perceptual ~ formats. - a lot of places changed in the early days of goat invasion didn't take advantage of gimp-babl utility functions and constructed formats manually. They all needed revisiting and many now use much simpler code calling gimp-babl API. - change gimp_babl_format_get_color_profile() to really extract a newly allocated color profile from the format, and add gimp_babl_get_builtin_color_profile() which does the same as gimp_babl_format_get_color_profile() did before. Visited all callers to decide whether they are looking for the format's actual profile, or for one of the builtin profiles, simplifying code that only needs builtin profiles. - drawables have a new get_space_api(), get_linear() is now get_trc(). - images now have a "layer space" and an API to get it, gimp_image_get_layer_format() returns formats in that space. - an image's layer space is created from the image's color profile, change gimpimage-color-profile to deal with that correctly - change many babl_format() calls to babl_format_with_space() and take the space from passed formats or drawables - add function gimp_layer_fix_format_space() which replaces the layer's buffer with one that has the image's layer format, but doesn't change pixel values - use gimp_layer_fix_format_space() to make sure layers loaded from XCF and created by plug-ins have the right space when added to the image, because it's impossible to always assign the right space upon layer creation - "assign color profile" and "discard color profile" now require use of gimp_layer_fix_format_space() too because the profile is now embedded in all formats via the space. Add gimp_image_assign_color_profile() which does all that and call it instead of a simple gimp_image_set_color_profile(), also from the PDB set-color-profile functions, which are essentially "assign" and "discard" calls. - generally, make sure a new image's color profile is set before adding layers to it, gimp_image_set_color_profile() is more than before considered know-what-you-are-doing API. - take special precaution in all places that call gimp_drawable_convert_type(), we now must pass a new_profile from all callers that convert layers within the same image (such as image_convert_type, image_convert_precision), because the layer's new space can't be determined from the image's layer format during the call. - change all "linear" properties to "trc", in all config objects like for levels and curves, in the histogram, in the widgets. This results in some GUI that now has three choices instead of two. TODO: we might want to reduce that back to two later. - keep "linear" boolean properties around as compat if needed for file pasring, but always convert the parsed parsed boolean to GimpTRCType. - TODO: the image's "enable color management" switch is currently broken, will fix that in another commit.
2018-07-21 14:23:01 +02:00
case GIMP_PRECISION_DOUBLE_NON_LINEAR:
case GIMP_PRECISION_DOUBLE_PERCEPTUAL:
bitspersample = 64;
sampleformat = SAMPLEFORMAT_IEEEFP;
break;
}
*saved_bpp = bitspersample;
cols = gegl_buffer_get_width (buffer);
rows = gegl_buffer_get_height (buffer);
switch (drawable_type)
{
case GIMP_RGB_IMAGE:
predictor = 2;
samplesperpixel = 3;
photometric = PHOTOMETRIC_RGB;
alpha = FALSE;
if (out_linear)
{
format = babl_format_new (babl_model ("RGB"),
type,
babl_component ("R"),
babl_component ("G"),
babl_component ("B"),
NULL);
}
else
{
format = babl_format_new (babl_model ("R'G'B'"),
type,
babl_component ("R'"),
babl_component ("G'"),
babl_component ("B'"),
NULL);
}
break;
case GIMP_GRAY_IMAGE:
samplesperpixel = 1;
photometric = PHOTOMETRIC_MINISBLACK;
alpha = FALSE;
if (out_linear)
{
format = babl_format_new (babl_model ("Y"),
type,
babl_component ("Y"),
NULL);
}
else
{
format = babl_format_new (babl_model ("Y'"),
type,
babl_component ("Y'"),
NULL);
}
break;
case GIMP_RGBA_IMAGE:
predictor = 2;
samplesperpixel = 4;
photometric = PHOTOMETRIC_RGB;
alpha = TRUE;
if (config_save_transp_pixels)
{
if (out_linear)
{
format = babl_format_new (babl_model ("RGBA"),
type,
babl_component ("R"),
babl_component ("G"),
babl_component ("B"),
babl_component ("A"),
NULL);
}
else
{
format = babl_format_new (babl_model ("R'G'B'A"),
type,
babl_component ("R'"),
babl_component ("G'"),
babl_component ("B'"),
babl_component ("A"),
NULL);
}
}
else
{
if (out_linear)
{
format = babl_format_new (babl_model ("RaGaBaA"),
type,
babl_component ("Ra"),
babl_component ("Ga"),
babl_component ("Ba"),
babl_component ("A"),
NULL);
}
else
{
format = babl_format_new (babl_model ("R'aG'aB'aA"),
type,
babl_component ("R'a"),
babl_component ("G'a"),
babl_component ("B'a"),
babl_component ("A"),
NULL);
}
}
break;
case GIMP_GRAYA_IMAGE:
samplesperpixel = 2;
photometric = PHOTOMETRIC_MINISBLACK;
alpha = TRUE;
if (config_save_transp_pixels)
{
if (out_linear)
{
format = babl_format_new (babl_model ("YA"),
type,
babl_component ("Y"),
babl_component ("A"),
NULL);
}
else
{
format = babl_format_new (babl_model ("Y'A"),
type,
babl_component ("Y'"),
babl_component ("A"),
NULL);
}
}
else
{
if (out_linear)
{
format = babl_format_new (babl_model ("YaA"),
type,
babl_component ("Ya"),
babl_component ("A"),
NULL);
}
else
{
format = babl_format_new (babl_model ("Y'aA"),
type,
babl_component ("Y'a"),
babl_component ("A"),
NULL);
}
}
break;
case GIMP_INDEXED_IMAGE:
case GIMP_INDEXEDA_IMAGE:
palette = gimp_image_get_palette (image);
format = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
cmap = gimp_palette_get_colormap (palette, babl_format_with_space ("R'G'B' u8", format), &num_colors, NULL);
if (drawable_type == GIMP_INDEXED_IMAGE && (num_colors == 2 || num_colors == 1))
{
is_bw = (memcmp (cmap, bw_map, 3 * num_colors) == 0);
photometric = PHOTOMETRIC_MINISWHITE;
if (!is_bw)
{
is_bw = (memcmp (cmap, wb_map, 3 * num_colors) == 0);
if (is_bw)
invert = FALSE;
}
}
if (is_bw)
{
bitspersample = 1;
}
else
{
bitspersample = 8;
photometric = PHOTOMETRIC_PALETTE;
for (i = 0; i < num_colors; i++)
{
red[i] = cmap[i * 3 + 0] * 65535 / 255;
grn[i] = cmap[i * 3 + 1] * 65535 / 255;
blu[i] = cmap[i * 3 + 2] * 65535 / 255;
}
}
samplesperpixel = (drawable_type == GIMP_INDEXEDA_IMAGE) ? 2 : 1;
bytesperrow = cols;
alpha = (drawable_type == GIMP_INDEXEDA_IMAGE);
g_free (cmap);
break;
default:
goto out;
}
if (config_cmyk)
{
if (alpha)
format = babl_format_new (babl_model ("CMYKA"),
type,
babl_component ("Cyan"),
babl_component ("Magenta"),
babl_component ("Yellow"),
babl_component ("Key"),
babl_component ("A"),
NULL);
else
format = babl_format_new (babl_model ("CMYK"),
type,
babl_component ("Cyan"),
babl_component ("Magenta"),
babl_component ("Yellow"),
babl_component ("Key"),
NULL);
format =
babl_format_with_space (babl_format_get_encoding (format),
space);
}
else
{
format = babl_format_with_space (babl_format_get_encoding (format),
space ? space : gegl_buffer_get_format (buffer));
}
2013-05-14 18:33:33 +05:30
bytesperrow = cols * babl_format_get_bytes_per_pixel (format);
if (compression == COMPRESSION_CCITTFAX3 ||
compression == COMPRESSION_CCITTFAX4)
{
if (bitspersample != 1 || samplesperpixel != 1)
{
const gchar *msg = _("Only monochrome pictures can be compressed "
"with \"CCITT Group 4\" or \"CCITT Group 3\".");
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, msg);
goto out;
}
}
if (compression == COMPRESSION_JPEG)
{
if (gimp_image_get_base_type (image) == GIMP_INDEXED)
{
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Indexed pictures cannot be compressed "
"with \"JPEG\"."));
goto out;
}
}
#ifdef TIFFTAG_ICCPROFILE
if (config_save_profile || config_cmyk)
{
const guint8 *icc_data = NULL;
gsize icc_length;
GimpColorProfile *profile;
GimpColorProfile *cmyk_profile = NULL;
profile = gimp_image_get_effective_color_profile (orig_image);
if (config_cmyk)
cmyk_profile = gimp_image_get_simulation_profile (image);
/* If a non-CMYK profile was assigned as the simulation profile,
* set it back to NULL and save the RGB profile instead
*/
if (cmyk_profile && ! gimp_color_profile_is_cmyk (cmyk_profile))
g_clear_object (&cmyk_profile);
/* Write the RGB or CMYK color profile to the TIFF file */
if (profile && ! config_cmyk)
icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
else if (cmyk_profile)
icc_data = gimp_color_profile_get_icc_profile (cmyk_profile, &icc_length);
if (icc_data)
TIFFSetField (tif, TIFFTAG_ICCPROFILE, icc_length, icc_data);
g_object_unref (profile);
g_clear_object (&cmyk_profile);
}
#endif
/* Set CMYK Properties */
if (config_cmyk)
{
photometric = PHOTOMETRIC_SEPARATED;
/* If there's transparency, save as CMYKA format */
samplesperpixel = alpha ? 5 : 4;
TIFFSetField (tif, TIFFTAG_INKSET, INKSET_CMYK);
TIFFSetField (tif, TIFFTAG_NUMBEROFINKS, 4);
}
/* Set TIFF parameters. */
if (config_save_comment && config_comment && *config_comment)
{
const gchar *c = config_comment;
gint len;
/* The TIFF spec explicitly says ASCII for the image description. */
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."));
g_free (config_comment);
config_comment = NULL;
break;
}
}
if (config_comment)
TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, config_comment);
}
if (num_pages > 1)
{
TIFFSetField (tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE);
TIFFSetField (tif, TIFFTAG_PAGENUMBER, page, num_pages);
}
TIFFSetField (tif, TIFFTAG_PAGENAME, layer_name);
TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, cols);
TIFFSetField (tif, TIFFTAG_IMAGELENGTH, rows);
TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bitspersample);
TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, sampleformat);
TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField (tif, TIFFTAG_COMPRESSION, compression);
if ((compression == COMPRESSION_LZW ||
compression == COMPRESSION_ADOBE_DEFLATE) &&
(predictor != 0))
{
TIFFSetField (tif, TIFFTAG_PREDICTOR, predictor);
}
if (alpha)
{
if (config_save_transp_pixels ||
/* Associated alpha, hence premultiplied components is
* meaningless for palette images with transparency in TIFF
* format, since alpha is set per pixel, not per color (so a
* given color could be set to different alpha on different
* pixels, hence it cannot be premultiplied).
*/
drawable_type == GIMP_INDEXEDA_IMAGE)
extra_samples [0] = EXTRASAMPLE_UNASSALPHA;
else
extra_samples [0] = EXTRASAMPLE_ASSOCALPHA;
TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, extra_samples);
}
TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, photometric);
TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
/* TIFFSetField( tif, TIFFTAG_STRIPBYTECOUNTS, rows / rowsperstrip ); */
TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
/* resolution fields */
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_drawable_get_offsets (GIMP_DRAWABLE (layer), &offset_x, &offset_y);
offset_x -= origin_x;
offset_y -= origin_y;
if (offset_x || offset_y)
{
TIFFSetField (tif, TIFFTAG_XPOSITION, offset_x / xresolution);
TIFFSetField (tif, TIFFTAG_YPOSITION, offset_y / yresolution);
}
if (! is_bw && ! config_cmyk &&
(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);
/* Now write the TIFF data. */
for (y = 0; y < rows; y = yend)
{
yend = y + tile_height;
yend = MIN (yend, rows);
gegl_buffer_get (buffer,
GEGL_RECTANGLE (0, y, cols, yend - y), 1.0,
format, src,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
for (row = y; row < yend; row++)
{
guchar *t = src + bytesperrow * (row - y);
switch (drawable_type)
{
case GIMP_INDEXED_IMAGE:
case GIMP_INDEXEDA_IMAGE:
if (is_bw)
{
byte2bit (t, bytesperrow, data, invert);
success = (TIFFWriteScanline (tif, data, row, 0) >= 0);
}
else
{
success = (TIFFWriteScanline (tif, t, row, 0) >= 0);
}
break;
case GIMP_GRAY_IMAGE:
case GIMP_GRAYA_IMAGE:
case GIMP_RGB_IMAGE:
case GIMP_RGBA_IMAGE:
success = (TIFFWriteScanline (tif, t, row, 0) >= 0);
break;
default:
success = FALSE;
break;
}
if (!success)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Failed a scanline write on row %d"), row);
goto out;
}
}
if ((row % 32) == 0)
gimp_progress_update (progress_base + progress_fraction
* (gdouble) row / (gdouble) rows);
}
/* Save GeoTIFF tags to file, if available */
if (config_save_geotiff_tags)
{
GimpParasite *parasite = NULL;
gchar *parasite_data;
guint32 parasite_size;
parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_ModelPixelScale");
if (parasite)
{
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
TIFFSetField (tif,
GEOTIFF_MODELPIXELSCALE,
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
parasite_data);
gimp_parasite_free (parasite);
}
parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_ModelTiePoint");
if (parasite)
{
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
TIFFSetField (tif,
GEOTIFF_MODELTIEPOINT,
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
parasite_data);
gimp_parasite_free (parasite);
}
parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_ModelTransformation");
if (parasite)
{
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
TIFFSetField (tif,
GEOTIFF_MODELTRANSFORMATION,
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
parasite_data);
gimp_parasite_free (parasite);
}
parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_KeyDirectory");
if (parasite)
{
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
TIFFSetField (tif,
GEOTIFF_KEYDIRECTORY,
(parasite_size / TIFFDataWidth (TIFF_SHORT)),
parasite_data);
gimp_parasite_free (parasite);
}
parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_DoubleParams");
if (parasite)
{
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
TIFFSetField (tif,
GEOTIFF_DOUBLEPARAMS,
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
parasite_data);
gimp_parasite_free (parasite);
}
parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_Asciiparams");
if (parasite)
{
parasite_data = (gchar *) gimp_parasite_get_data (parasite, &parasite_size);
parasite_data = g_strndup (parasite_data, parasite_size);
TIFFSetField (tif,
GEOTIFF_ASCIIPARAMS,
parasite_data);
gimp_parasite_free (parasite);
g_free (parasite_data);
}
}
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;
}
/* FIXME Most of the stuff in save_metadata except the
* thumbnail saving should probably be moved to
* gimpmetadata.c and gimpmetadata-save.c.
*/
static void
save_metadata (GFile *file,
GObject *config,
GimpImage *image,
GimpMetadata *metadata,
gint saved_bpp,
gboolean cmyk)
{
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", /* MinSampleValue */
"Exif.Image.0x0119", /* MaxSampleValue */
"Exif.Image.0x011d", /* PageName */
"Exif.Image.Compression",
"Exif.Image.FillOrder",
"Exif.Image.InterColorProfile",
"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_try_clear_tag (GEXIV2_METADATA (metadata),
exif_tags_to_remove[k], NULL);
}
/* 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++)
{
/* Keeping Exif.Image2, 3 can cause exiv2 to save faulty extra TIFF pages
* that are empty except for the Exif metadata. See issue #7195. */
if (g_str_has_prefix (*tag, "Exif.Image")
&& (*tag)[strlen ("Exif.Image")] >= '0'
&& (*tag)[strlen ("Exif.Image")] <= '9')
gexiv2_metadata_try_clear_tag (GEXIV2_METADATA (metadata), *tag, NULL);
if (g_str_has_prefix (*tag, "Exif.SubImage")
&& (*tag)[strlen ("Exif.SubImage")] >= '0'
&& (*tag)[strlen ("Exif.SubImage")] <= '9')
gexiv2_metadata_try_clear_tag (GEXIV2_METADATA (metadata), *tag, NULL);
if (g_str_has_prefix (*tag, "Exif.Thumbnail"))
gexiv2_metadata_try_clear_tag (GEXIV2_METADATA (metadata), *tag, NULL);
}
gimp_metadata_set_bits_per_sample (metadata, saved_bpp);
if (cmyk)
gimp_metadata_set_colorspace (metadata, GIMP_METADATA_COLORSPACE_UNCALIBRATED);
gimp_procedure_config_save_metadata (GIMP_PROCEDURE_CONFIG (config),
image, file);
}
gboolean
export_image (GFile *file,
GimpImage *image,
GimpImage *orig_image, /* the export function might
* have created a duplicate */
GObject *config,
GimpMetadata *metadata,
GError **error)
{
TIFF *tif = NULL;
const Babl *space = NULL;
gboolean status = FALSE;
gboolean out_linear = FALSE;
gint32 num_layers;
gint32 current_layer = 0;
GList *layers;
GList *iter;
gint origin_x = 0;
gint origin_y = 0;
gint saved_bpp;
gboolean bigtiff;
gboolean config_save_profile;
gboolean config_save_thumbnail;
gboolean config_cmyk;
g_object_get (config,
"bigtiff", &bigtiff,
"include-color-profile", &config_save_profile,
"include-thumbnail", &config_save_thumbnail,
"cmyk", &config_cmyk,
NULL);
layers = gimp_image_list_layers (image);
layers = g_list_reverse (layers);
num_layers = g_list_length (layers);
gimp_progress_init_printf (_("Exporting '%s'"),
gimp_file_get_utf8_name (file));
2019-07-15 08:25:35 -04:00
/* Open file and write some global data */
Issue #7956: Add full BigTiff open/export support to GIMP. Recent libtiff supports loading BigTiff automatically so we didn't have anything to do there (as long as a recent libtiff was used). For creating a BigTIFF though, we simply needed to add a "8" flag to TIFFOpen/TIFFClientOpen when creating a new image (i.e. using "w8" mode) as explained here in the "Implementation Strategy" section: http://www.simplesystems.org/libtiff/BigTIFFProposal.html What this commit does: - Explicitly bump our libtiff requirement to version 4.0.0 or higher (which is where BigTiff support appeared). libtiff 4.0.0 was apparently released on 2011-12-22 and is available on all current distributions, so it's probably not a problem. - Switch to detect libtiff with a pkg-config test (added in libtiff commit faf5f3eb before 4.0.0 release, so it's fine) instead of function checks. (Note: meson was already detecting for libtiff-4 with pkg-config, which was obviously wrong since it should have mimicked autotools, but well… then changes were minimal on meson) - Add a new "bigtiff" boolean argument to the "file-tiff-save" PDB procedure, FALSE by default. I set this as the first argument as I figure that choosing the format you want is quite a major choice. Unless I misunderstood something, since BigTIFF is really designed to be an evolution of TIFF with a "minimum change strategy", i.e. mostly using 64-bit instead of 32-bit offsets, everything which is possible in TIFF will be in BigTIFF (and oppositely as well, except of course having huge files) so there is no need to have 2 separate procedures. - Adding this new argument to the GUI dialog as a checkbox. - Tweak the load and export procedures' documentation strings to make clear we support both TIFF and BigTIFF. Note: interestingly there doesn't seem to be a separate mimetype for BigTIFF so nothing to update on this side. - Tweak the procedure labels too to mention BigTIFF. Since BigTIFF is still a different format (though very closely resembling) from TIFF, unlike some others which are just extensions embedded in a TIFF file (like GeoTIFF we recently added), I figure it deserves to be explicitly cited.
2022-03-11 10:50:44 +01:00
tif = tiff_open (file, (bigtiff ? "w8" : "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;
}
if (config_save_profile || config_cmyk)
{
GimpColorProfile *profile;
GError *error = NULL;
if (config_cmyk)
{
profile = gimp_image_get_simulation_profile (image);
if (profile && ! gimp_color_profile_is_cmyk (profile))
g_clear_object (&profile);
}
else
{
profile = gimp_image_get_effective_color_profile (orig_image);
}
/* Curve of the exported data depends on the saved profile, i.e.
* any explicitly-set profile in priority, or the default one for
* the storage format as fallback.
*/
out_linear = (gimp_color_profile_is_linear (profile));
if (profile)
space = gimp_color_profile_get_space (profile,
config_cmyk ?
gimp_image_get_simulation_intent (image) :
GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
&error);
if (error)
{
g_printerr ("%s: error getting the profile space: %s",
G_STRFUNC, error->message);
g_error_free (error);
space = NULL;
}
g_object_unref (profile);
}
/* calculate the top-left coordinates */
for (iter = layers; iter; iter = g_list_next (iter))
{
GimpDrawable *drawable = iter->data;
gint offset_x, offset_y;
gimp_drawable_get_offsets (drawable, &offset_x, &offset_y);
origin_x = MIN (origin_x, offset_x);
origin_y = MIN (origin_y, offset_y);
}
/* write last layer as first page. */
if (! save_layer (tif, config, space, image,
g_list_nth_data (layers, current_layer),
current_layer, num_layers,
orig_image,
origin_x, origin_y,
&saved_bpp, out_linear, error))
{
goto out;
}
current_layer++;
2019-07-15 08:25:35 -04:00
/* close file so we can safely 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, config, image, metadata, saved_bpp, config_cmyk);
/* write the remaining layers */
if (num_layers > 1)
{
tif = tiff_open (file, "a", 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;
}
for (; current_layer < num_layers; current_layer++)
{
gint tmp_saved_bpp;
if (! save_layer (tif, config, space, image,
g_list_nth_data (layers, current_layer),
current_layer, num_layers, orig_image,
origin_x, origin_y,
&tmp_saved_bpp, out_linear, 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);
}
}
status = TRUE;
out:
/* close the file for good */
if (tif)
{
TIFFFlushData (tif);
TIFFClose (tif);
}
gimp_progress_update (1.0);
g_list_free (layers);
return status;
}
gboolean
save_dialog (GimpImage *image,
GimpProcedure *procedure,
GObject *config,
gboolean has_alpha,
gboolean is_monochrome,
gboolean is_indexed,
gboolean is_multi_layer,
gboolean classic_tiff_failed)
{
GtkWidget *dialog;
GtkWidget *profile_label;
gchar **parasites;
GimpCompression compression;
gboolean run;
gboolean has_geotiff = FALSE;
gint i;
GimpColorProfile *cmyk_profile = NULL;
GParamSpec *cspec;
GimpChoice *choice;
cspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "compression");
choice = gimp_param_spec_choice_get_choice (cspec);
gimp_choice_set_sensitive (choice, "ccittfax3", is_monochrome);
gimp_choice_set_sensitive (choice, "ccittfax4", is_monochrome);
gimp_choice_set_sensitive (choice, "jpeg", ! is_indexed);
parasites = gimp_image_get_parasite_list (image);
for (i = 0; i < g_strv_length (parasites); i++)
{
if (g_str_has_prefix (parasites[i], "Gimp_GeoTIFF_"))
{
has_geotiff = TRUE;
break;
}
}
g_strfreev (parasites);
dialog = gimp_export_procedure_dialog_new (GIMP_EXPORT_PROCEDURE (procedure),
GIMP_PROCEDURE_CONFIG (config),
image);
if (classic_tiff_failed)
{
GtkWidget *label;
gchar *text;
/* Warning sign emoticone. */
text = g_strdup_printf ("\xe2\x9a\xa0 %s",
_("Warning: maximum TIFF file size exceeded. "
"Retry as BigTIFF or with a different compression algorithm, "
"or cancel."));
label = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
"big-tif-warning", text,
FALSE, FALSE);
g_free (text);
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD);
gtk_label_set_max_width_chars (GTK_LABEL (label), 60);
}
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
"layers-frame", "save-layers", FALSE,
"crop-layers");
/* TODO: if single-layer TIFF, set the toggle insensitive and show it
* as unchecked though I don't actually change the config value to
* keep storing previously chosen value.
* This used to be so before. We probably need to add some logics in
* the GimpProcedureDialog generation for such case.
*/
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
"layers-frame", is_multi_layer,
NULL, NULL, FALSE);
/* TODO: same for "save-transparent-pixels", we probably want to show
* it unchecked even though it doesn't matter for processing.
*/
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
"save-transparent-pixels",
has_alpha && ! is_indexed,
NULL, NULL, FALSE);
/* Profile label. */
profile_label = gimp_procedure_dialog_get_label (GIMP_PROCEDURE_DIALOG (dialog),
"profile-label", _("No soft-proofing profile"),
FALSE, FALSE);
gtk_label_set_xalign (GTK_LABEL (profile_label), 0.0);
gtk_label_set_ellipsize (GTK_LABEL (profile_label), PANGO_ELLIPSIZE_END);
gimp_label_set_attributes (GTK_LABEL (profile_label),
PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
-1);
gimp_help_set_help_data (profile_label,
_("Name of the color profile used for CMYK export."), NULL);
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
"cmyk-frame", "cmyk", FALSE,
"profile-label");
cmyk_profile = gimp_image_get_simulation_profile (image);
if (cmyk_profile)
{
gchar *label_text;
if (gimp_color_profile_is_cmyk (cmyk_profile))
{
label_text = g_strdup_printf (_("Profile: %s"),
gimp_color_profile_get_label (cmyk_profile));
}
else
{
label_text = g_strdup_printf (_("The assigned soft-proofing profile is not a CMYK profile.\n"
"This profile will not be included in the exported image."));
}
gtk_label_set_text (GTK_LABEL (profile_label), label_text);
gimp_label_set_attributes (GTK_LABEL (profile_label),
PANGO_ATTR_STYLE, PANGO_STYLE_NORMAL,
-1);
g_free (label_text);
g_object_unref (cmyk_profile);
}
gimp_export_procedure_dialog_add_metadata (GIMP_EXPORT_PROCEDURE_DIALOG (dialog), "save-geotiff");
gimp_procedure_dialog_set_sensitive (GIMP_PROCEDURE_DIALOG (dialog),
"save-geotiff",
has_geotiff, NULL, NULL, FALSE);
if (classic_tiff_failed)
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
"big-tif-warning",
"compression",
"bigtiff",
"layers-frame",
"save-transparent-pixels",
"cmyk-frame",
NULL);
else
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
"compression",
"bigtiff",
"layers-frame",
"save-transparent-pixels",
"cmyk-frame",
NULL);
compression = gimp_procedure_config_get_choice_id (GIMP_PROCEDURE_CONFIG (config), "compression");
if (! is_monochrome)
{
if (compression == GIMP_COMPRESSION_CCITTFAX3 ||
compression == GIMP_COMPRESSION_CCITTFAX4)
g_object_set (config, "compression", "none", NULL);
}
if (is_indexed && compression == GIMP_COMPRESSION_JPEG)
g_object_set (config, "compression", "none", NULL);
gtk_widget_show (dialog);
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
gtk_widget_destroy (dialog);
return run;
}
/* Convert n bytes of 0/1 to a line of bits */
static void
byte2bit (const guchar *byteline,
gint width,
guchar *bitline,
gboolean invert)
{
guchar bitval;
guchar rest[8];
while (width >= 8)
{
bitval = 0;
if (*(byteline++)) bitval |= 0x80;
if (*(byteline++)) bitval |= 0x40;
if (*(byteline++)) bitval |= 0x20;
if (*(byteline++)) bitval |= 0x10;
if (*(byteline++)) bitval |= 0x08;
if (*(byteline++)) bitval |= 0x04;
if (*(byteline++)) bitval |= 0x02;
if (*(byteline++)) bitval |= 0x01;
*(bitline++) = invert ? ~bitval : bitval;
width -= 8;
}
if (width > 0)
{
memset (rest, 0, 8);
memcpy (rest, byteline, width);
bitval = 0;
byteline = rest;
if (*(byteline++)) bitval |= 0x80;
if (*(byteline++)) bitval |= 0x40;
if (*(byteline++)) bitval |= 0x20;
if (*(byteline++)) bitval |= 0x10;
if (*(byteline++)) bitval |= 0x08;
if (*(byteline++)) bitval |= 0x04;
if (*(byteline++)) bitval |= 0x02;
*bitline = invert ? ~bitval & (0xff << (8 - width)) : bitval;
}
}