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 <webp/encode.h>
24 #include <glib/gi18n.h>
25 #include <gthumb.h>
26 #include "gth-image-saver-webp.h"
27 #include "preferences.h"
28 
29 
30 #define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
31 
32 
33 struct _GthImageSaverWebpPrivate {
34 	GtkBuilder *builder;
35 	GSettings  *settings;
36 };
37 
38 
G_DEFINE_TYPE_WITH_CODE(GthImageSaverWebp,gth_image_saver_webp,GTH_TYPE_IMAGE_SAVER,G_ADD_PRIVATE (GthImageSaverWebp))39 G_DEFINE_TYPE_WITH_CODE (GthImageSaverWebp,
40 			 gth_image_saver_webp,
41 			 GTH_TYPE_IMAGE_SAVER,
42 			 G_ADD_PRIVATE (GthImageSaverWebp))
43 
44 
45 static void
46 gth_image_saver_webp_finalize (GObject *object)
47 {
48 	GthImageSaverWebp *self = GTH_IMAGE_SAVER_WEBP (object);
49 
50 	_g_object_unref (self->priv->builder);
51 	_g_object_unref (self->priv->settings);
52 	G_OBJECT_CLASS (gth_image_saver_webp_parent_class)->finalize (object);
53 }
54 
55 
56 static GtkWidget *
gth_image_saver_webp_get_control(GthImageSaver * base)57 gth_image_saver_webp_get_control (GthImageSaver *base)
58 {
59 	GthImageSaverWebp *self = GTH_IMAGE_SAVER_WEBP (base);
60 
61 	_g_object_unref (self->priv->builder);
62 	self->priv->builder = _gtk_builder_new_from_file ("webp-options.ui", "cairo_io");
63 
64 	gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("quality_adjustment")),
65 				  g_settings_get_int (self->priv->settings, PREF_WEBP_QUALITY));
66 	gtk_adjustment_set_value (GTK_ADJUSTMENT (GET_WIDGET ("method_adjustment")),
67 				  g_settings_get_int (self->priv->settings, PREF_WEBP_METHOD));
68 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("lossless_checkbutton")),
69 				      g_settings_get_boolean (self->priv->settings, PREF_WEBP_LOSSLESS));
70 
71 	return GET_WIDGET ("webp_options");
72 }
73 
74 
75 static void
gth_image_saver_webp_save_options(GthImageSaver * base)76 gth_image_saver_webp_save_options (GthImageSaver *base)
77 {
78 	GthImageSaverWebp *self = GTH_IMAGE_SAVER_WEBP (base);
79 
80 	g_settings_set_int (self->priv->settings, PREF_WEBP_QUALITY, (int) gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("quality_adjustment"))));
81 	g_settings_set_int (self->priv->settings, PREF_WEBP_METHOD, (int) gtk_adjustment_get_value (GTK_ADJUSTMENT (GET_WIDGET ("method_adjustment"))));
82 	g_settings_set_boolean (self->priv->settings, PREF_WEBP_LOSSLESS, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("lossless_checkbutton"))));
83 }
84 
85 
86 static gboolean
gth_image_saver_webp_can_save(GthImageSaver * self,const char * mime_type)87 gth_image_saver_webp_can_save (GthImageSaver *self,
88 			      const char    *mime_type)
89 {
90 	return g_content_type_equals (mime_type, "image/webp");
91 }
92 
93 
94 typedef struct {
95 	GError        **error;
96 	GthBufferData  *buffer_data;
97 	int             success;
98 } CairoWebpData;
99 
100 
101 static void
_cairo_webp_data_destroy(CairoWebpData * cairo_webp_data)102 _cairo_webp_data_destroy (CairoWebpData *cairo_webp_data)
103 {
104 	gth_buffer_data_free (cairo_webp_data->buffer_data, ! cairo_webp_data->success);
105 	g_free (cairo_webp_data);
106 }
107 
108 
109 static int
cairo_webp_writer_func(const uint8_t * data,size_t data_size,const WebPPicture * picture)110 cairo_webp_writer_func (const uint8_t     *data,
111 			size_t             data_size,
112 			const WebPPicture *picture)
113 {
114 	CairoWebpData *cairo_webp_data = picture->custom_ptr;
115 
116 	cairo_webp_data->success = gth_buffer_data_write (cairo_webp_data->buffer_data,
117 							  (void *) data,
118 							  data_size,
119 							  cairo_webp_data->error);
120 
121 	return cairo_webp_data->success;
122 }
123 
124 
125 static int
_WebPPictureImportCairoSurface(WebPPicture * const picture,cairo_surface_t * image)126 _WebPPictureImportCairoSurface (WebPPicture     *const picture,
127 				cairo_surface_t *image)
128 {
129 	int       stride;
130 	guchar   *src_row;
131 	uint32_t *dest_row;
132 	int       y, x, temp;
133 	guchar    r, g, b, a;
134 
135 	if (_cairo_image_surface_get_has_alpha (image))
136 		picture->colorspace |= WEBP_CSP_ALPHA_BIT;
137 	else
138 		picture->colorspace &= ~WEBP_CSP_ALPHA_BIT;
139 
140 	if (! WebPPictureAlloc (picture))
141 		return 0;
142 
143 	stride = cairo_image_surface_get_stride (image);
144 	src_row = _cairo_image_surface_flush_and_get_data (image);
145 	dest_row = picture->argb;
146 
147 	for (y= 0; y < cairo_image_surface_get_height (image); y++) {
148 		guchar *pixel = src_row;
149 
150 		for (x = 0; x < cairo_image_surface_get_width (image); x++) {
151 			CAIRO_GET_RGBA (pixel, r, g, b, a);
152 			dest_row[x] = ((a << 24) | (r << 16) | (g <<  8) | b);
153 
154 			pixel += 4;
155 		}
156 
157 		src_row += stride;
158 		dest_row += picture->argb_stride;
159 	}
160 
161 	return 1;
162 }
163 
164 
165 static gboolean
_cairo_surface_write_as_webp(cairo_surface_t * image,char ** buffer,gsize * buffer_size,char ** keys,char ** values,GError ** error)166 _cairo_surface_write_as_webp (cairo_surface_t  *image,
167 			      char            **buffer,
168 			      gsize            *buffer_size,
169 			      char            **keys,
170 			      char            **values,
171 			      GError          **error)
172 {
173 	gboolean       lossless;
174 	int            quality;
175 	int            method;
176 	WebPConfig     config;
177 	CairoWebpData *cairo_webp_data;
178 	WebPPicture    pic;
179 
180 	lossless = TRUE;
181 	quality = 75;
182 	method = 4;
183 
184 	if (keys && *keys) {
185 		char **kiter = keys;
186 		char **viter = values;
187 
188 		while (*kiter) {
189 			if (strcmp (*kiter, "lossless") == 0) {
190 				if (*viter == NULL) {
191 					g_set_error (error,
192 						     G_IO_ERROR,
193 						     G_IO_ERROR_INVALID_DATA,
194 						     "Must specify a value for the 'lossless' option");
195 					return FALSE;
196 				}
197 
198 				lossless = atoi (*viter);
199 
200 				if (lossless < 0 || lossless > 1) {
201 					g_set_error (error,
202 						     G_IO_ERROR,
203 						     G_IO_ERROR_INVALID_DATA,
204 						     "Invalid value set for the 'lossless' option of the WebP saver");
205 					return FALSE;
206 				}
207 			}
208 			else if (strcmp (*kiter, "quality") == 0) {
209 				if (*viter == NULL) {
210 					g_set_error (error,
211 						     G_IO_ERROR,
212 						     G_IO_ERROR_INVALID_DATA,
213 						     "Must specify a quality value to the WebP saver");
214 					return FALSE;
215 				}
216 
217 				quality = atoi (*viter);
218 
219 				if (quality < 0 || quality > 100) {
220 					g_set_error (error,
221 						     G_IO_ERROR,
222 						     G_IO_ERROR_INVALID_DATA,
223 						     "Unsupported quality value passed to the WebP saver");
224 					return FALSE;
225 				}
226 			}
227 			else if (strcmp (*kiter, "method") == 0) {
228 				if (*viter == NULL) {
229 					g_set_error (error,
230 						     G_IO_ERROR,
231 						     G_IO_ERROR_INVALID_DATA,
232 						     "Must specify a method value to the WebP saver");
233 					return FALSE;
234 				}
235 
236 				method = atoi (*viter);
237 
238 				if (method < 0 || method > 6) {
239 					g_set_error (error,
240 						     G_IO_ERROR,
241 						     G_IO_ERROR_INVALID_DATA,
242 						     "Unsupported method value passed to the WebP saver");
243 					return FALSE;
244 				}
245 			}
246 			else {
247 				g_warning ("Bad option name '%s' passed to the WebP saver", *kiter);
248 				return FALSE;
249 			}
250 
251 			++kiter;
252 			++viter;
253 		}
254 	}
255 
256 	if (! WebPConfigInit (&config)) {
257 		g_set_error (error,
258 			     G_IO_ERROR,
259 			     G_IO_ERROR_INVALID_DATA,
260 			     "Version error");
261 		return FALSE;
262 	}
263 
264 	config.lossless = lossless;
265 	config.quality = quality;
266 	config.method = method;
267 
268 	if (! WebPValidateConfig (&config)) {
269 		g_set_error (error,
270 			     G_IO_ERROR,
271 			     G_IO_ERROR_INVALID_DATA,
272 			     "Config error");
273 		return FALSE;
274 	}
275 
276 	cairo_webp_data = g_new0 (CairoWebpData, 1);
277 	cairo_webp_data->error = error;
278 	cairo_webp_data->buffer_data = gth_buffer_data_new ();
279 	cairo_webp_data->success = FALSE;
280 
281 	WebPPictureInit (&pic);
282 	pic.width = cairo_image_surface_get_width (image);
283 	pic.height = cairo_image_surface_get_height (image);
284 	pic.writer = cairo_webp_writer_func;
285 	pic.custom_ptr = cairo_webp_data;
286 	pic.use_argb = TRUE;
287 
288 	if (_WebPPictureImportCairoSurface (&pic, image)) {
289 		int ok = WebPEncode (&config, &pic);
290 		WebPPictureFree (&pic);
291 
292 		if (cairo_webp_data->success && ! ok) {
293 			g_set_error (cairo_webp_data->error,
294 				     G_IO_ERROR,
295 				     G_IO_ERROR_INVALID_DATA,
296 				     "Encoding error: %d", pic.error_code);
297 			cairo_webp_data->success = FALSE;
298 		}
299 	}
300 	else {
301 		g_set_error (cairo_webp_data->error,
302 			     G_IO_ERROR,
303 			     G_IO_ERROR_INVALID_DATA,
304 			     "Memory error");
305 		cairo_webp_data->success = FALSE;
306 	}
307 
308 	if (cairo_webp_data->success)
309 		gth_buffer_data_get (cairo_webp_data->buffer_data, buffer, buffer_size);
310 
311 	_cairo_webp_data_destroy (cairo_webp_data);
312 
313 	return TRUE;
314 }
315 
316 
317 static gboolean
gth_image_saver_webp_save_image(GthImageSaver * base,GthImage * image,char ** buffer,gsize * buffer_size,const char * mime_type,GCancellable * cancellable,GError ** error)318 gth_image_saver_webp_save_image (GthImageSaver   *base,
319 				 GthImage        *image,
320 				 char           **buffer,
321 				 gsize           *buffer_size,
322 				 const char      *mime_type,
323 				 GCancellable    *cancellable,
324 				 GError         **error)
325 {
326 	GthImageSaverWebp  *self = GTH_IMAGE_SAVER_WEBP (base);
327 	cairo_surface_t    *surface;
328 	char              **option_keys;
329 	char              **option_values;
330 	int                 i = -1;
331 	int                 i_value;
332 	gboolean            result;
333 
334 	option_keys = g_new (char *, 4);
335 	option_values = g_new (char *, 4);
336 
337 	i++;
338 	i_value = g_settings_get_boolean (self->priv->settings, PREF_WEBP_LOSSLESS);
339 	option_keys[i] = g_strdup ("lossless");;
340 	option_values[i] = g_strdup_printf ("%d", i_value);
341 
342 	i++;
343 	i_value = g_settings_get_int (self->priv->settings, PREF_WEBP_QUALITY);
344 	option_keys[i] = g_strdup ("quality");;
345 	option_values[i] = g_strdup_printf ("%d", i_value);
346 
347 	i++;
348 	i_value = g_settings_get_int (self->priv->settings, PREF_WEBP_METHOD);
349 	option_keys[i] = g_strdup ("method");;
350 	option_values[i] = g_strdup_printf ("%d", i_value);
351 
352 	i++;
353 	option_keys[i] = NULL;
354 	option_values[i] = NULL;
355 
356 	surface = gth_image_get_cairo_surface (image);
357 	result = _cairo_surface_write_as_webp (surface,
358 					       buffer,
359 					       buffer_size,
360 					       option_keys,
361 					       option_values,
362 					       error);
363 
364 	cairo_surface_destroy (surface);
365 	g_strfreev (option_keys);
366 	g_strfreev (option_values);
367 
368 	return result;
369 }
370 
371 
372 static void
gth_image_saver_webp_class_init(GthImageSaverWebpClass * klass)373 gth_image_saver_webp_class_init (GthImageSaverWebpClass *klass)
374 {
375 	GObjectClass       *object_class;
376 	GthImageSaverClass *image_saver_class;
377 
378 	object_class = G_OBJECT_CLASS (klass);
379 	object_class->finalize = gth_image_saver_webp_finalize;
380 
381 	image_saver_class = GTH_IMAGE_SAVER_CLASS (klass);
382 	image_saver_class->id = "webp";
383 	image_saver_class->display_name = _("WebP");
384 	image_saver_class->mime_type = "image/webp";
385 	image_saver_class->extensions = "webp";
386 	image_saver_class->get_default_ext = NULL;
387 	image_saver_class->get_control = gth_image_saver_webp_get_control;
388 	image_saver_class->save_options = gth_image_saver_webp_save_options;
389 	image_saver_class->can_save = gth_image_saver_webp_can_save;
390 	image_saver_class->save_image = gth_image_saver_webp_save_image;
391 }
392 
393 
394 static void
gth_image_saver_webp_init(GthImageSaverWebp * self)395 gth_image_saver_webp_init (GthImageSaverWebp *self)
396 {
397 	self->priv = gth_image_saver_webp_get_instance_private (self);
398 	self->priv->settings = g_settings_new (GTHUMB_IMAGE_SAVERS_WEBP_SCHEMA);
399 	self->priv->builder = NULL;
400 }
401