/* tiff loading 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 #include #include #include "file-tiff.h" #include "file-tiff-io.h" #include "file-tiff-load.h" #include "file-tiff-save.h" #include "libpika/stdplugins-intl.h" #define SAVE_PROC "file-tiff-save" #define PLUG_IN_BINARY "file-tiff" typedef struct _Tiff Tiff; typedef struct _TiffClass TiffClass; struct _Tiff { PikaPlugIn parent_instance; }; struct _TiffClass { PikaPlugInClass parent_class; }; #define TIFF_TYPE (tiff_get_type ()) #define TIFF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TIFF_TYPE, Tiff)) GType tiff_get_type (void) G_GNUC_CONST; static GList * tiff_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * tiff_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * tiff_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data); static PikaValueArray * tiff_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, PikaMetadata *metadata, PikaProcedureConfig *config, gpointer run_data); static PikaPDBStatusType tiff_save_rec (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *orig_image, gint n_orig_drawables, PikaDrawable **orig_drawables, GFile *file, PikaProcedureConfig *config, PikaMetadata *metadata, gboolean retried, GError **error); static gboolean image_is_monochrome (PikaImage *image); static gboolean image_is_multi_layer (PikaImage *image); G_DEFINE_TYPE (Tiff, tiff, PIKA_TYPE_PLUG_IN) PIKA_MAIN (TIFF_TYPE) DEFINE_STD_SET_I18N static void tiff_class_init (TiffClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = tiff_query_procedures; plug_in_class->create_procedure = tiff_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void tiff_init (Tiff *tiff) { } static GList * tiff_query_procedures (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (LOAD_PROC)); list = g_list_append (list, g_strdup (SAVE_PROC)); return list; } static PikaProcedure * tiff_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, LOAD_PROC)) { procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, tiff_load, NULL, NULL); pika_procedure_set_menu_label (procedure, _("TIFF or BigTIFF image")); pika_procedure_set_documentation (procedure, "Loads files of the TIFF and BigTIFF file formats", "Loads files of the Tag Image File Format (TIFF) and " "its 64-bit offsets variant (BigTIFF)", name); pika_procedure_set_attribution (procedure, "Spencer Kimball, Peter Mattis & Nick Lamb", "Nick Lamb ", "1995-1996,1998-2003"); pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure), TRUE); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/tiff"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "tif,tiff"); pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure), "0,string,II*\\0,0,string,MM\\0*"); /* TODO: the 2 below AUX arguments should likely be real arguments, but I * just wanted to get rid of pika_get_data/pika_set_data() usage at first * and didn't dig much into proper and full usage. Since it's always * possible to add arguments, but not to remove them, I prefer to make * them AUX for now and leave it as further exercise to decide whether it * should be part of the PDB API. */ PIKA_PROC_AUX_ARG_ENUM (procedure, "target", "Open _pages as", NULL, PIKA_TYPE_PAGE_SELECTOR_TARGET, PIKA_PAGE_SELECTOR_TARGET_LAYERS, G_PARAM_READWRITE); PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "keep-empty-space", _("_Keep empty space around imported layers"), NULL, TRUE, PIKA_PARAM_READWRITE); } else if (! strcmp (name, SAVE_PROC)) { procedure = pika_save_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, TRUE, tiff_save, NULL, NULL); pika_procedure_set_image_types (procedure, "*"); pika_procedure_set_menu_label (procedure, _("TIFF or BigTIFF image")); pika_procedure_set_documentation (procedure, "Exports files in the TIFF or BigTIFF file formats", "Exports files in the Tag Image File Format (TIFF) or " "its 64-bit offsets variant (BigTIFF) able to support " "much bigger file sizes", name); pika_procedure_set_attribution (procedure, "Spencer Kimball & Peter Mattis", "Spencer Kimball & Peter Mattis", "1995-1996,2000-2003"); pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure), TRUE); pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure), _("TIFF")); pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure), "image/tiff"); pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure), "tif,tiff"); PIKA_PROC_ARG_BOOLEAN (procedure, "bigtiff", _("Export in _BigTIFF variant file format"), _("The BigTIFF variant file format uses 64-bit offsets, " "hence supporting over 4GiB files and bigger"), FALSE, G_PARAM_READWRITE); PIKA_PROC_ARG_CHOICE (procedure, "compression", _("Co_mpression"), _("Compression type"), pika_choice_new_with_values ("none", PIKA_COMPRESSION_NONE, _("None"), NULL, "lzw", PIKA_COMPRESSION_LZW, _("LZW"), NULL, "packbits", PIKA_COMPRESSION_PACKBITS, _("Pack Bits"), NULL, "adobe_deflate", PIKA_COMPRESSION_ADOBE_DEFLATE, _("Deflate"), NULL, "jpeg", PIKA_COMPRESSION_JPEG, _("JPEG"), NULL, "ccittfax3", PIKA_COMPRESSION_CCITTFAX3, _("CCITT Group 3 fax"), NULL, "ccittfax4", PIKA_COMPRESSION_CCITTFAX4, _("CCITT Group 4 fax"), NULL, NULL), "none", G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "save-transparent-pixels", _("Save color _values from transparent pixels"), _("Keep the color data masked by an alpha channel " "intact (do not store premultiplied components)"), TRUE, G_PARAM_READWRITE); PIKA_PROC_ARG_BOOLEAN (procedure, "cmyk", _("Export as CMY_K"), _("Create a CMYK TIFF image using the soft-proofing color profile"), FALSE, G_PARAM_READWRITE); PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-layers", _("Save La_yers"), _("Save Layers"), TRUE, G_PARAM_READWRITE); PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "crop-layers", _("Crop L_ayers"), _("Crop Layers"), TRUE, G_PARAM_READWRITE); PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-geotiff", _("Save _GeoTIFF data"), _("Save GeoTIFF data"), TRUE, G_PARAM_READWRITE); pika_save_procedure_set_support_exif (PIKA_SAVE_PROCEDURE (procedure), TRUE); pika_save_procedure_set_support_iptc (PIKA_SAVE_PROCEDURE (procedure), TRUE); pika_save_procedure_set_support_xmp (PIKA_SAVE_PROCEDURE (procedure), TRUE); #ifdef TIFFTAG_ICCPROFILE pika_save_procedure_set_support_profile (PIKA_SAVE_PROCEDURE (procedure), TRUE); #endif pika_save_procedure_set_support_thumbnail (PIKA_SAVE_PROCEDURE (procedure), TRUE); pika_save_procedure_set_support_comment (PIKA_SAVE_PROCEDURE (procedure), TRUE); } return procedure; } static PikaValueArray * tiff_load (PikaProcedure *procedure, PikaRunMode run_mode, GFile *file, PikaMetadata *metadata, PikaMetadataLoadFlags *flags, PikaProcedureConfig *config, gpointer run_data) { PikaValueArray *return_vals; PikaPDBStatusType status; PikaImage *image = NULL; gboolean resolution_loaded = FALSE; gboolean profile_loaded = FALSE; gboolean ps_metadata_loaded = FALSE; GError *error = NULL; gegl_init (NULL, NULL); if (run_mode == PIKA_RUN_INTERACTIVE) pika_ui_init (PLUG_IN_BINARY); status = load_image (file, run_mode, &image, &resolution_loaded, &profile_loaded, &ps_metadata_loaded, config, &error); if (!image) return pika_procedure_new_return_values (procedure, status, error); if (resolution_loaded) *flags &= ~PIKA_METADATA_LOAD_RESOLUTION; if (profile_loaded) *flags &= ~PIKA_METADATA_LOAD_COLORSPACE; return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } static PikaValueArray * tiff_save (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, GFile *file, PikaMetadata *metadata, PikaProcedureConfig *config, gpointer run_data) { GError *error = NULL; PikaPDBStatusType status = PIKA_PDB_SUCCESS; gegl_init (NULL, NULL); switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: pika_ui_init (PLUG_IN_BINARY); break; default: break; } status = tiff_save_rec (procedure, run_mode, image, n_drawables, drawables, file, config, metadata, FALSE, &error); return pika_procedure_new_return_values (procedure, status, error); } static PikaPDBStatusType tiff_save_rec (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *orig_image, gint n_orig_drawables, PikaDrawable **orig_drawables, GFile *file, PikaProcedureConfig *config, PikaMetadata *metadata, gboolean retried, GError **error) { PikaImage *image = orig_image; PikaDrawable **drawables = orig_drawables; gint n_drawables = n_orig_drawables; PikaPDBStatusType status = PIKA_PDB_SUCCESS; PikaExportReturn export = PIKA_EXPORT_CANCEL; gboolean bigtiff = FALSE; if (run_mode == PIKA_RUN_INTERACTIVE) { if (! save_dialog (orig_image, procedure, G_OBJECT (config), n_drawables == 1 ? pika_drawable_has_alpha (drawables[0]) : TRUE, image_is_monochrome (orig_image), pika_image_get_base_type (orig_image) == PIKA_INDEXED, image_is_multi_layer (orig_image), retried)) { return PIKA_PDB_CANCEL; } } switch (run_mode) { case PIKA_RUN_INTERACTIVE: case PIKA_RUN_WITH_LAST_VALS: { PikaExportCapabilities capabilities; PikaCompression compression; gboolean save_layers; gboolean crop_layers; g_object_get (config, "bigtiff", &bigtiff, "save-layers", &save_layers, "crop-layers", &crop_layers, NULL); compression = pika_procedure_config_get_choice_id (config, "compression"); if (compression == PIKA_COMPRESSION_CCITTFAX3 || compression == PIKA_COMPRESSION_CCITTFAX4) { /* G3/G4 are fax compressions. They only support * monochrome images without alpha support. */ capabilities = PIKA_EXPORT_CAN_HANDLE_INDEXED; } else { capabilities = (PIKA_EXPORT_CAN_HANDLE_RGB | PIKA_EXPORT_CAN_HANDLE_GRAY | PIKA_EXPORT_CAN_HANDLE_INDEXED | PIKA_EXPORT_CAN_HANDLE_ALPHA); } if (save_layers && image_is_multi_layer (orig_image)) { capabilities |= PIKA_EXPORT_CAN_HANDLE_LAYERS; if (crop_layers) capabilities |= PIKA_EXPORT_NEEDS_CROP; } export = pika_export_image (&image, &n_drawables, &drawables, "TIFF", capabilities); if (export == PIKA_EXPORT_CANCEL) return PIKA_PDB_CANCEL; } break; default: break; } #if 0 /* FIXME */ if (n_drawables != 1 && tsvals.save_layers) { g_set_error (&error, G_FILE_ERROR, 0, _("\"Save layers\" option not set while trying to export multiple layers.")); status = PIKA_PDB_CALLING_ERROR; } #endif if (status == PIKA_PDB_SUCCESS) { if (! save_image (file, image, orig_image, G_OBJECT (config), metadata, error)) status = PIKA_PDB_EXECUTION_ERROR; } if (export == PIKA_EXPORT_EXPORT) { pika_image_delete (image); g_free (drawables); } if (status == PIKA_PDB_EXECUTION_ERROR && run_mode == PIKA_RUN_INTERACTIVE && ! retried && ! bigtiff && tiff_got_file_size_error ()) { /* Retrying but just once, when the save failed because we exceeded * TIFF max size, to propose BigTIFF instead. */ tiff_reset_file_size_error (); g_clear_error (error); return tiff_save_rec (procedure, run_mode, orig_image, n_orig_drawables, orig_drawables, file, config, metadata, TRUE, error); } return status; } static gboolean image_is_monochrome (PikaImage *image) { guchar *colors; gint num_colors; gboolean monochrome = FALSE; g_return_val_if_fail (PIKA_IS_IMAGE (image), FALSE); colors = pika_image_get_colormap (image, NULL, &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; } static gboolean image_is_multi_layer (PikaImage *image) { gint32 n_layers; g_free (pika_image_get_layers (image, &n_layers)); return (n_layers > 1); } gint pika_compression_to_tiff_compression (PikaCompression compression) { switch (compression) { case PIKA_COMPRESSION_NONE: return COMPRESSION_NONE; case PIKA_COMPRESSION_LZW: return COMPRESSION_LZW; case PIKA_COMPRESSION_PACKBITS: return COMPRESSION_PACKBITS; case PIKA_COMPRESSION_ADOBE_DEFLATE: return COMPRESSION_ADOBE_DEFLATE; case PIKA_COMPRESSION_JPEG: return COMPRESSION_JPEG; case PIKA_COMPRESSION_CCITTFAX3: return COMPRESSION_CCITTFAX3; case PIKA_COMPRESSION_CCITTFAX4: return COMPRESSION_CCITTFAX4; } return COMPRESSION_NONE; } PikaCompression tiff_compression_to_pika_compression (gint compression) { switch (compression) { case COMPRESSION_NONE: return PIKA_COMPRESSION_NONE; case COMPRESSION_LZW: return PIKA_COMPRESSION_LZW; case COMPRESSION_PACKBITS: return PIKA_COMPRESSION_PACKBITS; case COMPRESSION_DEFLATE: return PIKA_COMPRESSION_ADOBE_DEFLATE; case COMPRESSION_ADOBE_DEFLATE: return PIKA_COMPRESSION_ADOBE_DEFLATE; case COMPRESSION_OJPEG: return PIKA_COMPRESSION_JPEG; case COMPRESSION_JPEG: return PIKA_COMPRESSION_JPEG; case COMPRESSION_CCITTFAX3: return PIKA_COMPRESSION_CCITTFAX3; case COMPRESSION_CCITTFAX4: return PIKA_COMPRESSION_CCITTFAX4; } return PIKA_COMPRESSION_NONE; }