/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * Copyright (C) 2009 Free Software Foundation, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #ifdef HAVE_LIBTIFF #include #endif /* HAVE_LIBTIFF */ #include #include #include "cairo-io-enum-types.h" #include "gth-image-saver-tiff.h" #include "preferences.h" struct _GthImageSaverTiffPrivate { GSettings *settings; GtkBuilder *builder; char *default_ext; }; G_DEFINE_TYPE_WITH_CODE (GthImageSaverTiff, gth_image_saver_tiff, GTH_TYPE_IMAGE_SAVER, G_ADD_PRIVATE (GthImageSaverTiff)) static void gth_image_saver_tiff_finalize (GObject *object) { GthImageSaverTiff *self = GTH_IMAGE_SAVER_TIFF (object); _g_object_unref (self->priv->settings); _g_object_unref (self->priv->builder); g_free (self->priv->default_ext); G_OBJECT_CLASS (gth_image_saver_tiff_parent_class)->finalize (object); } static const char * gth_image_saver_tiff_get_default_ext (GthImageSaver *base) { GthImageSaverTiff *self = GTH_IMAGE_SAVER_TIFF (base); if (self->priv->default_ext == NULL) self->priv->default_ext = g_settings_get_string (self->priv->settings, PREF_TIFF_DEFAULT_EXT); return self->priv->default_ext; } static GtkWidget * gth_image_saver_tiff_get_control (GthImageSaver *base) { #ifdef HAVE_LIBTIFF GthImageSaverTiff *self = GTH_IMAGE_SAVER_TIFF (base); char **extensions; int i; int active_idx; GthTiffCompression compression_type; _g_object_unref (self->priv->builder); self->priv->builder = _gtk_builder_new_from_file ("tiff-options.ui", "cairo_io"); active_idx = 0; extensions = g_strsplit (gth_image_saver_get_extensions (base), " ", -1); for (i = 0; extensions[i] != NULL; i++) { GtkTreeIter iter; gtk_list_store_append (GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder, "tiff_default_ext_liststore")), &iter); gtk_list_store_set (GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder, "tiff_default_ext_liststore")), &iter, 0, extensions[i], -1); if (g_str_equal (extensions[i], gth_image_saver_get_default_ext (base))) active_idx = i; } gtk_combo_box_set_active (GTK_COMBO_BOX (_gtk_builder_get_widget (self->priv->builder, "tiff_default_extension_combobox")), active_idx); g_strfreev (extensions); compression_type = g_settings_get_enum (self->priv->settings, PREF_TIFF_COMPRESSION); switch (compression_type) { case GTH_TIFF_COMPRESSION_NONE: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_comp_none_radiobutton")), TRUE); break; case GTH_TIFF_COMPRESSION_DEFLATE: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_comp_deflate_radiobutton")), TRUE); break; case GTH_TIFF_COMPRESSION_JPEG: gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_comp_jpeg_radiobutton")), TRUE); break; } gtk_spin_button_set_value (GTK_SPIN_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_hdpi_spinbutton")), g_settings_get_int (self->priv->settings, PREF_TIFF_HORIZONTAL_RES)); gtk_spin_button_set_value (GTK_SPIN_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_vdpi_spinbutton")), g_settings_get_int (self->priv->settings, PREF_TIFF_VERTICAL_RES)); return _gtk_builder_get_widget (self->priv->builder, "tiff_options"); #else /* ! HAVE_LIBTIFF */ return GTH_IMAGE_SAVER_CLASS (gth_image_saver_tiff_parent_class)->get_control (base); #endif /* HAVE_LIBTIFF */ } static void gth_image_saver_tiff_save_options (GthImageSaver *base) { #ifdef HAVE_LIBTIFF GthImageSaverTiff *self = GTH_IMAGE_SAVER_TIFF (base); GtkTreeIter iter; GthTiffCompression compression_type; if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (_gtk_builder_get_widget (self->priv->builder, "tiff_default_extension_combobox")), &iter)) { g_free (self->priv->default_ext); gtk_tree_model_get (GTK_TREE_MODEL (gtk_builder_get_object (self->priv->builder, "tiff_default_ext_liststore")), &iter, 0, &self->priv->default_ext, -1); g_settings_set_string (self->priv->settings, PREF_TIFF_DEFAULT_EXT, self->priv->default_ext); } if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_comp_none_radiobutton")))) compression_type = GTH_TIFF_COMPRESSION_NONE; else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_comp_deflate_radiobutton")))) compression_type = GTH_TIFF_COMPRESSION_DEFLATE; else compression_type = GTH_TIFF_COMPRESSION_JPEG; g_settings_set_enum (self->priv->settings, PREF_TIFF_COMPRESSION, compression_type); g_settings_set_int (self->priv->settings, PREF_TIFF_HORIZONTAL_RES, (int) gtk_spin_button_get_value (GTK_SPIN_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_hdpi_spinbutton")))); g_settings_set_int (self->priv->settings, PREF_TIFF_VERTICAL_RES, (int) gtk_spin_button_get_value (GTK_SPIN_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tiff_vdpi_spinbutton")))); #endif /* HAVE_LIBTIFF */ } static gboolean gth_image_saver_tiff_can_save (GthImageSaver *self, const char *mime_type) { #ifdef HAVE_LIBTIFF return g_content_type_equals (mime_type, "image/tiff"); #else /* ! HAVE_LIBTIFF */ GSList *formats; GSList *scan; GdkPixbufFormat *tiff_format; if (! g_content_type_equals (mime_type, "image/tiff")) return FALSE; formats = gdk_pixbuf_get_formats (); tiff_format = NULL; for (scan = formats; (tiff_format == NULL) && (scan != NULL); scan = g_slist_next (scan)) { GdkPixbufFormat *format = scan->data; char **mime_types; int i; mime_types = gdk_pixbuf_format_get_mime_types (format); for (i = 0; mime_types[i] != NULL; i++) if (g_content_type_equals (mime_types[i], "image/tiff")) break; if (mime_types[i] == NULL) continue; if (! gdk_pixbuf_format_is_writable (format)) continue; tiff_format = format; } return tiff_format != NULL; #endif /* HAVE_LIBTIFF */ } #ifdef HAVE_LIBTIFF /* -- gth_image_saver_tiff_save_pixbuf -- */ #define TILE_HEIGHT 40 /* FIXME */ static tsize_t tiff_read (thandle_t handle, tdata_t buf, tsize_t size) { return -1; } static tsize_t tiff_write (thandle_t handle, tdata_t buf, tsize_t size) { GthBufferData *buffer_data = (GthBufferData *)handle; gth_buffer_data_write (buffer_data, buf, size, NULL); return size; } static toff_t tiff_seek (thandle_t handle, toff_t offset, int whence) { GthBufferData *buffer_data = (GthBufferData *)handle; return gth_buffer_data_seek (buffer_data, offset, whence); } static int tiff_close (thandle_t context) { return 0; } static toff_t tiff_size (thandle_t handle) { return -1; } static gboolean _cairo_surface_write_as_tiff (cairo_surface_t *image, char **buffer, gsize *buffer_size, char **keys, char **values, GError **error) { GthBufferData *buffer_data; TIFF *tif; int cols, rows, row; glong rowsperstrip; gushort compression; int alpha; gshort predictor; gshort photometric; gshort samplesperpixel; gshort bitspersample; gushort extra_samples[1]; int rowstride; guchar *pixels, *ptr, *buf; int horizontal_dpi = 72, vertical_dpi = 72; gboolean save_resolution = FALSE; compression = COMPRESSION_DEFLATE; if (keys && *keys) { char **kiter = keys; char **viter = values; while (*kiter) { if (strcmp (*kiter, "compression") == 0) { if (*viter == NULL) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "Must specify a compression type"); return FALSE; } if (strcmp (*viter, "none") == 0) compression = COMPRESSION_NONE; else if (strcmp (*viter, "pack bits") == 0) compression = COMPRESSION_PACKBITS; else if (strcmp (*viter, "lzw") == 0) compression = COMPRESSION_LZW; else if (strcmp (*viter, "deflate") == 0) compression = COMPRESSION_DEFLATE; else if (strcmp (*viter, "jpeg") == 0) compression = COMPRESSION_JPEG; else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "Unsupported compression type passed to the TIFF saver"); return FALSE; } } else if (strcmp (*kiter, "vertical dpi") == 0) { char *endptr = NULL; vertical_dpi = strtol (*viter, &endptr, 10); save_resolution = TRUE; if (endptr == *viter) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "TIFF vertical dpi must be a value greater than 0; value '%s' could not be parsed.", *viter); return FALSE; } if (vertical_dpi < 0) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "TIFF vertical dpi must be a value greater than 0; value '%d' is not allowed.", vertical_dpi); return FALSE; } } else if (strcmp (*kiter, "horizontal dpi") == 0) { char *endptr = NULL; horizontal_dpi = strtol (*viter, &endptr, 10); save_resolution = TRUE; if (endptr == *viter) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "TIFF horizontal dpi must be a value greater than 0; value '%s' could not be parsed.", *viter); return FALSE; } if (horizontal_dpi < 0) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "TIFF horizontal dpi must be a value greater than 0; value '%d' is not allowed.", horizontal_dpi); return FALSE; } } else { g_warning ("Bad option name '%s' passed to the TIFF saver", *kiter); return FALSE; } ++kiter; ++viter; } } predictor = 0; rowsperstrip = TILE_HEIGHT; buffer_data = gth_buffer_data_new (); tif = TIFFClientOpen ("gth-tiff-writer", "w", buffer_data, tiff_read, tiff_write, tiff_seek, tiff_close, tiff_size, NULL, NULL); if (tif == NULL) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, "Couldn't allocate memory for writing TIFF file"); return FALSE; } cols = cairo_image_surface_get_width (image); rows = cairo_image_surface_get_height (image); alpha = _cairo_image_surface_get_has_alpha (image); pixels = _cairo_image_surface_flush_and_get_data (image); rowstride = cairo_image_surface_get_stride (image); predictor = 2; bitspersample = 8; photometric = PHOTOMETRIC_RGB; if (alpha) samplesperpixel = 4; else samplesperpixel = 3; /* Set TIFF parameters. */ TIFFSetField (tif, TIFFTAG_SUBFILETYPE, 0); TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, cols); TIFFSetField (tif, TIFFTAG_IMAGELENGTH, rows); TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bitspersample); TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField (tif, TIFFTAG_COMPRESSION, compression); if ((compression == COMPRESSION_LZW || compression == COMPRESSION_DEFLATE) && (predictor != 0)) { TIFFSetField (tif, TIFFTAG_PREDICTOR, predictor); } if (alpha) { extra_samples [0] = EXTRASAMPLE_ASSOCALPHA; TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, extra_samples); } TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, photometric); /*TIFFSetField (tif, TIFFTAG_DOCUMENTNAME, filename);*/ TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel); TIFFSetField (tif, TIFFTAG_ROWSPERSTRIP, rowsperstrip); TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); if (save_resolution) { TIFFSetField (tif, TIFFTAG_XRESOLUTION, (double) horizontal_dpi); TIFFSetField (tif, TIFFTAG_YRESOLUTION, (double) vertical_dpi); TIFFSetField (tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); } buf = g_try_malloc (cols * samplesperpixel * sizeof (guchar)); if (! buf) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory")); return FALSE; } /* Now write the TIFF data. */ ptr = pixels; for (row = 0; row < rows; row++) { _cairo_copy_line_as_rgba_big_endian (buf, ptr, cols, alpha); if (TIFFWriteScanline (tif, buf, row, 0) < 0) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED, "TIFF Failed a scanline write on row %d", row); return FALSE; } ptr += rowstride; } g_free (buf); TIFFFlushData (tif); TIFFClose (tif); gth_buffer_data_get (buffer_data, buffer, buffer_size); gth_buffer_data_free (buffer_data, FALSE); return TRUE; } #endif /* HAVE_LIBTIFF */ static gboolean gth_image_saver_tiff_save_image (GthImageSaver *base, GthImage *image, char **buffer, gsize *buffer_size, const char *mime_type, GCancellable *cancellable, GError **error) { #ifdef HAVE_LIBTIFF GthImageSaverTiff *self = GTH_IMAGE_SAVER_TIFF (base); char **option_keys; char **option_values; int i = -1; int i_value; cairo_surface_t *surface; gboolean result; option_keys = g_malloc (sizeof (char *) * 4); option_values = g_malloc (sizeof (char *) * 4); i++; option_keys[i] = g_strdup ("compression");; option_values[i] = g_settings_get_string (self->priv->settings, PREF_TIFF_COMPRESSION); i++; i_value = g_settings_get_int (self->priv->settings, PREF_TIFF_VERTICAL_RES); option_keys[i] = g_strdup ("vertical dpi");; option_values[i] = g_strdup_printf ("%d", i_value); i++; i_value = g_settings_get_int (self->priv->settings, PREF_TIFF_HORIZONTAL_RES); option_keys[i] = g_strdup ("horizontal dpi");; option_values[i] = g_strdup_printf ("%d", i_value); i++; option_keys[i] = NULL; option_values[i] = NULL; surface = gth_image_get_cairo_surface (image); result = _cairo_surface_write_as_tiff (surface, buffer, buffer_size, option_keys, option_values, error); cairo_surface_destroy (surface); g_strfreev (option_keys); g_strfreev (option_values); #else /* ! HAVE_LIBTIFF */ GdkPixbuf *pixbuf; char *pixbuf_type; gboolean result; pixbuf = gth_image_get_pixbuf (image); pixbuf_type = _gdk_pixbuf_get_type_from_mime_type (mime_type); result = gdk_pixbuf_save_to_bufferv (pixbuf, buffer, buffer_size, pixbuf_type, NULL, NULL, error); g_free (pixbuf_type); g_object_unref (pixbuf); #endif /* HAVE_LIBTIFF */ return result; } static void gth_image_saver_tiff_class_init (GthImageSaverTiffClass *klass) { GObjectClass *object_class; GthImageSaverClass *image_saver_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = gth_image_saver_tiff_finalize; image_saver_class = GTH_IMAGE_SAVER_CLASS (klass); image_saver_class->id = "tiff"; image_saver_class->display_name = _("TIFF"); image_saver_class->mime_type = "image/tiff"; image_saver_class->extensions = "tiff tif"; image_saver_class->get_default_ext = gth_image_saver_tiff_get_default_ext; image_saver_class->get_control = gth_image_saver_tiff_get_control; image_saver_class->save_options = gth_image_saver_tiff_save_options; image_saver_class->can_save = gth_image_saver_tiff_can_save; image_saver_class->save_image = gth_image_saver_tiff_save_image; } static void gth_image_saver_tiff_init (GthImageSaverTiff *self) { self->priv = gth_image_saver_tiff_get_instance_private (self); self->priv->settings = g_settings_new (PIX_IMAGE_SAVERS_TIFF_SCHEMA); self->priv->builder = NULL; self->priv->default_ext = NULL; }