/* -*- 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
#include "gth-image-saver-png.h"
#include "preferences.h"
/* starting from libpng version 1.5 it is not possible
* to access inside the PNG struct directly
*/
#define PNG_SETJMP(ptr) setjmp(png_jmpbuf(ptr))
#ifdef PNG_LIBPNG_VER
#if PNG_LIBPNG_VER < 10400
#ifdef PNG_SETJMP
#undef PNG_SETJMP
#endif
#define PNG_SETJMP(ptr) setjmp(ptr->jmpbuf)
#endif
#endif
struct _GthImageSaverPngPrivate {
GtkBuilder *builder;
GSettings *settings;
};
G_DEFINE_TYPE_WITH_CODE (GthImageSaverPng,
gth_image_saver_png,
GTH_TYPE_IMAGE_SAVER,
G_ADD_PRIVATE (GthImageSaverPng))
static void
gth_image_saver_png_finalize (GObject *object)
{
GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (object);
_g_object_unref (self->priv->builder);
_g_object_unref (self->priv->settings);
G_OBJECT_CLASS (gth_image_saver_png_parent_class)->finalize (object);
}
static GtkWidget *
gth_image_saver_png_get_control (GthImageSaver *base)
{
GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (base);
_g_object_unref (self->priv->builder);
self->priv->builder = _gtk_builder_new_from_file ("png-options.ui", "cairo_io");
gtk_adjustment_set_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "png_compression_adjustment")),
g_settings_get_int (self->priv->settings, PREF_PNG_COMPRESSION_LEVEL));
return _gtk_builder_get_widget (self->priv->builder, "png_options");
}
static void
gth_image_saver_png_save_options (GthImageSaver *base)
{
GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (base);
g_settings_set_int (self->priv->settings, PREF_PNG_COMPRESSION_LEVEL, (int) gtk_adjustment_get_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "png_compression_adjustment"))));
}
static gboolean
gth_image_saver_png_can_save (GthImageSaver *self,
const char *mime_type)
{
return g_content_type_equals (mime_type, "image/png");
}
typedef struct {
GError **error;
png_struct *png_ptr;
png_info *png_info_ptr;
GthBufferData *buffer_data;
} CairoPngData;
static void
_cairo_png_data_destroy (CairoPngData *cairo_png_data)
{
png_destroy_write_struct (&cairo_png_data->png_ptr, &cairo_png_data->png_info_ptr);
gth_buffer_data_free (cairo_png_data->buffer_data, FALSE);
g_free (cairo_png_data);
}
static void
gerror_error_func (png_structp png_ptr,
png_const_charp message)
{
GError ***error_p = png_get_error_ptr (png_ptr);
GError **error = *error_p;
if (error != NULL)
*error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s", message);
}
static void
gerror_warning_func (png_structp png_ptr,
png_const_charp message)
{
/* void: we don't care about warnings */
}
static void
cairo_png_write_data_func (png_structp png_ptr,
png_bytep buffer,
png_size_t size)
{
CairoPngData *cairo_png_data;
GError *error;
cairo_png_data = png_get_io_ptr (png_ptr);
if (! gth_buffer_data_write (cairo_png_data->buffer_data, buffer, size, &error)) {
png_error (png_ptr, error->message);
g_error_free (error);
}
}
static void
cairo_png_flush_data_func (png_structp png_ptr)
{
/* we are saving in a buffer, no need to flush */
}
static gboolean
_cairo_surface_write_as_png (cairo_surface_t *image,
char **buffer,
gsize *buffer_size,
char **keys,
char **values,
GError **error)
{
volatile int compression_level;
int width, height;
gboolean alpha;
guchar *pixels, *ptr, *buf;
int rowstride;
CairoPngData *cairo_png_data;
png_color_8 sig_bit;
int bpp;
int row;
compression_level = 6;
if (keys && *keys) {
char **kiter = keys;
char **viter = values;
while (*kiter) {
if (strcmp (*kiter, "compression") == 0) {
if (*viter == NULL) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Must specify a compression level");
return FALSE;
}
compression_level = atoi (*viter);
if (compression_level < 0 || compression_level > 9) {
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"Unsupported compression level passed to the PNG saver");
return FALSE;
}
}
else {
g_warning ("Bad option name '%s' passed to the PNG 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);
cairo_png_data = g_new0 (CairoPngData, 1);
cairo_png_data->error = error;
cairo_png_data->buffer_data = gth_buffer_data_new ();
cairo_png_data->png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
&cairo_png_data->error,
gerror_error_func,
gerror_warning_func);
if (cairo_png_data->png_ptr == NULL) {
_cairo_png_data_destroy (cairo_png_data);
return FALSE;
}
cairo_png_data->png_info_ptr = png_create_info_struct (cairo_png_data->png_ptr);
if (cairo_png_data->png_info_ptr == NULL) {
_cairo_png_data_destroy (cairo_png_data);
return FALSE;
}
if (PNG_SETJMP (cairo_png_data->png_ptr)) {
_cairo_png_data_destroy (cairo_png_data);
return FALSE;
}
png_set_write_fn (cairo_png_data->png_ptr,
cairo_png_data,
cairo_png_write_data_func,
cairo_png_flush_data_func);
/* Set the image information here */
png_set_IHDR (cairo_png_data->png_ptr,
cairo_png_data->png_info_ptr,
width,
height,
8,
(alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB),
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
/* Options */
sig_bit.red = 8;
sig_bit.green = 8;
sig_bit.blue = 8;
if (alpha)
sig_bit.alpha = 8;
png_set_sBIT (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr, &sig_bit);
png_set_compression_level (cairo_png_data->png_ptr, compression_level);
/* Write the file header information. */
png_write_info (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
/* Write the image */
bpp = alpha ? 4 : 3;
buf = g_new (guchar, width * bpp);
ptr = pixels;
for (row = 0; row < height; ++row) {
_cairo_copy_line_as_rgba_big_endian (buf, ptr, width, alpha);
png_write_rows (cairo_png_data->png_ptr, &buf, 1);
ptr += rowstride;
}
g_free (buf);
png_write_end (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
gth_buffer_data_get (cairo_png_data->buffer_data, buffer, buffer_size);
_cairo_png_data_destroy (cairo_png_data);
return TRUE;
}
static gboolean
gth_image_saver_png_save_image (GthImageSaver *base,
GthImage *image,
char **buffer,
gsize *buffer_size,
const char *mime_type,
GCancellable *cancellable,
GError **error)
{
GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (base);
cairo_surface_t *surface;
char **option_keys;
char **option_values;
int i = -1;
int i_value;
gboolean result;
option_keys = g_malloc (sizeof (char *) * 2);
option_values = g_malloc (sizeof (char *) * 2);
i++;
i_value = g_settings_get_int (self->priv->settings, PREF_PNG_COMPRESSION_LEVEL);
option_keys[i] = g_strdup ("compression");;
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_png (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_png_class_init (GthImageSaverPngClass *klass)
{
GObjectClass *object_class;
GthImageSaverClass *image_saver_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gth_image_saver_png_finalize;
image_saver_class = GTH_IMAGE_SAVER_CLASS (klass);
image_saver_class->id = "png";
image_saver_class->display_name = _("PNG");
image_saver_class->mime_type = "image/png";
image_saver_class->extensions = "png";
image_saver_class->get_default_ext = NULL;
image_saver_class->get_control = gth_image_saver_png_get_control;
image_saver_class->save_options = gth_image_saver_png_save_options;
image_saver_class->can_save = gth_image_saver_png_can_save;
image_saver_class->save_image = gth_image_saver_png_save_image;
}
static void
gth_image_saver_png_init (GthImageSaverPng *self)
{
self->priv = gth_image_saver_png_get_instance_private (self);
self->priv->settings = g_settings_new (PIX_IMAGE_SAVERS_PNG_SCHEMA);
self->priv->builder = NULL;
}