/* -*- 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 #include #include #include "gth-image-saver-tga.h" #include "preferences.h" struct _GthImageSaverTgaPrivate { GtkBuilder *builder; GSettings *settings; }; G_DEFINE_TYPE_WITH_CODE (GthImageSaverTga, gth_image_saver_tga, GTH_TYPE_IMAGE_SAVER, G_ADD_PRIVATE (GthImageSaverTga)) static void gth_image_saver_tga_finalize (GObject *object) { GthImageSaverTga *self = GTH_IMAGE_SAVER_TGA (object); _g_object_unref (self->priv->builder); _g_object_unref (self->priv->settings); G_OBJECT_CLASS (gth_image_saver_tga_parent_class)->finalize (object); } static GtkWidget * gth_image_saver_tga_get_control (GthImageSaver *base) { GthImageSaverTga *self = GTH_IMAGE_SAVER_TGA (base); _g_object_unref (self->priv->builder); self->priv->builder = _gtk_builder_new_from_file ("tga-options.ui", "cairo_io"); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tga_rle_compression_checkbutton")), g_settings_get_boolean (self->priv->settings, PREF_TGA_RLE_COMPRESSION)); return _gtk_builder_get_widget (self->priv->builder, "tga_options"); } static void gth_image_saver_tga_save_options (GthImageSaver *base) { GthImageSaverTga *self = GTH_IMAGE_SAVER_TGA (base); g_settings_set_boolean (self->priv->settings, PREF_TGA_RLE_COMPRESSION, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "tga_rle_compression_checkbutton")))); } static gboolean gth_image_saver_tga_can_save (GthImageSaver *self, const char *mime_type) { return g_content_type_equals (mime_type, "image/tga") || g_content_type_equals (mime_type, "image/x-tga"); } /* -- _gdk_pixbuf_save_as_tga -- */ /* TRUEVISION-XFILE magic signature string */ static guchar magic[18] = { 0x54, 0x52, 0x55, 0x45, 0x56, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x2d, 0x58, 0x46, 0x49, 0x4c, 0x45, 0x2e, 0x0 }; static gboolean rle_write (GthBufferData *buffer_data, guchar *buffer, guint width, guint bytes, GError **error) { int repeat = 0; int direct = 0; guchar *from = buffer; guint x; for (x = 1; x < width; ++x) { if (memcmp (buffer, buffer + bytes, bytes)) { /* next pixel is different */ if (repeat) { gth_buffer_data_putc (buffer_data, 128 + repeat, error); gth_buffer_data_write (buffer_data, from, bytes, error); from = buffer + bytes; /* point to first different pixel */ repeat = 0; direct = 0; } else direct += 1; } else { /* next pixel is the same */ if (direct) { gth_buffer_data_putc (buffer_data, direct - 1, error); gth_buffer_data_write (buffer_data, from, bytes * direct, error); from = buffer; /* point to first identical pixel */ direct = 0; repeat = 1; } else repeat += 1; } if (repeat == 128) { gth_buffer_data_putc (buffer_data, 255, error); gth_buffer_data_write (buffer_data, from, bytes, error); from = buffer + bytes; direct = 0; repeat = 0; } else if (direct == 128) { gth_buffer_data_putc (buffer_data, 127, error); gth_buffer_data_write (buffer_data, from, bytes * direct, error); from = buffer + bytes; direct = 0; repeat = 0; } buffer += bytes; } if (repeat > 0) { gth_buffer_data_putc (buffer_data, 128 + repeat, error); gth_buffer_data_write (buffer_data, from, bytes, error); } else { gth_buffer_data_putc (buffer_data, direct, error); gth_buffer_data_write (buffer_data, from, bytes * (direct + 1), error); } return TRUE; } static gboolean _cairo_surface_write_as_tga (cairo_surface_t *image, char **buffer, gsize *buffer_size, char **keys, char **values, GError **error) { GthBufferData *buffer_data; int out_bpp = 0; int row; guchar header[18]; guchar footer[26]; gboolean rle_compression; gboolean alpha; guchar *pixels, *ptr, *buf; int width, height; int rowstride; rle_compression = TRUE; 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) rle_compression = FALSE; else if (strcmp (*viter, "rle") == 0) rle_compression = TRUE; else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "Unsupported compression type passed to the TGA saver"); return FALSE; } } else { g_warning ("Bad option name '%s' passed to the TGA saver", *kiter); return FALSE; } ++kiter; ++viter; } } width = cairo_image_surface_get_width (image); height = 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); buffer_data = gth_buffer_data_new (); /* write the header */ header[0] = 0; /* No image identifier / description */ header[1] = 0; header[2] = rle_compression ? 10 : 2; header[3] = header[4] = header[5] = header[6] = header[7] = 0; header[8] = header[9] = 0; /* xorigin */ header[10] = header[11] = 0; /* yorigin */ header[12] = width % 256; header[13] = width / 256; header[14] = height % 256; header[15] = height / 256; if (alpha) { out_bpp = 4; header[16] = 32; /* bpp */ header[17] = 0x28; /* alpha + orientation */ } else { out_bpp = 3; header[16] = 24; /* bpp */ header[17] = 0x20; /* alpha + orientation */ } gth_buffer_data_write (buffer_data, header, sizeof (header), error); /* allocate a small buffer to convert image data */ buf = g_try_malloc (width * out_bpp * sizeof (guchar)); if (! buf) { g_set_error_literal (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, _("Insufficient memory")); return FALSE; } ptr = pixels; for (row = 0; row < height; ++row) { _cairo_copy_line_as_rgba_little_endian (buf, ptr, width, alpha); if (rle_compression) rle_write (buffer_data, buf, width, out_bpp, error); else gth_buffer_data_write (buffer_data, buf, width * out_bpp, error); ptr += rowstride; } g_free (buf); /* write the footer */ memset (footer, 0, 8); /* No extensions, no developer directory */ memcpy (footer + 8, magic, sizeof (magic)); /* magic signature */ gth_buffer_data_write (buffer_data, footer, sizeof (footer), error); gth_buffer_data_get (buffer_data, buffer, buffer_size); gth_buffer_data_free (buffer_data, FALSE); return TRUE; } static gboolean gth_image_saver_tga_save_image (GthImageSaver *base, GthImage *image, char **buffer, gsize *buffer_size, const char *mime_type, GCancellable *cancellable, GError **error) { GthImageSaverTga *self = GTH_IMAGE_SAVER_TGA (base); char **option_keys; char **option_values; int i = -1; cairo_surface_t *surface; gboolean result; option_keys = g_malloc (sizeof (char *) * 2); option_values = g_malloc (sizeof (char *) * 2); i++; option_keys[i] = g_strdup ("compression"); option_values[i] = g_strdup (g_settings_get_boolean (self->priv->settings, PREF_TGA_RLE_COMPRESSION) ? "rle" : "none"); i++; option_keys[i] = NULL; option_values[i] = NULL; surface = gth_image_get_cairo_surface (image); result = _cairo_surface_write_as_tga (surface, buffer, buffer_size, option_keys, option_values, error); cairo_surface_destroy (surface); g_strfreev (option_keys); g_strfreev (option_values); return result; } static void gth_image_saver_tga_class_init (GthImageSaverTgaClass *klass) { GObjectClass *object_class; GthImageSaverClass *image_saver_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = gth_image_saver_tga_finalize; image_saver_class = GTH_IMAGE_SAVER_CLASS (klass); image_saver_class->id = "tga"; image_saver_class->display_name = _("TGA"); image_saver_class->mime_type = "image/x-tga"; image_saver_class->extensions = "tga"; image_saver_class->get_default_ext = NULL; image_saver_class->get_control = gth_image_saver_tga_get_control; image_saver_class->save_options = gth_image_saver_tga_save_options; image_saver_class->can_save = gth_image_saver_tga_can_save; image_saver_class->save_image = gth_image_saver_tga_save_image; } static void gth_image_saver_tga_init (GthImageSaverTga *self) { self->priv = gth_image_saver_tga_get_instance_private (self); self->priv->settings = g_settings_new (PIX_IMAGE_SAVERS_TGA_SCHEMA); self->priv->builder = NULL; }