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

569 lines
19 KiB
C

/* tiff loading 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 <tiffio.h>
#include <gexiv2/gexiv2.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "file-tiff-io.h"
#include "file-tiff-load.h"
#include "file-tiff-save.h"
#include "libgimp/stdplugins-intl.h"
#define LOAD_PROC "file-tiff-load"
#define SAVE_PROC "file-tiff-save"
#define SAVE2_PROC "file-tiff-save2"
#define PLUG_IN_BINARY "file-tiff"
static void query (void);
static void run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
static gboolean image_is_monochrome (gint32 image);
const GimpPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run, /* run_proc */
};
static TiffSaveVals tsvals =
{
COMPRESSION_NONE, /* compression */
TRUE, /* alpha handling */
TRUE, /* save transp. pixels */
FALSE, /* save exif */
FALSE, /* save xmp */
FALSE, /* save iptc */
TRUE /* save thumbnail */
};
static gchar *image_comment = NULL;
MAIN ()
static void
query (void)
{
static const GimpParamDef load_args[] =
{
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
{ GIMP_PDB_STRING, "filename", "The name of the file to load" },
{ GIMP_PDB_STRING, "raw-filename", "The name of the file to load" }
};
static const GimpParamDef load_return_vals[] =
{
{ GIMP_PDB_IMAGE, "image", "Output image" }
};
#define COMMON_SAVE_ARGS \
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },\
{ GIMP_PDB_IMAGE, "image", "Input image" },\
{ GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" },\
{ GIMP_PDB_STRING, "filename", "The name of the file to save the image in" },\
{ GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" },\
{ GIMP_PDB_INT32, "compression", "Compression type: { NONE (0), LZW (1), PACKBITS (2), DEFLATE (3), JPEG (4), CCITT G3 Fax (5), CCITT G4 Fax (6) }" }
static const GimpParamDef save_args_old[] =
{
COMMON_SAVE_ARGS
};
static const GimpParamDef save_args[] =
{
COMMON_SAVE_ARGS,
{ GIMP_PDB_INT32, "save-transp-pixels", "Keep the color data masked by an alpha channel intact" }
};
gimp_install_procedure (LOAD_PROC,
"loads files of the tiff file format",
"FIXME: write help for tiff_load",
"Spencer Kimball, Peter Mattis & Nick Lamb",
"Nick Lamb <njl195@zepler.org.uk>",
"1995-1996,1998-2003",
N_("TIFF image"),
NULL,
GIMP_PLUGIN,
G_N_ELEMENTS (load_args),
G_N_ELEMENTS (load_return_vals),
load_args, load_return_vals);
gimp_register_file_handler_mime (LOAD_PROC, "image/tiff");
gimp_register_file_handler_uri (LOAD_PROC);
gimp_register_magic_load_handler (LOAD_PROC,
"tif,tiff",
"",
"0,string,II*\\0,0,string,MM\\0*");
gimp_install_procedure (SAVE_PROC,
"saves files in the tiff file format",
"Saves files in the Tagged Image File Format. "
"The value for the saved comment is taken "
"from the 'gimp-comment' parasite.",
"Spencer Kimball & Peter Mattis",
"Spencer Kimball & Peter Mattis",
"1995-1996,2000-2003",
N_("TIFF image"),
"RGB*, GRAY*, INDEXED",
GIMP_PLUGIN,
G_N_ELEMENTS (save_args_old), 0,
save_args_old, NULL);
gimp_register_file_handler_mime (SAVE_PROC, "image/tiff");
gimp_register_file_handler_uri (SAVE_PROC);
gimp_register_save_handler (SAVE_PROC, "tif,tiff", "");
gimp_install_procedure (SAVE2_PROC,
"saves files in the tiff file format",
"Saves files in the Tagged Image File Format. "
"The value for the saved comment is taken "
"from the 'gimp-comment' parasite.",
"Spencer Kimball & Peter Mattis",
"Spencer Kimball & Peter Mattis",
"1995-1996,2000-2003",
N_("TIFF image"),
"RGB*, GRAY*, INDEXED",
GIMP_PLUGIN,
G_N_ELEMENTS (save_args), 0,
save_args, NULL);
}
static void
run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
static GimpParam values[2];
GimpRunMode run_mode;
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
GError *error = NULL;
INIT_I18N ();
gegl_init (NULL, NULL);
run_mode = param[0].data.d_int32;
*nreturn_vals = 1;
*return_vals = values;
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
if (strcmp (name, LOAD_PROC) == 0)
{
GFile *file = g_file_new_for_uri (param[1].data.d_string);
TIFF *tif;
tif = tiff_open (file, "r", &error);
if (tif)
{
TiffSelectedPages pages;
pages.target = GIMP_PAGE_SELECTOR_TARGET_LAYERS;
gimp_get_data (LOAD_PROC, &pages.target);
pages.n_pages = pages.o_pages = TIFFNumberOfDirectories (tif);
if (pages.n_pages == 0)
{
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("TIFF '%s' does not contain any directories"),
gimp_file_get_utf8_name (file));
status = GIMP_PDB_EXECUTION_ERROR;
}
else
{
gboolean run_it = FALSE;
gint i;
if (run_mode != GIMP_RUN_INTERACTIVE)
{
pages.pages = g_new (gint, pages.n_pages);
for (i = 0; i < pages.n_pages; i++)
pages.pages[i] = i;
run_it = TRUE;
}
else
{
gimp_ui_init (PLUG_IN_BINARY, FALSE);
}
if (pages.n_pages == 1)
{
pages.pages = g_new0 (gint, pages.n_pages);
pages.target = GIMP_PAGE_SELECTOR_TARGET_LAYERS;
run_it = TRUE;
}
if ((! run_it) && (run_mode == GIMP_RUN_INTERACTIVE))
run_it = load_dialog (tif, LOAD_PROC, &pages);
if (run_it)
{
gint32 image;
gboolean resolution_loaded = FALSE;
gimp_set_data (LOAD_PROC,
&pages.target, sizeof (pages.target));
image = load_image (file, tif, &pages,
&resolution_loaded,
&error);
g_free (pages.pages);
if (image > 0)
{
GimpMetadata *metadata;
metadata = gimp_image_metadata_load_prepare (image,
"image/tiff",
file, NULL);
if (metadata)
{
GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL;
if (resolution_loaded)
flags &= ~GIMP_METADATA_LOAD_RESOLUTION;
gimp_image_metadata_load_finish (image, "image/tiff",
metadata, flags,
run_mode == GIMP_RUN_INTERACTIVE);
g_object_unref (metadata);
}
*nreturn_vals = 2;
values[1].type = GIMP_PDB_IMAGE;
values[1].data.d_image = image;
}
else
{
status = GIMP_PDB_EXECUTION_ERROR;
}
}
else
{
status = GIMP_PDB_CANCEL;
}
}
TIFFClose (tif);
}
else
{
status = GIMP_PDB_EXECUTION_ERROR;
}
g_object_unref (file);
}
else if ((strcmp (name, SAVE_PROC) == 0) ||
(strcmp (name, SAVE2_PROC) == 0))
{
/* Plug-in is either file_tiff_save or file_tiff_save2 */
GimpMetadata *metadata;
GimpMetadataSaveFlags metadata_flags;
GimpParasite *parasite;
gint32 image = param[1].data.d_int32;
gint32 drawable = param[2].data.d_int32;
gint32 orig_image = image;
GimpExportReturn export = GIMP_EXPORT_CANCEL;
switch (run_mode)
{
case GIMP_RUN_INTERACTIVE:
case GIMP_RUN_WITH_LAST_VALS:
gimp_ui_init (PLUG_IN_BINARY, FALSE);
export = gimp_export_image (&image, &drawable, "TIFF",
GIMP_EXPORT_CAN_HANDLE_RGB |
GIMP_EXPORT_CAN_HANDLE_GRAY |
GIMP_EXPORT_CAN_HANDLE_INDEXED |
GIMP_EXPORT_CAN_HANDLE_ALPHA);
if (export == GIMP_EXPORT_CANCEL)
{
values[0].data.d_status = GIMP_PDB_CANCEL;
return;
}
break;
default:
break;
}
/* Override the defaults with preferences. */
metadata = gimp_image_metadata_save_prepare (orig_image,
"image/tiff",
&metadata_flags);
tsvals.save_exif = (metadata_flags & GIMP_METADATA_SAVE_EXIF) != 0;
tsvals.save_xmp = (metadata_flags & GIMP_METADATA_SAVE_XMP) != 0;
tsvals.save_iptc = (metadata_flags & GIMP_METADATA_SAVE_IPTC) != 0;
tsvals.save_thumbnail = (metadata_flags & GIMP_METADATA_SAVE_THUMBNAIL) != 0;
parasite = gimp_image_get_parasite (orig_image, "gimp-comment");
if (parasite)
{
image_comment = g_strndup (gimp_parasite_data (parasite),
gimp_parasite_data_size (parasite));
gimp_parasite_free (parasite);
}
switch (run_mode)
{
case GIMP_RUN_INTERACTIVE:
/* Possibly retrieve data */
gimp_get_data (SAVE_PROC, &tsvals);
parasite = gimp_image_get_parasite (orig_image, "tiff-save-options");
if (parasite)
{
const TiffSaveVals *pvals = gimp_parasite_data (parasite);
if (pvals->compression == COMPRESSION_DEFLATE)
tsvals.compression = COMPRESSION_ADOBE_DEFLATE;
else
tsvals.compression = pvals->compression;
tsvals.save_transp_pixels = pvals->save_transp_pixels;
}
gimp_parasite_free (parasite);
/* First acquire information with a dialog */
if (! save_dialog (&tsvals,
SAVE_PROC,
gimp_drawable_has_alpha (drawable),
image_is_monochrome (image),
gimp_image_base_type (image) == GIMP_INDEXED,
&image_comment))
{
status = GIMP_PDB_CANCEL;
}
break;
case GIMP_RUN_NONINTERACTIVE:
/* Make sure all the arguments are there! */
if (nparams == 6 || nparams == 7)
{
switch (param[5].data.d_int32)
{
case 0: tsvals.compression = COMPRESSION_NONE; break;
case 1: tsvals.compression = COMPRESSION_LZW; break;
case 2: tsvals.compression = COMPRESSION_PACKBITS; break;
case 3: tsvals.compression = COMPRESSION_ADOBE_DEFLATE; break;
case 4: tsvals.compression = COMPRESSION_JPEG; break;
case 5: tsvals.compression = COMPRESSION_CCITTFAX3; break;
case 6: tsvals.compression = COMPRESSION_CCITTFAX4; break;
default: status = GIMP_PDB_CALLING_ERROR; break;
}
if (nparams == 7)
tsvals.save_transp_pixels = param[6].data.d_int32;
else
tsvals.save_transp_pixels = TRUE;
}
else
{
status = GIMP_PDB_CALLING_ERROR;
}
break;
case GIMP_RUN_WITH_LAST_VALS:
/* Possibly retrieve data */
gimp_get_data (SAVE_PROC, &tsvals);
parasite = gimp_image_get_parasite (orig_image, "tiff-save-options");
if (parasite)
{
const TiffSaveVals *pvals = gimp_parasite_data (parasite);
tsvals.compression = pvals->compression;
tsvals.save_transp_pixels = pvals->save_transp_pixels;
}
gimp_parasite_free (parasite);
break;
default:
break;
}
if (status == GIMP_PDB_SUCCESS)
{
GFile *file;
gint saved_bpp;
file = g_file_new_for_uri (param[3].data.d_string);
if (save_image (file, &tsvals,
image, drawable, orig_image, image_comment,
&saved_bpp, &error))
{
if (metadata)
{
/* 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
*/
gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
"Exif.Image.0x0118");
gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
"Exif.Image.0x0119");
gexiv2_metadata_clear_tag (GEXIV2_METADATA (metadata),
"Exif.Image.PageNumber");
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;
/* never save metadata thumbnails for TIFF, see bug #729952 */
metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL;
gimp_image_metadata_save_finish (image,
"image/tiff",
metadata, metadata_flags,
file, NULL);
}
/* Store mvals data */
gimp_set_data (SAVE_PROC, &tsvals, sizeof (TiffSaveVals));
}
else
{
status = GIMP_PDB_EXECUTION_ERROR;
}
g_object_unref (file);
}
if (export == GIMP_EXPORT_EXPORT)
gimp_image_delete (image);
if (metadata)
g_object_unref (metadata);
}
else
{
status = GIMP_PDB_CALLING_ERROR;
}
if (status != GIMP_PDB_SUCCESS && error)
{
*nreturn_vals = 2;
values[1].type = GIMP_PDB_STRING;
values[1].data.d_string = error->message;
}
values[0].data.d_status = status;
}
static gboolean
image_is_monochrome (gint32 image)
{
guchar *colors;
gint num_colors;
gboolean monochrome = FALSE;
g_return_val_if_fail (image != -1, FALSE);
colors = gimp_image_get_colormap (image, &num_colors);
if (colors)
{
if (num_colors == 2 || num_colors == 1)
{
const guchar bw_map[] = { 0, 0, 0, 255, 255, 255 };
const guchar wb_map[] = { 255, 255, 255, 0, 0, 0 };
if (memcmp (colors, bw_map, 3 * num_colors) == 0 ||
memcmp (colors, wb_map, 3 * num_colors) == 0)
{
monochrome = TRUE;
}
}
g_free (colors);
}
return monochrome;
}