1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2009 Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <config.h>
23 #include <png.h>
24 #include <glib/gi18n.h>
25 #include <gthumb.h>
26 #include "gth-image-saver-png.h"
27 #include "preferences.h"
28
29
30 /* starting from libpng version 1.5 it is not possible
31 * to access inside the PNG struct directly
32 */
33 #define PNG_SETJMP(ptr) setjmp(png_jmpbuf(ptr))
34
35 #ifdef PNG_LIBPNG_VER
36 #if PNG_LIBPNG_VER < 10400
37 #ifdef PNG_SETJMP
38 #undef PNG_SETJMP
39 #endif
40 #define PNG_SETJMP(ptr) setjmp(ptr->jmpbuf)
41 #endif
42 #endif
43
44
45 struct _GthImageSaverPngPrivate {
46 GtkBuilder *builder;
47 GSettings *settings;
48 };
49
50
G_DEFINE_TYPE_WITH_CODE(GthImageSaverPng,gth_image_saver_png,GTH_TYPE_IMAGE_SAVER,G_ADD_PRIVATE (GthImageSaverPng))51 G_DEFINE_TYPE_WITH_CODE (GthImageSaverPng,
52 gth_image_saver_png,
53 GTH_TYPE_IMAGE_SAVER,
54 G_ADD_PRIVATE (GthImageSaverPng))
55
56
57 static void
58 gth_image_saver_png_finalize (GObject *object)
59 {
60 GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (object);
61
62 _g_object_unref (self->priv->builder);
63 _g_object_unref (self->priv->settings);
64 G_OBJECT_CLASS (gth_image_saver_png_parent_class)->finalize (object);
65 }
66
67
68 static GtkWidget *
gth_image_saver_png_get_control(GthImageSaver * base)69 gth_image_saver_png_get_control (GthImageSaver *base)
70 {
71 GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (base);
72
73 _g_object_unref (self->priv->builder);
74 self->priv->builder = _gtk_builder_new_from_file ("png-options.ui", "cairo_io");
75
76 gtk_adjustment_set_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "png_compression_adjustment")),
77 g_settings_get_int (self->priv->settings, PREF_PNG_COMPRESSION_LEVEL));
78
79 return _gtk_builder_get_widget (self->priv->builder, "png_options");
80 }
81
82
83 static void
gth_image_saver_png_save_options(GthImageSaver * base)84 gth_image_saver_png_save_options (GthImageSaver *base)
85 {
86 GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (base);
87
88 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"))));
89 }
90
91
92 static gboolean
gth_image_saver_png_can_save(GthImageSaver * self,const char * mime_type)93 gth_image_saver_png_can_save (GthImageSaver *self,
94 const char *mime_type)
95 {
96 return g_content_type_equals (mime_type, "image/png");
97 }
98
99
100 typedef struct {
101 GError **error;
102 png_struct *png_ptr;
103 png_info *png_info_ptr;
104 GthBufferData *buffer_data;
105 } CairoPngData;
106
107
108 static void
_cairo_png_data_destroy(CairoPngData * cairo_png_data)109 _cairo_png_data_destroy (CairoPngData *cairo_png_data)
110 {
111 png_destroy_write_struct (&cairo_png_data->png_ptr, &cairo_png_data->png_info_ptr);
112 gth_buffer_data_free (cairo_png_data->buffer_data, FALSE);
113 g_free (cairo_png_data);
114 }
115
116
117 static void
gerror_error_func(png_structp png_ptr,png_const_charp message)118 gerror_error_func (png_structp png_ptr,
119 png_const_charp message)
120 {
121 GError ***error_p = png_get_error_ptr (png_ptr);
122 GError **error = *error_p;
123
124 if (error != NULL)
125 *error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s", message);
126 }
127
128
129 static void
gerror_warning_func(png_structp png_ptr,png_const_charp message)130 gerror_warning_func (png_structp png_ptr,
131 png_const_charp message)
132 {
133 /* void: we don't care about warnings */
134 }
135
136
137 static void
cairo_png_write_data_func(png_structp png_ptr,png_bytep buffer,png_size_t size)138 cairo_png_write_data_func (png_structp png_ptr,
139 png_bytep buffer,
140 png_size_t size)
141 {
142 CairoPngData *cairo_png_data;
143 GError *error;
144
145 cairo_png_data = png_get_io_ptr (png_ptr);
146 if (! gth_buffer_data_write (cairo_png_data->buffer_data, buffer, size, &error)) {
147 png_error (png_ptr, error->message);
148 g_error_free (error);
149 }
150 }
151
152
153 static void
cairo_png_flush_data_func(png_structp png_ptr)154 cairo_png_flush_data_func (png_structp png_ptr)
155 {
156 /* we are saving in a buffer, no need to flush */
157 }
158
159
160 static gboolean
_cairo_surface_write_as_png(cairo_surface_t * image,char ** buffer,gsize * buffer_size,char ** keys,char ** values,GError ** error)161 _cairo_surface_write_as_png (cairo_surface_t *image,
162 char **buffer,
163 gsize *buffer_size,
164 char **keys,
165 char **values,
166 GError **error)
167 {
168 int compression_level;
169 int width, height;
170 gboolean alpha;
171 guchar *pixels, *ptr, *buf;
172 int rowstride;
173 CairoPngData *cairo_png_data;
174 png_color_8 sig_bit;
175 int bpp;
176 int row;
177
178 compression_level = 6;
179
180 if (keys && *keys) {
181 char **kiter = keys;
182 char **viter = values;
183
184 while (*kiter) {
185 if (strcmp (*kiter, "compression") == 0) {
186 if (*viter == NULL) {
187 g_set_error (error,
188 G_IO_ERROR,
189 G_IO_ERROR_INVALID_DATA,
190 "Must specify a compression level");
191 return FALSE;
192 }
193
194 compression_level = atoi (*viter);
195
196 if (compression_level < 0 || compression_level > 9) {
197 g_set_error (error,
198 G_IO_ERROR,
199 G_IO_ERROR_INVALID_DATA,
200 "Unsupported compression level passed to the PNG saver");
201 return FALSE;
202 }
203 }
204 else {
205 g_warning ("Bad option name '%s' passed to the PNG saver", *kiter);
206 return FALSE;
207 }
208
209 ++kiter;
210 ++viter;
211 }
212 }
213
214 width = cairo_image_surface_get_width (image);
215 height = cairo_image_surface_get_height (image);
216 alpha = _cairo_image_surface_get_has_alpha (image);
217 pixels = _cairo_image_surface_flush_and_get_data (image);
218 rowstride = cairo_image_surface_get_stride (image);
219
220 cairo_png_data = g_new0 (CairoPngData, 1);
221 cairo_png_data->error = error;
222 cairo_png_data->buffer_data = gth_buffer_data_new ();
223
224 cairo_png_data->png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
225 &cairo_png_data->error,
226 gerror_error_func,
227 gerror_warning_func);
228 if (cairo_png_data->png_ptr == NULL) {
229 _cairo_png_data_destroy (cairo_png_data);
230 return FALSE;
231 }
232
233 cairo_png_data->png_info_ptr = png_create_info_struct (cairo_png_data->png_ptr);
234 if (cairo_png_data->png_info_ptr == NULL) {
235 _cairo_png_data_destroy (cairo_png_data);
236 return FALSE;
237 }
238
239 if (PNG_SETJMP (cairo_png_data->png_ptr)) {
240 _cairo_png_data_destroy (cairo_png_data);
241 return FALSE;
242 }
243
244 png_set_write_fn (cairo_png_data->png_ptr,
245 cairo_png_data,
246 cairo_png_write_data_func,
247 cairo_png_flush_data_func);
248
249 /* Set the image information here */
250
251 png_set_IHDR (cairo_png_data->png_ptr,
252 cairo_png_data->png_info_ptr,
253 width,
254 height,
255 8,
256 (alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB),
257 PNG_INTERLACE_NONE,
258 PNG_COMPRESSION_TYPE_BASE,
259 PNG_FILTER_TYPE_BASE);
260
261 /* Options */
262
263 sig_bit.red = 8;
264 sig_bit.green = 8;
265 sig_bit.blue = 8;
266 if (alpha)
267 sig_bit.alpha = 8;
268 png_set_sBIT (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr, &sig_bit);
269
270 png_set_compression_level (cairo_png_data->png_ptr, compression_level);
271
272 /* Write the file header information. */
273
274 png_write_info (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
275
276 /* Write the image */
277
278 bpp = alpha ? 4 : 3;
279 buf = g_new (guchar, width * bpp);
280 ptr = pixels;
281 for (row = 0; row < height; ++row) {
282 _cairo_copy_line_as_rgba_big_endian (buf, ptr, width, alpha);
283 png_write_rows (cairo_png_data->png_ptr, &buf, 1);
284
285 ptr += rowstride;
286 }
287 g_free (buf);
288
289 png_write_end (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
290 gth_buffer_data_get (cairo_png_data->buffer_data, buffer, buffer_size);
291
292 _cairo_png_data_destroy (cairo_png_data);
293
294 return TRUE;
295 }
296
297
298 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)299 gth_image_saver_png_save_image (GthImageSaver *base,
300 GthImage *image,
301 char **buffer,
302 gsize *buffer_size,
303 const char *mime_type,
304 GCancellable *cancellable,
305 GError **error)
306 {
307 GthImageSaverPng *self = GTH_IMAGE_SAVER_PNG (base);
308 cairo_surface_t *surface;
309 char **option_keys;
310 char **option_values;
311 int i = -1;
312 int i_value;
313 gboolean result;
314
315 option_keys = g_malloc (sizeof (char *) * 2);
316 option_values = g_malloc (sizeof (char *) * 2);
317
318 i++;
319 i_value = g_settings_get_int (self->priv->settings, PREF_PNG_COMPRESSION_LEVEL);
320 option_keys[i] = g_strdup ("compression");;
321 option_values[i] = g_strdup_printf ("%d", i_value);
322
323 i++;
324 option_keys[i] = NULL;
325 option_values[i] = NULL;
326
327 surface = gth_image_get_cairo_surface (image);
328 result = _cairo_surface_write_as_png (surface,
329 buffer,
330 buffer_size,
331 option_keys,
332 option_values,
333 error);
334
335 cairo_surface_destroy (surface);
336 g_strfreev (option_keys);
337 g_strfreev (option_values);
338
339 return result;
340 }
341
342
343 static void
gth_image_saver_png_class_init(GthImageSaverPngClass * klass)344 gth_image_saver_png_class_init (GthImageSaverPngClass *klass)
345 {
346 GObjectClass *object_class;
347 GthImageSaverClass *image_saver_class;
348
349 object_class = G_OBJECT_CLASS (klass);
350 object_class->finalize = gth_image_saver_png_finalize;
351
352 image_saver_class = GTH_IMAGE_SAVER_CLASS (klass);
353 image_saver_class->id = "png";
354 image_saver_class->display_name = _("PNG");
355 image_saver_class->mime_type = "image/png";
356 image_saver_class->extensions = "png";
357 image_saver_class->get_default_ext = NULL;
358 image_saver_class->get_control = gth_image_saver_png_get_control;
359 image_saver_class->save_options = gth_image_saver_png_save_options;
360 image_saver_class->can_save = gth_image_saver_png_can_save;
361 image_saver_class->save_image = gth_image_saver_png_save_image;
362 }
363
364
365 static void
gth_image_saver_png_init(GthImageSaverPng * self)366 gth_image_saver_png_init (GthImageSaverPng *self)
367 {
368 self->priv = gth_image_saver_png_get_instance_private (self);
369 self->priv->settings = g_settings_new (GTHUMB_IMAGE_SAVERS_PNG_SCHEMA);
370 self->priv->builder = NULL;
371 }
372