1 /*
2  * go-image.c: Image formats
3  *
4  * Copyright (C) 2004, 2005 Jody Goldberg (jody@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) version 3.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  * USA
20  */
21 
22 #include <goffice/goffice-config.h>
23 #include <goffice/goffice.h>
24 #include <string.h>
25 #include <gsf/gsf-utils.h>
26 #include <gsf/gsf-impl-utils.h>
27 #include <glib/gi18n-lib.h>
28 #include <librsvg/rsvg.h>
29 
30 /**
31  * GOImageFormat:
32  * @GO_IMAGE_FORMAT_SVG: SVG.
33  * @GO_IMAGE_FORMAT_PNG: PNG.
34  * @GO_IMAGE_FORMAT_JPG: JPEG.
35  * @GO_IMAGE_FORMAT_PDF: PDF.
36  * @GO_IMAGE_FORMAT_PS: PostScript.
37  * @GO_IMAGE_FORMAT_EMF: Enhanced Metafile.
38  * @GO_IMAGE_FORMAT_WMF: Windows Metafile.
39  * @GO_IMAGE_FORMAT_EPS: Encapsulated Postscript.
40  * @GO_IMAGE_FORMAT_UNKNOWN: unknown.
41  **/
42 
43 /**
44  * GOImageFormatInfo:
45  * @format: #GOImageFormat.
46  * @name: image type name.
47  * @desc: image type description.
48  * @ext: file extension.
49  * @has_pixbuf_saver: can be saved as pixbuf.
50  * @is_dpi_useful: depends upon device resolution.
51  * @alpha_support: supports transparency.
52  **/
53 
54 /**
55  * GOImageClass:
56  * @parent_klass: parent class.
57  * @get_pixbuf: gets a pixbuf with the same size as the image.
58  * @get_scaled_pixbuf: gets a scaled pixbuf respecting the original aspect ratio.
59  * @save: saves the image.
60  * @load_attr: loads the images properties.
61  * @load_data: loads the image data.
62  * @draw: draws the image.
63  * @differ: returns %TRUE if the two images are different.
64  **/
65 
66 static GOImageFormatInfo *pixbuf_image_format_infos = NULL;
67 static GHashTable *pixbuf_mimes = NULL;
68 static unsigned pixbuf_format_nbr = 0;
69 static gboolean pixbuf_format_done = FALSE;
70 
71 #define PIXBUF_IMAGE_FORMAT_OFFSET (1+GO_IMAGE_FORMAT_UNKNOWN)
72 
73 static void go_image_build_pixbuf_format_infos (void);
74 
75 /**
76  * go_mime_to_image_format:
77  * @mime_type: a mime type string
78  *
79  * returns: file extension for the given mime type.
80  **/
81 
82 char *
go_mime_to_image_format(char const * mime_type)83 go_mime_to_image_format (char const *mime_type)
84 {
85  	guint i;
86 	const char* exceptions[] = {
87 		"image/svg", "svg",
88 		"image/svg+xml", "svg",
89 		"image/svg-xml", "svg",
90 		"image/vnd.adobe.svg+xml", "svg",
91 		"text/xml-svg", "svg",
92 		"image/wmf", "wmf",
93 		"image/emf", "emf",
94 		"application/pdf", "pdf",
95 		"application/postscript", "ps",
96 		"image/x-eps", "eps",
97 	};
98 
99 	for (i = 0; i < G_N_ELEMENTS (exceptions); i += 2)
100 		if (strcmp (mime_type, exceptions[i]) == 0)
101 			return g_strdup (exceptions[i + 1]);
102 
103 	go_image_build_pixbuf_format_infos ();
104 
105 	return g_strdup (g_hash_table_lookup (pixbuf_mimes, mime_type));
106 }
107 
108 /**
109  * go_image_format_to_mime:
110  * @format: a file extension string
111  *
112  * returns: corresponding mime type.
113  **/
114 
115 char *
go_image_format_to_mime(char const * format)116 go_image_format_to_mime (char const *format)
117 {
118 	char *ret = NULL;
119  	guint i;
120 	GSList *ptr, *pixbuf_fmts;
121 	static const char* const formats[] = {
122 		"svg", "image/svg,image/svg+xml",
123 		"wmf", "image/x-wmf",
124 		"emf", "image/x-emf",
125 		"pdf", "application/pdf",
126 		"ps", "application/postscript",
127 		"eps", "image/x-eps",
128 	};
129 
130 	if (format == NULL)
131 		return NULL;
132 
133 	for (i = 0; i < G_N_ELEMENTS (formats); i += 2)
134 		if (strcmp (format, formats[i]) == 0)
135 			return g_strdup (formats[i + 1]);
136 
137 	/* Not a format we have special knowledge about - ask gdk-pixbuf */
138 	pixbuf_fmts = gdk_pixbuf_get_formats ();
139 	for (ptr = pixbuf_fmts; ptr != NULL; ptr = ptr->next) {
140 		GdkPixbufFormat *pfmt = ptr->data;
141 		gchar *name = gdk_pixbuf_format_get_name (pfmt);
142 		int cmp = strcmp (format, name);
143 		g_free (name);
144 		if (cmp == 0) {
145 			gchar **mimes =
146 				gdk_pixbuf_format_get_mime_types (pfmt);
147 			ret = g_strjoinv (",", mimes);
148 			g_strfreev (mimes);
149 			break;
150 		}
151 	}
152 	g_slist_free (pixbuf_fmts);
153 
154 	return ret;
155 }
156 
157 static GOImageFormatInfo const image_format_infos[GO_IMAGE_FORMAT_UNKNOWN] = {
158 	{GO_IMAGE_FORMAT_SVG, (char *) "svg",  (char *) N_("SVG (vector graphics)"),
159 	 (char *) "svg", FALSE, FALSE, TRUE},
160 	{GO_IMAGE_FORMAT_PNG, (char *) "png",  (char *) N_("PNG (raster graphics)"),
161 	 (char *) "png", TRUE,  TRUE, TRUE},
162 	{GO_IMAGE_FORMAT_JPG, (char *) "jpeg", (char *) N_("JPEG (photograph)"),
163 	 (char *) "jpg", TRUE,  TRUE, FALSE},
164 	{GO_IMAGE_FORMAT_PDF, (char *) "pdf",  (char *) N_("PDF (portable document format)"),
165 	 (char *) "pdf", FALSE, FALSE, TRUE},
166 	{GO_IMAGE_FORMAT_PS,  (char *) "ps",   (char *) N_("PS (postscript)"),
167 	 (char *) "ps",  FALSE, TRUE, TRUE},
168 	{GO_IMAGE_FORMAT_EMF, (char *) "emf",  (char *) N_("EMF (extended metafile)"),
169 	 (char *) "emf", FALSE, FALSE, TRUE},
170 	{GO_IMAGE_FORMAT_WMF, (char *) "wmf",  (char *) N_("WMF (windows metafile)"),
171 	 (char *) "wmf", FALSE, FALSE, TRUE},
172 	{GO_IMAGE_FORMAT_EPS,  (char *) "eps",   (char *) N_("EPS (encapsulated postscript)"),
173 	 (char *) "eps",  FALSE, TRUE, TRUE},
174 };
175 
176 /* Making GOFormatInfo as a boxed type, as they are static, ref and unref don't
177  * need to do anything */
178 static GOImageFormatInfo *
go_image_format_info_ref(GOImageFormatInfo * info)179 go_image_format_info_ref (GOImageFormatInfo *info)
180 {
181 	return info;
182 }
183 
184 static void
go_image_format_info_unref(GOImageFormatInfo const * info)185 go_image_format_info_unref (GOImageFormatInfo const *info)
186 {
187 }
188 
189 GType
go_image_format_info_get_type(void)190 go_image_format_info_get_type (void)
191 {
192 	static GType t = 0;
193 
194 	if (t == 0)
195 		t = g_boxed_type_register_static ("GOImageFormatInfo",
196 			 (GBoxedCopyFunc)go_image_format_info_ref,
197 			 (GBoxedFreeFunc)go_image_format_info_unref);
198 	return t;
199 }
200 
201 static void
go_image_build_pixbuf_format_infos(void)202 go_image_build_pixbuf_format_infos (void)
203 {
204 	GdkPixbufFormat *fmt;
205 	GSList *l, *pixbuf_fmts;
206 	GOImageFormatInfo *format_info;
207 	gchar **exts, **mimes;
208 	unsigned i, j;
209 
210 	if (pixbuf_format_done)
211 		return;
212 
213 	pixbuf_fmts = gdk_pixbuf_get_formats ();
214 	pixbuf_format_nbr = g_slist_length (pixbuf_fmts);
215 
216 	if (pixbuf_format_nbr > 0) {
217 		pixbuf_image_format_infos = g_new (GOImageFormatInfo, pixbuf_format_nbr);
218 		pixbuf_mimes = g_hash_table_new_full
219 			(g_str_hash, g_str_equal, g_free, g_free);
220 
221 		for (l = pixbuf_fmts, i = 1, format_info = pixbuf_image_format_infos;
222 		     l != NULL;
223 		     l = l->next, i++, format_info++) {
224 			fmt = (GdkPixbufFormat *)l->data;
225 
226 			format_info->format = GO_IMAGE_FORMAT_UNKNOWN + i;
227 			format_info->name = gdk_pixbuf_format_get_name (fmt);
228 			format_info->desc = gdk_pixbuf_format_get_description (fmt);
229 			exts = gdk_pixbuf_format_get_extensions (fmt);
230 			format_info->ext = g_strdup (exts[0]);
231 			if (format_info->ext == NULL)
232 				format_info->ext = format_info->name;
233 			g_strfreev (exts);
234 			format_info->has_pixbuf_saver = gdk_pixbuf_format_is_writable (fmt);
235 			format_info->is_dpi_useful = FALSE;
236 			format_info->alpha_support = FALSE;
237 
238 			mimes = gdk_pixbuf_format_get_mime_types (fmt);
239 			for (j = 0; mimes[j]; j++) {
240 				g_hash_table_insert
241 					(pixbuf_mimes,
242 					 g_strdup((gpointer) mimes[j]),
243 					 g_strdup((gpointer) format_info->name));
244 			}
245 			g_strfreev (mimes);
246 		}
247 	}
248 
249 	g_slist_free (pixbuf_fmts);
250 	pixbuf_format_done = TRUE;
251 }
252 
253 /**
254  * go_image_get_format_info:
255  * @format: a #GOImageFormat
256  *
257  * Retrieves information associated to @format.
258  *
259  * Returns: a #GOImageFormatInfo struct.
260  **/
261 
262 GOImageFormatInfo const *
go_image_get_format_info(GOImageFormat format)263 go_image_get_format_info (GOImageFormat format)
264 {
265 	if (format == GO_IMAGE_FORMAT_UNKNOWN)
266 		return NULL;
267 	if (format > GO_IMAGE_FORMAT_UNKNOWN)
268 		go_image_build_pixbuf_format_infos ();
269 
270 	g_return_val_if_fail (format >= 0 &&
271 			      format <= GO_IMAGE_FORMAT_UNKNOWN + pixbuf_format_nbr, NULL);
272 	if (format < GO_IMAGE_FORMAT_UNKNOWN)
273 		return &image_format_infos[format];
274 
275 	return &pixbuf_image_format_infos[format - PIXBUF_IMAGE_FORMAT_OFFSET];
276 }
277 
278 /**
279  * go_image_get_format_from_name:
280  * @name: a string
281  *
282  * returns: corresponding #GOImageFormat.
283  **/
284 
285 GOImageFormat
go_image_get_format_from_name(char const * name)286 go_image_get_format_from_name (char const *name)
287 {
288 	unsigned i;
289 
290 	if (name == NULL || strcmp (name, "unknown") == 0)
291 		return GO_IMAGE_FORMAT_UNKNOWN;
292 
293 	go_image_build_pixbuf_format_infos ();
294 
295 	for (i = 0; i < GO_IMAGE_FORMAT_UNKNOWN; i++) {
296 		if (strcmp (name, image_format_infos[i].name) == 0)
297 			return image_format_infos[i].format;
298 	}
299 
300 	for (i = 0; i < pixbuf_format_nbr; i++) {
301 		if (strcmp (name, pixbuf_image_format_infos[i].name) == 0)
302 			return pixbuf_image_format_infos[i].format;
303 	}
304 
305 	g_warning ("[GOImage::get_format_from_name] Unknown format name (%s)", name);
306 	return GO_IMAGE_FORMAT_UNKNOWN;
307 }
308 
309 /**
310  * go_image_get_formats_with_pixbuf_saver:
311  *
312  * Returns: (element-type GOImageFormat) (transfer container): a list of #GOImageFormat that can be created
313  * from a pixbuf.
314  **/
315 
316 GSList *
go_image_get_formats_with_pixbuf_saver(void)317 go_image_get_formats_with_pixbuf_saver (void)
318 {
319 	GSList *list = NULL;
320 	unsigned i;
321 
322 	for (i = 0; i < GO_IMAGE_FORMAT_UNKNOWN; i++)
323 		if (image_format_infos[i].has_pixbuf_saver)
324 			list = g_slist_prepend (list, GUINT_TO_POINTER (i));
325 
326 	/* TODO: before enabling this code, we must remove duplicate in pixbuf_image_format_infos */
327 #if 0
328 	go_image_build_pixbuf_format_infos ();
329 
330 	for (i = 0; i < pixbuf_format_nbr; i++) {
331 		if (pixbuf_image_format_infos[i].has_pixbuf_saver)
332 			list = g_slist_prepend (list, GUINT_TO_POINTER (i + PIXBUF_IMAGE_FORMAT_OFFSET));
333 	}
334 #endif
335 
336 	return list;
337 }
338 
339 /*********************************
340  * GOImage object implementation *
341  *********************************/
342 
343 static GObjectClass *parent_klass;
344 
345 enum {
346 	IMAGE_PROP_0,
347 	IMAGE_PROP_WIDTH,
348 	IMAGE_PROP_HEIGHT
349 };
350 
351 static void
go_image_set_property(GObject * obj,guint param_id,GValue const * value,GParamSpec * pspec)352 go_image_set_property (GObject *obj, guint param_id,
353 		       GValue const *value, GParamSpec *pspec)
354 {
355 	GOImage *image = GO_IMAGE (obj);
356 	gboolean size_changed = FALSE;
357 	guint n;
358 
359 	switch (param_id) {
360 	case IMAGE_PROP_WIDTH:
361 		n = g_value_get_uint (value);
362 		if (n != image->width) {
363 			image->width = n;
364 			size_changed = TRUE;
365 		}
366 		break;
367 	case IMAGE_PROP_HEIGHT:
368 		n = g_value_get_uint (value);
369 		if (n != image->height) {
370 			image->height = n;
371 			size_changed = TRUE;
372 		}
373 		break;
374 
375 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
376 		return; /* NOTE : RETURN */
377 	}
378 
379 	if (size_changed) {
380 		g_free (image->data);
381 		image->data = NULL;
382 	}
383 }
384 
385 static void
go_image_get_property(GObject * obj,guint param_id,GValue * value,GParamSpec * pspec)386 go_image_get_property (GObject *obj, guint param_id,
387 		       GValue *value, GParamSpec *pspec)
388 {
389 	GOImage *image = GO_IMAGE (obj);
390 
391 	switch (param_id) {
392 	case IMAGE_PROP_WIDTH:
393 		g_value_set_uint (value, image->width);
394 		break;
395 	case IMAGE_PROP_HEIGHT:
396 		g_value_set_uint (value, image->height);
397 		break;
398 
399 	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
400 		return; /* NOTE : RETURN */
401 	}
402 }
403 
404 static void
go_image_finalize(GObject * obj)405 go_image_finalize (GObject *obj)
406 {
407 	GOImage *image = GO_IMAGE (obj);
408 	g_free (image->data);
409 	if (image->thumbnail)
410 		g_object_unref (image->thumbnail);
411 	if (image->pixbuf)
412 		g_object_unref (image->pixbuf);
413 	g_free (image->name);
414 	(parent_klass->finalize) (obj);
415 }
416 
417 /* default implementation for unsupported images */
418 static void
go_image_draw_fb(GOImage * image,cairo_t * cr)419 go_image_draw_fb (GOImage *image, cairo_t *cr)
420 {
421 #ifdef GOFFICE_WITH_GTK
422 	if (image->pixbuf) {
423 		cairo_rectangle (cr, 0., 0., image->width, image->height);
424 		gdk_cairo_set_source_pixbuf (cr, image->pixbuf, 0, 0);
425 		cairo_fill (cr);
426 	} else {
427 		GdkPixbuf *placeholder = gtk_icon_theme_load_icon
428 			(gtk_icon_theme_get_default (),
429 			 "unknown_image", 100, 0, NULL);
430 		double dx, dy;
431 		int n;
432 		if (placeholder == NULL)
433 			return;
434 		n = go_fake_floor (image->width / gdk_pixbuf_get_width (placeholder));
435 		dx = (image->width - n * gdk_pixbuf_get_width (placeholder)) / 2.;
436 		n = go_fake_floor (image->height / gdk_pixbuf_get_height (placeholder));
437 		dy = (image->height - n * gdk_pixbuf_get_height (placeholder)) / 2.;
438 		cairo_save (cr);
439 		cairo_rectangle (cr, 0., 0., image->width, image->height);
440 		cairo_clip (cr);
441 		cairo_rectangle (cr, -dx, -dy, image->width + dx, image->height + dy);
442 		gdk_cairo_set_source_pixbuf (cr, placeholder, 0, 0);
443 		cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
444 		cairo_fill (cr);
445 		cairo_restore (cr);
446 		g_object_unref (placeholder);
447 	}
448 #endif
449 }
450 
451 static GdkPixbuf *
go_image_get_pixbuf_fb(GOImage * image)452 go_image_get_pixbuf_fb (GOImage *image)
453 {
454 #ifdef GOFFICE_WITH_GTK
455 	cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
456 		                                                    image->width,
457 		                                                    image->height);
458 	cairo_t *cr = cairo_create (surface);
459 	GdkPixbuf *ret, *placeholder = gtk_icon_theme_load_icon
460 		(gtk_icon_theme_get_default (),
461 		 "unknown_image", 100, 0, NULL);
462 	double dx, dy;
463 	int n;
464 	if (placeholder == NULL)
465 		return NULL;
466 	n = go_fake_floor (image->width / gdk_pixbuf_get_width (placeholder));
467 	dx = (image->width - n * gdk_pixbuf_get_width (placeholder)) / 2.;
468 	n = go_fake_floor (image->height / gdk_pixbuf_get_height (placeholder));
469 	dy = (image->height - n * gdk_pixbuf_get_height (placeholder)) / 2.;
470 	cairo_rectangle (cr, 0., 0., image->width, image->height);
471 	cairo_clip (cr);
472 	cairo_rectangle (cr, -dx, -dy, image->width + dx, image->height + dy);
473 	gdk_cairo_set_source_pixbuf (cr, placeholder, 0, 0);
474 	cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
475 	cairo_fill (cr);
476 	cairo_destroy (cr);
477 	ret = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, image->width, image->height);
478 	if (cairo_image_surface_get_stride (surface) != gdk_pixbuf_get_rowstride (ret)) {
479 		g_object_unref (ret);
480 		ret = NULL;
481 	} else
482 		go_cairo_convert_data_from_pixbuf (gdk_pixbuf_get_pixels (ret),
483 			                               cairo_image_surface_get_data (surface),
484 			                               image->width, image->height,
485 			                               gdk_pixbuf_get_rowstride (ret));
486 	cairo_surface_destroy (surface);
487 	return ret;
488 #else
489 	return NULL;
490 #endif
491 }
492 
493 static GdkPixbuf *
go_image_get_scaled_pixbuf_fb(GOImage * image,int width,int height)494 go_image_get_scaled_pixbuf_fb (GOImage *image, int width, int height)
495 {
496 	if (image->pixbuf == NULL)
497 		image->pixbuf = go_image_get_pixbuf_fb (image);
498 	return gdk_pixbuf_scale_simple (image->pixbuf, width, height, GDK_INTERP_BILINEAR);
499 }
500 
501 static void
go_image_class_init(GOImageClass * klass)502 go_image_class_init (GOImageClass *klass)
503 {
504 	GObjectClass *obj_klass = (GObjectClass *) klass;
505 	obj_klass->finalize = go_image_finalize;
506 	obj_klass->set_property = go_image_set_property;
507 	obj_klass->get_property = go_image_get_property;
508 	parent_klass = g_type_class_peek_parent (klass);
509 	g_object_class_install_property (obj_klass, IMAGE_PROP_WIDTH,
510 					 g_param_spec_uint ("width", _("Width"),
511 							    _("Image width in pixels"),
512 							    0, G_MAXUINT16, 0, G_PARAM_READWRITE));
513 	g_object_class_install_property (obj_klass, IMAGE_PROP_HEIGHT,
514 					 g_param_spec_uint ("height", _("Height"),
515 							    _("Image height in pixels"),
516 							    0, G_MAXUINT16, 0, G_PARAM_READWRITE));
517 
518 	klass->draw = go_image_draw_fb;
519 	klass->get_pixbuf = go_image_get_pixbuf_fb;
520 	klass->get_scaled_pixbuf = go_image_get_scaled_pixbuf_fb;
521 }
522 
GSF_CLASS_ABSTRACT(GOImage,go_image,go_image_class_init,NULL,G_TYPE_OBJECT)523 GSF_CLASS_ABSTRACT (GOImage, go_image,
524 		go_image_class_init, NULL,
525 		G_TYPE_OBJECT)
526 
527 void
528 go_image_draw (GOImage *image, cairo_t *cr)
529 {
530 	g_return_if_fail (GO_IS_IMAGE (image));
531 	((GOImageClass *) G_OBJECT_GET_CLASS (image))->draw (image, cr);
532 }
533 
534 #define GO_THUMBNAIL_SIZE 64
535 
536 /**
537  * go_image_get_thumbnail:
538  * @image: #GOImage
539  *
540  * Generates a thumbnail for @image if not already done and returns it, adding
541  a reference. The pixbuf is scaled so that its width and height are not larger
542  * than 64 pixels, and preserving the aspect ratio.
543  * Returns: (transfer full): the thumbnail for @image
544  **/
545 GdkPixbuf const *
go_image_get_thumbnail(GOImage * image)546 go_image_get_thumbnail (GOImage *image)
547 {
548 	g_return_val_if_fail (GO_IS_IMAGE (image), NULL);
549 	if (image->thumbnail == NULL)
550 		image->thumbnail = go_image_get_scaled_pixbuf (image, GO_THUMBNAIL_SIZE, GO_THUMBNAIL_SIZE);
551 	return image->thumbnail;
552 }
553 
554 /**
555  * go_image_get_pixbuf:
556  * @image: #GOImage
557  *
558  * Builds a pixbuf from the image if not already done and returns it, adding
559  * a reference.
560  * Returns: (transfer full): the pixbuf for @image
561  **/
562 GdkPixbuf *
go_image_get_pixbuf(GOImage * image)563 go_image_get_pixbuf (GOImage *image)
564 {
565 	g_return_val_if_fail (GO_IS_IMAGE (image), NULL);
566 	if (image->pixbuf == NULL)
567 		image->pixbuf = ((GOImageClass *) G_OBJECT_GET_CLASS (image))->get_pixbuf (image);
568 	return g_object_ref (image->pixbuf);
569 }
570 
571 /**
572  * go_image_get_scaled_pixbuf:
573  * @image: #GOImage
574  * @width: the new pixbuf width
575  * @height: the new pixbuf height
576  *
577  * Builds a scaled pixbuf from the image and returns it. The caller needs to
578  * unref it. The pixbuf is scaled so that its width and height are not larger
579  * than, respectively, @width and @height, and preserving the aspect ratio.
580  * Returns: (transfer full): the scaled pixbuf for @image
581  **/
582 GdkPixbuf *
go_image_get_scaled_pixbuf(GOImage * image,int width,int height)583 go_image_get_scaled_pixbuf (GOImage *image, int width, int height)
584 {
585 	g_return_val_if_fail (GO_IS_IMAGE (image), NULL);
586 	if (image->width > width || image->height > height) {
587 		if (image->width * height > image->height * width) {
588 			height = width * image->height / image->width;
589 		} else {
590 			width = height * image->width / image->height;
591 		}
592 	} else
593 		return ((GOImageClass *) G_OBJECT_GET_CLASS (image))->get_pixbuf (image);
594 	return ((GOImageClass *) G_OBJECT_GET_CLASS (image))->get_scaled_pixbuf (image, width, height);
595 }
596 
597 GOImage *
go_image_new_from_file(char const * filename,GError ** error)598 go_image_new_from_file (char const *filename, GError **error)
599 {
600 	char *mime, *name;
601 	GOImageFormat format;
602 
603 	if (!filename) {
604 		return NULL;
605 	}
606 	mime = go_get_mime_type (filename);
607 	if (!mime) {
608 		return NULL;
609 	}
610 	name = go_mime_to_image_format (mime);
611 	g_free (mime);
612 	if (!name) {
613 		return NULL;
614 	}
615 	format = go_image_get_format_from_name (name);
616 	g_free (name);
617 	switch (format) {
618 	case GO_IMAGE_FORMAT_SVG:
619 		return go_svg_new_from_file (filename, error);
620 	case GO_IMAGE_FORMAT_EMF:
621 	case GO_IMAGE_FORMAT_WMF:
622 		return go_emf_new_from_file (filename, error);
623 	case GO_IMAGE_FORMAT_EPS:
624 		return go_spectre_new_from_file (filename, error);
625 	case GO_IMAGE_FORMAT_PS: /* we might support ps with libspectre */
626 	case GO_IMAGE_FORMAT_PDF:
627 	case GO_IMAGE_FORMAT_UNKNOWN:
628 		break;
629 	default: {
630 		GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file (filename, error);
631 		if (pixbuf) {
632 			GOImage *image = go_pixbuf_new_from_pixbuf (pixbuf);
633 			g_object_unref (pixbuf);
634 			return image;
635 		}
636 	}
637 	}
638 	return NULL;
639 }
640 
641 GOImage	*
go_image_new_from_data(char const * type,guint8 const * data,gsize length,char ** format,GError ** error)642 go_image_new_from_data (char const *type, guint8 const *data, gsize length, char **format, GError **error)
643 {
644 	char *real_type = NULL;
645 	GOImage *image = NULL;
646 	if (type == NULL || *type == 0) {
647 		char *mime_type = go_get_mime_type_for_data (data, length);
648 		real_type = go_mime_to_image_format (mime_type);
649 		g_free (mime_type);
650 		type = real_type;
651 	}
652 	if (type == NULL) {
653 		g_warning ("unrecognized image format");
654 		return NULL;
655 	}
656 	if (data == NULL || length == 0) {
657 		image = NULL;
658 		type = "unknown";
659 	} else if (!strcmp (type, "svg")) {
660 		image = go_svg_new_from_data (data, length, error);
661 	} else if (!strcmp (type, "emf") || !strcmp (type, "wmf")) {
662 		image = go_emf_new_from_data (data, length, error);
663 	} else if (!strcmp (type, "eps")) {
664 		image = go_spectre_new_from_data (data, length, error);
665 	} else {
666 		image = go_pixbuf_new_from_data (type, data, length, error);
667 	}
668 	if (image == NULL) {
669 #ifdef GOFFICE_WITH_GTK
670 		GdkScreen *screen = gdk_screen_get_default ();
671 		GtkIconTheme *theme = screen
672 			? gtk_icon_theme_get_default()
673 			: gtk_icon_theme_new();
674 		GdkPixbuf *placeholder;
675 		const char *icon_name;
676 
677 		if (theme) {
678 			if (gtk_icon_theme_has_icon (theme, "unknown_image"))
679 				icon_name = "unknown_image";
680 			else if (gtk_icon_theme_has_icon (theme, "unknown"))
681 				icon_name = "unknown";
682 			else
683 				icon_name = gtk_icon_theme_get_example_icon_name (theme);
684 
685 			placeholder = gtk_icon_theme_load_icon (theme, icon_name, 100, 0, NULL);
686 			image = go_pixbuf_new_from_pixbuf (placeholder);
687 			g_object_unref (placeholder);
688 
689 			if (!screen)
690 				g_object_unref (theme);
691 		}
692 #endif
693 	}
694 	if (format)
695 		*format = g_strdup (type);
696 	g_free (real_type);
697 	return image;
698 }
699 
700 GOImage *
go_image_new_for_format(char const * format)701 go_image_new_for_format (char const *format)
702 {
703 	GType gtype = go_image_type_for_format (format);
704 	return (gtype > 0)? g_object_new (gtype, NULL): NULL;
705 }
706 
707 GType
go_image_type_for_format(char const * format)708 go_image_type_for_format (char const *format)
709 {
710 	g_return_val_if_fail (format && *format, 0);
711 	if (!strcmp (format, "svg"))
712 		return GO_TYPE_SVG;
713 	if (!strcmp (format, "emf") || !strcmp (format, "wmf"))
714 		return GO_TYPE_EMF;
715 	if (!strcmp (format, "eps"))
716 		return GO_TYPE_SPECTRE;
717 	if (go_image_get_format_from_name (format) != GO_IMAGE_FORMAT_UNKNOWN)
718 		return GO_TYPE_PIXBUF;
719 	return 0;
720 }
721 
722 guint8 const *
go_image_get_data(GOImage * image,gsize * length)723 go_image_get_data (GOImage *image, gsize *length)
724 {
725 	g_return_val_if_fail (image, NULL);
726 	if (length)
727 		*length = image->data_length;
728 	return image->data;
729 }
730 
731 void
go_image_set_name(GOImage * image,char const * name)732 go_image_set_name (GOImage *image, char const *name)
733 {
734 	g_return_if_fail (GO_IS_IMAGE (image));
735 	g_free (image->name);
736 	image->name = (name)? g_strdup (name): NULL;
737 }
738 
739 char const *
go_image_get_name(GOImage const * image)740 go_image_get_name (GOImage const *image)
741 {
742 	g_return_val_if_fail (GO_IS_IMAGE (image), NULL);
743 	return image->name;
744 }
745 
746 gboolean
go_image_differ(GOImage * first,GOImage * second)747 go_image_differ (GOImage *first, GOImage *second)
748 {
749 	g_return_val_if_fail (GO_IS_IMAGE (first), FALSE);
750 	g_return_val_if_fail (GO_IS_IMAGE (second), FALSE);
751 	if (G_OBJECT_TYPE (first) != G_OBJECT_TYPE (second))
752 		return TRUE;
753 	if (first->width != second->width || first->height != second->height)
754 		return TRUE;
755 	return ((GOImageClass *) G_OBJECT_GET_CLASS (first))->differ (first, second);
756 }
757 
758 void
go_image_save(GOImage * image,GsfXMLOut * output)759 go_image_save (GOImage *image, GsfXMLOut *output)
760 {
761 	g_return_if_fail (GO_IS_IMAGE (image) && image->name);
762 	gsf_xml_out_start_element (output, "GOImage");
763 	gsf_xml_out_add_cstr (output, "name", image->name);
764 	gsf_xml_out_add_cstr (output, "type", G_OBJECT_TYPE_NAME (image));
765 	gsf_xml_out_add_int (output, "width", image->width);
766 	gsf_xml_out_add_int (output, "height", image->height);
767 	((GOImageClass *) G_OBJECT_GET_CLASS (image))->save (image, output);
768 	gsf_xml_out_end_element (output);
769 }
770 
771 void
go_image_load_attrs(GOImage * image,GsfXMLIn * xin,xmlChar const ** attrs)772 go_image_load_attrs (GOImage *image, GsfXMLIn *xin, xmlChar const **attrs)
773 {
774 	xmlChar const **attr;
775 	g_return_if_fail (image != NULL);
776 	for (attr = attrs; attr != NULL && attr[0] && attr[1] ; attr += 2)
777 		if (0 == strcmp (attr[0], "width"))
778 			image->width = strtol (attr[1], NULL, 10);
779 		else if (0 == strcmp (attr[0], "height"))
780 			image->height= strtol (attr[1], NULL, 10);
781 		else
782 			((GOImageClass *) G_OBJECT_GET_CLASS (image))->load_attr (image, attr[0], attr[1]);
783 }
784 
785 void
go_image_load_data(GOImage * image,GsfXMLIn * xin)786 go_image_load_data (GOImage *image, GsfXMLIn *xin)
787 {
788 	((GOImageClass *) G_OBJECT_GET_CLASS (image))->load_data (image, xin);
789 }
790 
791 void
_go_image_changed(GOImage * image,double width,double height)792 _go_image_changed (GOImage *image, double width, double height)
793 {
794 	image->width = width;
795 	image->height = height;
796 	if (image->thumbnail) {
797 		g_object_unref (image->thumbnail);
798 		image->thumbnail = NULL;
799 	}
800 }
801 
802 double
go_image_get_width(GOImage const * image)803 go_image_get_width (GOImage const *image)
804 {
805 	g_return_val_if_fail (GO_IS_IMAGE (image), 0.);
806 	return image->width;
807 }
808 
809 double
go_image_get_height(GOImage const * image)810 go_image_get_height (GOImage const *image)
811 {
812 	g_return_val_if_fail (GO_IS_IMAGE (image), 0.);
813 	return image->height;
814 }
815 
816 static double _go_image_dpi_x = 96.,  _go_image_dpi_y = 96.;
817 
818 void
go_image_set_default_dpi(double dpi_x,double dpi_y)819 go_image_set_default_dpi (double dpi_x, double dpi_y)
820 {
821 	_go_image_dpi_x = dpi_x;
822 	_go_image_dpi_y = dpi_y;
823 }
824 
825 void
go_image_get_default_dpi(double * dpi_x,double * dpi_y)826 go_image_get_default_dpi (double *dpi_x, double *dpi_y)
827 {
828 	*dpi_x = _go_image_dpi_x;
829 	*dpi_y = _go_image_dpi_y;
830 }
831 
832 GOImageFormatInfo const *
go_image_get_info(GOImage * image)833 go_image_get_info (GOImage *image)
834 {
835 	if (GO_IS_PIXBUF (image)) {
836 		GOImageFormat f;
837 		char *typ;
838 		g_object_get (image, "image-type", &typ, NULL);
839 		f = go_image_get_format_from_name (typ);
840 		g_free (typ);
841 		return go_image_get_format_info (f);
842 	}
843 
844 	/* Dubious */
845 	if (GO_IS_EMF (image))
846 		return go_image_get_format_info (GO_IMAGE_FORMAT_EMF);
847 	if (GO_IS_SVG (image))
848 		return go_image_get_format_info (GO_IMAGE_FORMAT_SVG);
849 	if (GO_IS_SPECTRE (image))
850 		return go_image_get_format_info (GO_IMAGE_FORMAT_EPS);
851 	return NULL;
852 }
853