1430 lines
47 KiB
C
1430 lines
47 KiB
C
/* tiff exporting for PIKA
|
|
* -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 <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#include "file-tiff.h"
|
|
#include "file-tiff-io.h"
|
|
#include "file-tiff-save.h"
|
|
|
|
#include "libpika/stdplugins-intl.h"
|
|
|
|
|
|
#define PLUG_IN_ROLE "pika-file-tiff-save"
|
|
|
|
|
|
static gboolean save_paths (TIFF *tif,
|
|
PikaImage *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,
|
|
PikaImage *image,
|
|
gdouble width,
|
|
gdouble height,
|
|
gint offset_x,
|
|
gint offset_y)
|
|
{
|
|
gint id = 2000; /* Photoshop paths have IDs >= 2000 */
|
|
GList *vectors;
|
|
GList *iter;
|
|
gint v;
|
|
gint num_strokes, *strokes, s;
|
|
GString *ps_tag;
|
|
|
|
vectors = pika_image_list_vectors (image);
|
|
|
|
if (! vectors)
|
|
return FALSE;
|
|
|
|
ps_tag = g_string_new ("");
|
|
|
|
/* Only up to 1000 paths supported */
|
|
for (iter = vectors, 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 = pika_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 = pika_vectors_get_strokes (iter->data, &num_strokes);
|
|
|
|
for (s = 0; s < num_strokes; s++)
|
|
{
|
|
PikaVectorsStrokeType type;
|
|
gdouble *points;
|
|
gint num_points;
|
|
gboolean closed;
|
|
gint p = 0;
|
|
|
|
type = pika_vectors_stroke_get_points (iter->data, strokes[s],
|
|
&num_points, &points, &closed);
|
|
|
|
if (type != PIKA_VECTORS_STROKE_TYPE_BEZIER ||
|
|
num_points > 65535 ||
|
|
num_points % 6)
|
|
{
|
|
g_printerr ("tiff-save: unsupported stroke type: "
|
|
"%d (%d 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 (vectors);
|
|
|
|
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,
|
|
PikaImage *image,
|
|
PikaLayer *layer,
|
|
gint32 page,
|
|
gint32 num_pages,
|
|
PikaImage *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;
|
|
guchar *cmap;
|
|
gint num_colors;
|
|
gint success;
|
|
PikaImageType 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,
|
|
"pika-comment", &config_comment,
|
|
"save-comment", &config_save_comment,
|
|
"save-transparent-pixels", &config_save_transp_pixels,
|
|
"save-geotiff", &config_save_geotiff_tags,
|
|
"save-color-profile", &config_save_profile,
|
|
"cmyk", &config_cmyk,
|
|
NULL);
|
|
|
|
config_compression = pika_procedure_config_get_choice_id (PIKA_PROCEDURE_CONFIG (config), "compression");
|
|
compression = pika_compression_to_tiff_compression (config_compression);
|
|
|
|
layer_name = pika_item_get_name (PIKA_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 = pika_tile_height ();
|
|
rowsperstrip = tile_height;
|
|
|
|
drawable_type = pika_drawable_type (PIKA_DRAWABLE (layer));
|
|
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));
|
|
|
|
format = gegl_buffer_get_format (buffer);
|
|
type = babl_format_get_type (format, 0);
|
|
|
|
switch (pika_image_get_precision (image))
|
|
{
|
|
case PIKA_PRECISION_U8_LINEAR:
|
|
case PIKA_PRECISION_U8_NON_LINEAR:
|
|
case PIKA_PRECISION_U8_PERCEPTUAL:
|
|
/* Promote to 16-bit if storage and export TRC don't match. */
|
|
if ((pika_image_get_precision (image) == PIKA_PRECISION_U8_LINEAR && out_linear) ||
|
|
(pika_image_get_precision (image) != PIKA_PRECISION_U8_LINEAR && ! out_linear))
|
|
{
|
|
bitspersample = 8;
|
|
sampleformat = SAMPLEFORMAT_UINT;
|
|
}
|
|
else
|
|
{
|
|
bitspersample = 16;
|
|
sampleformat = SAMPLEFORMAT_UINT;
|
|
type = babl_type ("u16");
|
|
}
|
|
break;
|
|
|
|
case PIKA_PRECISION_U16_LINEAR:
|
|
case PIKA_PRECISION_U16_NON_LINEAR:
|
|
case PIKA_PRECISION_U16_PERCEPTUAL:
|
|
bitspersample = 16;
|
|
sampleformat = SAMPLEFORMAT_UINT;
|
|
break;
|
|
|
|
case PIKA_PRECISION_U32_LINEAR:
|
|
case PIKA_PRECISION_U32_NON_LINEAR:
|
|
case PIKA_PRECISION_U32_PERCEPTUAL:
|
|
bitspersample = 32;
|
|
sampleformat = SAMPLEFORMAT_UINT;
|
|
break;
|
|
|
|
case PIKA_PRECISION_HALF_LINEAR:
|
|
case PIKA_PRECISION_HALF_NON_LINEAR:
|
|
case PIKA_PRECISION_HALF_PERCEPTUAL:
|
|
bitspersample = 16;
|
|
sampleformat = SAMPLEFORMAT_IEEEFP;
|
|
break;
|
|
|
|
default:
|
|
case PIKA_PRECISION_FLOAT_LINEAR:
|
|
case PIKA_PRECISION_FLOAT_NON_LINEAR:
|
|
case PIKA_PRECISION_FLOAT_PERCEPTUAL:
|
|
bitspersample = 32;
|
|
sampleformat = SAMPLEFORMAT_IEEEFP;
|
|
break;
|
|
|
|
case PIKA_PRECISION_DOUBLE_LINEAR:
|
|
case PIKA_PRECISION_DOUBLE_NON_LINEAR:
|
|
case PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_INDEXED_IMAGE:
|
|
case PIKA_INDEXEDA_IMAGE:
|
|
cmap = pika_image_get_colormap (image, NULL, &num_colors);
|
|
|
|
if (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 == PIKA_INDEXEDA_IMAGE) ? 2 : 1;
|
|
bytesperrow = cols;
|
|
alpha = (drawable_type == PIKA_INDEXEDA_IMAGE);
|
|
format = pika_drawable_get_format (PIKA_DRAWABLE (layer));
|
|
|
|
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));
|
|
}
|
|
|
|
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 (pika_image_get_base_type (image) == PIKA_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)
|
|
{
|
|
const guint8 *icc_data = NULL;
|
|
gsize icc_length;
|
|
PikaColorProfile *profile;
|
|
PikaColorProfile *cmyk_profile = NULL;
|
|
|
|
profile = pika_image_get_effective_color_profile (orig_image);
|
|
if (config_cmyk)
|
|
cmyk_profile = pika_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 && ! pika_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 = pika_color_profile_get_icc_profile (profile, &icc_length);
|
|
else if (cmyk_profile)
|
|
icc_data = pika_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 == PIKA_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 */
|
|
pika_image_get_resolution (orig_image, &xresolution, &yresolution);
|
|
|
|
if (pika_unit_is_metric (pika_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);
|
|
}
|
|
|
|
pika_drawable_get_offsets (PIKA_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 == PIKA_INDEXED_IMAGE || drawable_type == PIKA_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 PIKA_INDEXED_IMAGE:
|
|
case PIKA_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 PIKA_GRAY_IMAGE:
|
|
case PIKA_GRAYA_IMAGE:
|
|
case PIKA_RGB_IMAGE:
|
|
case PIKA_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)
|
|
pika_progress_update (progress_base + progress_fraction
|
|
* (gdouble) row / (gdouble) rows);
|
|
}
|
|
|
|
/* Save GeoTIFF tags to file, if available */
|
|
if (config_save_geotiff_tags)
|
|
{
|
|
PikaParasite *parasite = NULL;
|
|
gchar *parasite_data;
|
|
guint32 parasite_size;
|
|
|
|
parasite = pika_image_get_parasite (image,"Pika_GeoTIFF_ModelPixelScale");
|
|
if (parasite)
|
|
{
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
TIFFSetField (tif,
|
|
GEOTIFF_MODELPIXELSCALE,
|
|
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
|
|
parasite_data);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
|
|
parasite = pika_image_get_parasite (image,"Pika_GeoTIFF_ModelTiePoint");
|
|
if (parasite)
|
|
{
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
TIFFSetField (tif,
|
|
GEOTIFF_MODELTIEPOINT,
|
|
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
|
|
parasite_data);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
|
|
parasite = pika_image_get_parasite (image,"Pika_GeoTIFF_ModelTransformation");
|
|
if (parasite)
|
|
{
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
TIFFSetField (tif,
|
|
GEOTIFF_MODELTRANSFORMATION,
|
|
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
|
|
parasite_data);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
|
|
parasite = pika_image_get_parasite (image,"Pika_GeoTIFF_KeyDirectory");
|
|
if (parasite)
|
|
{
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
TIFFSetField (tif,
|
|
GEOTIFF_KEYDIRECTORY,
|
|
(parasite_size / TIFFDataWidth (TIFF_SHORT)),
|
|
parasite_data);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
|
|
parasite = pika_image_get_parasite (image,"Pika_GeoTIFF_DoubleParams");
|
|
if (parasite)
|
|
{
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
TIFFSetField (tif,
|
|
GEOTIFF_DOUBLEPARAMS,
|
|
(parasite_size / TIFFDataWidth (TIFF_DOUBLE)),
|
|
parasite_data);
|
|
pika_parasite_free (parasite);
|
|
}
|
|
|
|
parasite = pika_image_get_parasite (image,"Pika_GeoTIFF_Asciiparams");
|
|
if (parasite)
|
|
{
|
|
parasite_data = (gchar *) pika_parasite_get_data (parasite, ¶site_size);
|
|
parasite_data = g_strndup (parasite_data, parasite_size);
|
|
TIFFSetField (tif,
|
|
GEOTIFF_ASCIIPARAMS,
|
|
parasite_data);
|
|
pika_parasite_free (parasite);
|
|
g_free (parasite_data);
|
|
}
|
|
}
|
|
|
|
TIFFWriteDirectory (tif);
|
|
|
|
pika_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
|
|
* pikametadata.c and pikametadata-save.c.
|
|
*/
|
|
static void
|
|
save_metadata (GFile *file,
|
|
GObject *config,
|
|
PikaImage *image,
|
|
PikaMetadata *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);
|
|
}
|
|
|
|
pika_metadata_set_bits_per_sample (metadata, saved_bpp);
|
|
if (cmyk)
|
|
pika_metadata_set_colorspace (metadata, PIKA_METADATA_COLORSPACE_UNCALIBRATED);
|
|
|
|
pika_procedure_config_save_metadata (PIKA_PROCEDURE_CONFIG (config),
|
|
image, file);
|
|
}
|
|
|
|
gboolean
|
|
save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaImage *orig_image, /* the export function might
|
|
* have created a duplicate */
|
|
GObject *config,
|
|
PikaMetadata *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,
|
|
"save-color-profile", &config_save_profile,
|
|
"save-thumbnail", &config_save_thumbnail,
|
|
"cmyk", &config_cmyk,
|
|
NULL);
|
|
|
|
layers = pika_image_list_layers (image);
|
|
layers = g_list_reverse (layers);
|
|
num_layers = g_list_length (layers);
|
|
|
|
pika_progress_init_printf (_("Exporting '%s'"),
|
|
pika_file_get_utf8_name (file));
|
|
|
|
/* Open file and write some global data */
|
|
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"),
|
|
pika_file_get_utf8_name (file), g_strerror (errno));
|
|
goto out;
|
|
}
|
|
|
|
if (config_save_profile)
|
|
{
|
|
PikaColorProfile *profile;
|
|
GError *error = NULL;
|
|
|
|
if (config_cmyk)
|
|
{
|
|
profile = pika_image_get_simulation_profile (image);
|
|
|
|
if (profile && ! pika_color_profile_is_cmyk (profile))
|
|
g_clear_object (&profile);
|
|
}
|
|
else
|
|
{
|
|
profile = pika_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 = (pika_color_profile_is_linear (profile));
|
|
|
|
if (profile)
|
|
space = pika_color_profile_get_space (profile,
|
|
config_cmyk ?
|
|
pika_image_get_simulation_intent (image) :
|
|
PIKA_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))
|
|
{
|
|
PikaDrawable *drawable = iter->data;
|
|
gint offset_x, offset_y;
|
|
|
|
pika_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++;
|
|
|
|
/* 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"),
|
|
pika_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;
|
|
}
|
|
|
|
pika_progress_update ((gdouble) (current_layer + 1) / num_layers);
|
|
}
|
|
}
|
|
|
|
status = TRUE;
|
|
|
|
out:
|
|
/* close the file for good */
|
|
if (tif)
|
|
{
|
|
TIFFFlushData (tif);
|
|
TIFFClose (tif);
|
|
}
|
|
|
|
pika_progress_update (1.0);
|
|
|
|
g_list_free (layers);
|
|
|
|
return status;
|
|
}
|
|
|
|
gboolean
|
|
save_dialog (PikaImage *image,
|
|
PikaProcedure *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;
|
|
PikaCompression compression;
|
|
gboolean run;
|
|
gboolean has_geotiff = FALSE;
|
|
gint i;
|
|
PikaColorProfile *cmyk_profile = NULL;
|
|
GParamSpec *comp_spec;
|
|
PikaParamSpecChoice *cspec;
|
|
|
|
comp_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), "compression");
|
|
cspec = PIKA_PARAM_SPEC_CHOICE (comp_spec);
|
|
pika_choice_set_sensitive (cspec->choice, "ccittfax3", is_monochrome);
|
|
pika_choice_set_sensitive (cspec->choice, "ccittfax4", is_monochrome);
|
|
pika_choice_set_sensitive (cspec->choice, "jpeg", ! is_indexed);
|
|
|
|
parasites = pika_image_get_parasite_list (image);
|
|
for (i = 0; i < g_strv_length (parasites); i++)
|
|
{
|
|
if (g_str_has_prefix (parasites[i], "Pika_GeoTIFF_"))
|
|
{
|
|
has_geotiff = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
g_strfreev (parasites);
|
|
|
|
dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
|
|
PIKA_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 = pika_procedure_dialog_get_label (PIKA_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);
|
|
}
|
|
|
|
pika_procedure_dialog_fill_frame (PIKA_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 PikaProcedureDialog generation for such case.
|
|
*/
|
|
pika_procedure_dialog_set_sensitive (PIKA_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.
|
|
*/
|
|
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"save-transparent-pixels",
|
|
has_alpha && ! is_indexed,
|
|
NULL, NULL, FALSE);
|
|
|
|
/* Profile label. */
|
|
profile_label = pika_procedure_dialog_get_label (PIKA_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);
|
|
pika_label_set_attributes (GTK_LABEL (profile_label),
|
|
PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
|
|
-1);
|
|
pika_help_set_help_data (profile_label,
|
|
_("Name of the color profile used for CMYK export."), NULL);
|
|
pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"cmyk-frame", "cmyk", FALSE,
|
|
"profile-label");
|
|
|
|
cmyk_profile = pika_image_get_simulation_profile (image);
|
|
if (cmyk_profile)
|
|
{
|
|
gchar *label_text;
|
|
|
|
if (pika_color_profile_is_cmyk (cmyk_profile))
|
|
{
|
|
label_text = g_strdup_printf (_("Profile: %s"),
|
|
pika_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);
|
|
pika_label_set_attributes (GTK_LABEL (profile_label),
|
|
PANGO_ATTR_STYLE, PANGO_STYLE_NORMAL,
|
|
-1);
|
|
g_free (label_text);
|
|
|
|
g_object_unref (cmyk_profile);
|
|
}
|
|
|
|
pika_save_procedure_dialog_add_metadata (PIKA_SAVE_PROCEDURE_DIALOG (dialog), "save-geotiff");
|
|
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"save-geotiff",
|
|
has_geotiff, NULL, NULL, FALSE);
|
|
|
|
if (classic_tiff_failed)
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"big-tif-warning",
|
|
"compression",
|
|
"bigtiff",
|
|
"layers-frame",
|
|
"save-transparent-pixels",
|
|
"cmyk-frame",
|
|
NULL);
|
|
else
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"compression",
|
|
"bigtiff",
|
|
"layers-frame",
|
|
"save-transparent-pixels",
|
|
"cmyk-frame",
|
|
NULL);
|
|
|
|
compression = pika_procedure_config_get_choice_id (PIKA_PROCEDURE_CONFIG (config), "compression");
|
|
|
|
if (! is_monochrome)
|
|
{
|
|
if (compression == PIKA_COMPRESSION_CCITTFAX3 ||
|
|
compression == PIKA_COMPRESSION_CCITTFAX4)
|
|
g_object_set (config, "compression", "none", NULL);
|
|
}
|
|
|
|
if (is_indexed && compression == PIKA_COMPRESSION_JPEG)
|
|
g_object_set (config, "compression", "none", NULL);
|
|
|
|
gtk_widget_show (dialog);
|
|
|
|
run = pika_procedure_dialog_run (PIKA_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;
|
|
}
|
|
}
|