diff options
Diffstat (limited to 'plug-ins/file-tiff/file-tiff-save.c')
-rw-r--r-- | plug-ins/file-tiff/file-tiff-save.c | 1387 |
1 files changed, 1387 insertions, 0 deletions
diff --git a/plug-ins/file-tiff/file-tiff-save.c b/plug-ins/file-tiff/file-tiff-save.c new file mode 100644 index 0000000..0def177 --- /dev/null +++ b/plug-ins/file-tiff/file-tiff-save.c @@ -0,0 +1,1387 @@ +/* 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-io.h" +#include "file-tiff-save.h" + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_ROLE "gimp-file-tiff-save" + + +static gboolean save_paths (TIFF *tif, + gint32 image, + gdouble width, + gdouble height, + gint offset_x, + gint offset_y); + +static void comment_entry_callback (GtkWidget *widget, + gchar **comment); + +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, + gint32 image, + gdouble width, + gdouble height, + gint offset_x, + gint offset_y) +{ + gint id = 2000; /* Photoshop paths have IDs >= 2000 */ + gint num_vectors, *vectors, v; + gint num_strokes, *strokes, s; + GString *ps_tag; + + vectors = gimp_image_get_vectors (image, &num_vectors); + + if (num_vectors <= 0) + return FALSE; + + ps_tag = g_string_new (""); + + /* Only up to 1000 paths supported */ + for (v = 0; v < MIN (num_vectors, 1000); 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 (vectors[v]); + 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_vectors_get_strokes (vectors[v], &num_strokes); + + for (s = 0; s < num_strokes; s++) + { + GimpVectorsStrokeType type; + gdouble *points; + gint num_points; + gboolean closed; + gint p = 0; + + type = gimp_vectors_stroke_get_points (vectors[v], strokes[s], + &num_points, &points, &closed); + + if (type != GIMP_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_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, + TiffSaveVals *tsvals, + gint32 image, + gint32 layer, + gint32 page, + gint32 num_pages, + gint32 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; + 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; + + compression = tsvals->compression; + + layer_name = gimp_item_get_name (layer); + + /* Disabled because this isn't in older releases of libtiff, and it + * wasn't helping much anyway + */ +#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 (layer); + buffer = gimp_drawable_get_buffer (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_GAMMA: + /* 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: + case GIMP_PRECISION_U16_GAMMA: + bitspersample = 16; + sampleformat = SAMPLEFORMAT_UINT; + break; + + case GIMP_PRECISION_U32_LINEAR: + case GIMP_PRECISION_U32_GAMMA: + bitspersample = 32; + sampleformat = SAMPLEFORMAT_UINT; + break; + + case GIMP_PRECISION_HALF_LINEAR: + case GIMP_PRECISION_HALF_GAMMA: + bitspersample = 16; + sampleformat = SAMPLEFORMAT_IEEEFP; + break; + + default: + case GIMP_PRECISION_FLOAT_LINEAR: + case GIMP_PRECISION_FLOAT_GAMMA: + bitspersample = 32; + sampleformat = SAMPLEFORMAT_IEEEFP; + break; + + case GIMP_PRECISION_DOUBLE_LINEAR: + case GIMP_PRECISION_DOUBLE_GAMMA: + 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 (tsvals->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 (tsvals->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: + cmap = gimp_image_get_colormap (image, &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 == GIMP_INDEXEDA_IMAGE) ? 2 : 1; + bytesperrow = cols; + alpha = (drawable_type == GIMP_INDEXEDA_IMAGE); + format = gimp_drawable_get_format (layer); + + g_free (cmap); + break; + + default: + goto out; + } + + 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_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; + } + } + + + /* Set TIFF parameters. */ + 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 (tsvals->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_offsets (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 && + (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 (tsvals->save_geotiff) + { + GimpParasite *parasite = NULL; + + parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_ModelPixelScale"); + + if (parasite) + { + TIFFSetField (tif, + GEOTIFF_MODELPIXELSCALE, + (gimp_parasite_data_size (parasite) / TIFFDataWidth (TIFF_DOUBLE)), + gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + + parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_ModelTiePoint"); + if (parasite) + { + TIFFSetField (tif, + GEOTIFF_MODELTIEPOINT, + (gimp_parasite_data_size (parasite) / TIFFDataWidth (TIFF_DOUBLE)), + gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + + parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_ModelTransformation"); + if (parasite) + { + TIFFSetField (tif, + GEOTIFF_MODELTRANSFORMATION, + (gimp_parasite_data_size (parasite) / TIFFDataWidth (TIFF_DOUBLE)), + gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + + parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_KeyDirectory"); + if (parasite) + { + TIFFSetField (tif, + GEOTIFF_KEYDIRECTORY, + (gimp_parasite_data_size (parasite) / TIFFDataWidth (TIFF_SHORT)), + gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + + parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_DoubleParams"); + if (parasite) + { + TIFFSetField (tif, + GEOTIFF_DOUBLEPARAMS, + (gimp_parasite_data_size (parasite) / TIFFDataWidth (TIFF_DOUBLE)), + gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + + parasite = gimp_image_get_parasite (image,"Gimp_GeoTIFF_Asciiparams"); + if (parasite) + { + TIFFSetField (tif, + GEOTIFF_ASCIIPARAMS, + gimp_parasite_data (parasite)); + gimp_parasite_free (parasite); + } + } + + 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, + TiffSaveVals *tsvals, + gint32 image, + GimpMetadata *metadata, + GimpMetadataSaveFlags metadata_flags, + gint saved_bpp) +{ + gchar **exif_tags; + + /* See bug 758909: clear TIFFTAG_MIN/MAXSAMPLEVALUE because + * exiv2 saves them with wrong type and the original values + * could be invalid, see also bug 761823. + * we also clear some other tags that were only meaningful + * for the original imported image. + */ + static const gchar *exif_tags_to_remove[] = + { + "Exif.Image.0x0118", /* 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_clear_tag (GEXIV2_METADATA (metadata), + exif_tags_to_remove[k]); + } + + /* get rid of all the EXIF tags for anything but the first sub image. */ + exif_tags = gexiv2_metadata_get_exif_tags (GEXIV2_METADATA(metadata)); + for (char **tag = exif_tags; *tag; tag++) + { + /* 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_clear_tag (GEXIV2_METADATA (metadata), *tag); + if (g_str_has_prefix (*tag, "Exif.SubImage") + && (*tag)[strlen ("Exif.SubImage")] >= '0' + && (*tag)[strlen ("Exif.SubImage")] <= '9') + gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata), *tag); + if (g_str_has_prefix (*tag, "Exif.Thumbnail")) + gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata), *tag); + } + + gimp_metadata_set_bits_per_sample (metadata, saved_bpp); + + if (tsvals->save_exif) + metadata_flags |= GIMP_METADATA_SAVE_EXIF; + else + metadata_flags &= ~GIMP_METADATA_SAVE_EXIF; + + if (tsvals->save_xmp) + metadata_flags |= GIMP_METADATA_SAVE_XMP; + else + metadata_flags &= ~GIMP_METADATA_SAVE_XMP; + + if (tsvals->save_iptc) + metadata_flags |= GIMP_METADATA_SAVE_IPTC; + else + metadata_flags &= ~GIMP_METADATA_SAVE_IPTC; + + if (tsvals->save_thumbnail) + metadata_flags |= GIMP_METADATA_SAVE_THUMBNAIL; + else + metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL; + + if (tsvals->save_profile) + metadata_flags |= GIMP_METADATA_SAVE_COLOR_PROFILE; + else + metadata_flags &= ~GIMP_METADATA_SAVE_COLOR_PROFILE; + + gimp_image_metadata_save_finish (image, + "image/tiff", + metadata, metadata_flags, + file, NULL); +} + +gboolean +save_image (GFile *file, + TiffSaveVals *tsvals, + gint32 image, + gint32 orig_image, /* the export function */ + /* might have created */ + /* a duplicate */ + const gchar *image_comment, + gint *saved_bpp, + GimpMetadata *metadata, + GimpMetadataSaveFlags metadata_flags, + GError **error) +{ + TIFF *tif = NULL; + gboolean status = FALSE; + gboolean out_linear = FALSE; + gint32 num_layers, *layers, current_layer = 0; + gint origin_x = 0, origin_y = 0; + gint32 i; + + layers = gimp_image_get_layers (image, &num_layers); + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_file_get_utf8_name (file)); + + /* Open file and write some global data */ +#ifdef TIFF_VERSION_BIG + tif = tiff_open (file, (tsvals->bigtiff ? "w8" : "w"), error); +#else + tif = tiff_open (file, "w", error); +#endif + + 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; + } + + /* The TIFF spec explicitly says ASCII for the image description. */ + if (image_comment) + { + const gchar *c = image_comment; + gint len; + + for (len = strlen (c); len; c++, len--) + { + if ((guchar) *c > 127) + { + g_message (_("The TIFF format only supports comments in\n" + "7bit ASCII encoding. No comment is saved.")); + image_comment = NULL; + + break; + } + } + } + + /* do we have a comment? If so, create a new parasite to hold it, + * and attach it to the image. The attach function automatically + * detaches a previous incarnation of the parasite. */ + if (image_comment && *image_comment) + { + GimpParasite *parasite; + + TIFFSetField (tif, TIFFTAG_IMAGEDESCRIPTION, image_comment); + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (image_comment) + 1, image_comment); + gimp_image_attach_parasite (orig_image, parasite); + gimp_parasite_free (parasite); + } + +#ifdef TIFFTAG_ICCPROFILE + if (tsvals->save_profile) + { + GimpColorProfile *profile; + const guint8 *icc_data; + gsize icc_length; + + 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)); + + /* Write the profile to the TIFF file. */ + icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length); + TIFFSetField (tif, TIFFTAG_ICCPROFILE, icc_length, icc_data); + g_object_unref (profile); + } +#endif + + /* calculate the top-left coordinates */ + for (i = 0; i < num_layers; i++) + { + gint offset_x, offset_y; + + gimp_drawable_offsets (layers[i], &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, tsvals, image, + layers[num_layers - current_layer - 1], + 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, tsvals, image, metadata, metadata_flags, *saved_bpp); + + /* 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, tsvals, image, + layers[num_layers - current_layer - 1], + 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); + + return status; +} + +gboolean +save_dialog (TiffSaveVals *tsvals, + gint32 image, + const gchar *help_id, + gboolean has_alpha, + gboolean is_monochrome, + gboolean is_indexed, + gchar **image_comment, + GError **error, + gboolean classic_tiff_failed) +{ + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *entry; + GtkWidget *toggle; + GtkWidget *label; + GtkWidget *cmp_g3; + GtkWidget *cmp_g4; + GtkWidget *cmp_jpeg; + GtkBuilder *builder; + gchar *ui_file; + gchar **parasites; + gboolean run; + gboolean has_geotiff = FALSE; + gint n_parasites; + gint i; + + parasites = gimp_image_get_parasite_list (image, &n_parasites); + for (i = 0; i < n_parasites; i++) + { + if (g_str_has_prefix (parasites[i], "Gimp_GeoTIFF_")) + { + has_geotiff = TRUE; + break; + } + } + g_strfreev (parasites); + + dialog = gimp_export_dialog_new (_("TIFF"), PLUG_IN_ROLE, help_id); + + builder = gtk_builder_new (); + ui_file = g_build_filename (gimp_data_directory (), + "ui", "plug-ins", "plug-in-file-tiff.ui", + NULL); + if (! gtk_builder_add_from_file (builder, ui_file, error)) + { + gchar *display_name = g_filename_display_name (ui_file); + + g_printerr (_("Error loading UI file '%s': %s"), + display_name, error ? (*error)->message : _("Unknown error")); + + g_free (display_name); + } + + g_free (ui_file); + + vbox = GTK_WIDGET (gtk_builder_get_object (builder, "tiff_export_vbox")); + + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + vbox = GTK_WIDGET (gtk_builder_get_object (builder, "radio_button_box")); + + label = GTK_WIDGET (gtk_builder_get_object (builder, "bigtiff-warning")); +#ifdef TIFF_VERSION_BIG + if (classic_tiff_failed) + { + gtk_label_set_markup (GTK_LABEL (label), + _("<i>Warning: maximum TIFF file size exceeded.\n Retry as BigTIFF or cancel.</i>")); + 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); + } +#endif + if (! classic_tiff_failed) + { + gtk_widget_hide (label); + } + + frame = gimp_int_radio_group_new (TRUE, _("Compression"), + G_CALLBACK (gimp_radio_button_update), + &tsvals->compression, tsvals->compression, + + _("_None"), COMPRESSION_NONE, NULL, + _("_LZW"), COMPRESSION_LZW, NULL, + _("_Pack Bits"), COMPRESSION_PACKBITS, NULL, + _("_Deflate"), COMPRESSION_ADOBE_DEFLATE, NULL, + _("_JPEG"), COMPRESSION_JPEG, &cmp_jpeg, + _("CCITT Group _3 fax"), COMPRESSION_CCITTFAX3, &cmp_g3, + _("CCITT Group _4 fax"), COMPRESSION_CCITTFAX4, &cmp_g4, + + NULL); + + gtk_widget_set_sensitive (cmp_g3, is_monochrome); + gtk_widget_set_sensitive (cmp_g4, is_monochrome); + gtk_widget_set_sensitive (cmp_jpeg, ! is_indexed); + + if (! is_monochrome) + { + if (tsvals->compression == COMPRESSION_CCITTFAX3 || + tsvals->compression == COMPRESSION_CCITTFAX4) + { + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (cmp_g3), + COMPRESSION_NONE); + } + } + + if (is_indexed && tsvals->compression == COMPRESSION_JPEG) + { + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (cmp_jpeg), + COMPRESSION_NONE); + } + + gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); + gtk_widget_show (frame); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-alpha")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + has_alpha && (tsvals->save_transp_pixels || is_indexed)); + gtk_widget_set_sensitive (toggle, has_alpha && ! is_indexed); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_transp_pixels); + + entry = GTK_WIDGET (gtk_builder_get_object (builder, "commentfield")); + gtk_entry_set_text (GTK_ENTRY (entry), *image_comment ? *image_comment : ""); + + g_signal_connect (entry, "changed", + G_CALLBACK (comment_entry_callback), + image_comment); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-exif")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->save_exif); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_exif); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-xmp")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->save_xmp); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_xmp); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-iptc")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->save_iptc); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_iptc); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-geotiff")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + has_geotiff && tsvals->save_geotiff); + gtk_widget_set_sensitive (toggle, has_geotiff);; + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_geotiff); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-thumbnail")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->save_thumbnail); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_thumbnail); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-color-profile")); +#ifdef TIFFTAG_ICCPROFILE + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->save_profile); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_profile); +#else + gtk_widget_hide (toggle); +#endif + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "bigtiff")); +#ifdef TIFF_VERSION_BIG + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->bigtiff); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->bigtiff); +#else + gtk_widget_hide (toggle); +#endif + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "save-layers")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->save_layers); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->save_layers); + + frame = GTK_WIDGET (gtk_builder_get_object (builder, "layers-frame")); + g_object_bind_property (toggle, "active", + frame, "sensitive", + G_BINDING_SYNC_CREATE); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "crop-layers")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals->crop_layers); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals->crop_layers); + + gtk_widget_show (dialog); + + run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); + + gtk_widget_destroy (dialog); + + return run; +} + +static void +comment_entry_callback (GtkWidget *widget, + gchar **comment) +{ + const gchar *text = gtk_entry_get_text (GTK_ENTRY (widget)); + + g_free (*comment); + *comment = g_strdup (text); +} + +/* 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; + } +} |