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