1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2014-2016 Richard Hughes <richard@hughsie.com>
4  *
5  * SPDX-License-Identifier: LGPL-2.1+
6  */
7 
8 /**
9  * SECTION:as-image
10  * @short_description: Object representing a single image used in a screenshot.
11  * @include: appstream-glib.h
12  * @stability: Stable
13  *
14  * Screenshot may have multiple versions of an image in different resolutions
15  * or aspect ratios. This object allows access to the location and size of a
16  * single image.
17  *
18  * See also: #AsScreenshot
19  */
20 
21 #include "config.h"
22 
23 #include "as-image-private.h"
24 #include "as-node-private.h"
25 #include "as-ref-string.h"
26 #include "as-utils-private.h"
27 #include "as-yaml.h"
28 
29 typedef struct
30 {
31 	AsImageKind		 kind;
32 	AsRefString		*locale;
33 	AsRefString		*url;
34 	AsRefString		*md5;
35 	AsRefString		*basename;
36 	guint			 width;
37 	guint			 height;
38 	GdkPixbuf		*pixbuf;
39 } AsImagePrivate;
40 
G_DEFINE_TYPE_WITH_PRIVATE(AsImage,as_image,G_TYPE_OBJECT)41 G_DEFINE_TYPE_WITH_PRIVATE (AsImage, as_image, G_TYPE_OBJECT)
42 
43 #define GET_PRIVATE(o) (as_image_get_instance_private (o))
44 
45 static void
46 as_image_finalize (GObject *object)
47 {
48 	AsImage *image = AS_IMAGE (object);
49 	AsImagePrivate *priv = GET_PRIVATE (image);
50 
51 	if (priv->pixbuf != NULL)
52 		g_object_unref (priv->pixbuf);
53 	if (priv->url != NULL)
54 		as_ref_string_unref (priv->url);
55 	if (priv->md5 != NULL)
56 		as_ref_string_unref (priv->md5);
57 	if (priv->basename != NULL)
58 		as_ref_string_unref (priv->basename);
59 	if (priv->locale != NULL)
60 		as_ref_string_unref (priv->locale);
61 
62 	G_OBJECT_CLASS (as_image_parent_class)->finalize (object);
63 }
64 
65 static void
as_image_init(AsImage * image)66 as_image_init (AsImage *image)
67 {
68 }
69 
70 static void
as_image_class_init(AsImageClass * klass)71 as_image_class_init (AsImageClass *klass)
72 {
73 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
74 	object_class->finalize = as_image_finalize;
75 }
76 
77 
78 /**
79  * as_image_kind_from_string:
80  * @kind: the string.
81  *
82  * Converts the text representation to an enumerated value.
83  *
84  * Returns: (transfer full): a #AsImageKind, or %AS_IMAGE_KIND_UNKNOWN for unknown.
85  *
86  * Since: 0.1.0
87  **/
88 AsImageKind
as_image_kind_from_string(const gchar * kind)89 as_image_kind_from_string (const gchar *kind)
90 {
91 	if (g_strcmp0 (kind, "source") == 0)
92 		return AS_IMAGE_KIND_SOURCE;
93 	if (g_strcmp0 (kind, "thumbnail") == 0)
94 		return AS_IMAGE_KIND_THUMBNAIL;
95 	return AS_IMAGE_KIND_UNKNOWN;
96 }
97 
98 /**
99  * as_image_kind_to_string:
100  * @kind: the #AsImageKind.
101  *
102  * Converts the enumerated value to an text representation.
103  *
104  * Returns: string version of @kind
105  *
106  * Since: 0.1.0
107  **/
108 const gchar *
as_image_kind_to_string(AsImageKind kind)109 as_image_kind_to_string (AsImageKind kind)
110 {
111 	if (kind == AS_IMAGE_KIND_SOURCE)
112 		return "source";
113 	if (kind == AS_IMAGE_KIND_THUMBNAIL)
114 		return "thumbnail";
115 	return NULL;
116 }
117 
118 /**
119  * as_image_get_url:
120  * @image: a #AsImage instance.
121  *
122  * Gets the full qualified URL for the image, usually pointing at some mirror.
123  *
124  * Returns: URL
125  *
126  * Since: 0.1.0
127  **/
128 const gchar *
as_image_get_url(AsImage * image)129 as_image_get_url (AsImage *image)
130 {
131 	AsImagePrivate *priv = GET_PRIVATE (image);
132 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
133 	return priv->url;
134 }
135 
136 /**
137  * as_image_get_basename:
138  * @image: a #AsImage instance.
139  *
140  * Gets the suggested basename the image, including file extension.
141  *
142  * Returns: filename
143  *
144  * Since: 0.1.6
145  **/
146 const gchar *
as_image_get_basename(AsImage * image)147 as_image_get_basename (AsImage *image)
148 {
149 	AsImagePrivate *priv = GET_PRIVATE (image);
150 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
151 	return priv->basename;
152 }
153 
154 /**
155  * as_image_get_locale:
156  * @image: a #AsImage instance.
157  *
158  * Gets the locale of the image.
159  *
160  * Returns: locale, or %NULL
161  *
162  * Since: 0.5.14
163  **/
164 const gchar *
as_image_get_locale(AsImage * image)165 as_image_get_locale (AsImage *image)
166 {
167 	AsImagePrivate *priv = GET_PRIVATE (image);
168 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
169 	return priv->locale;
170 }
171 
172 /**
173  * as_image_get_md5:
174  * @image: a #AsImage instance.
175  *
176  * Gets the string representation of the pixbuf hash value.
177  *
178  * Returns: string representing the MD5 sum, or %NULL if unset
179  *
180  * Since: 0.1.6
181  **/
182 const gchar *
as_image_get_md5(AsImage * image)183 as_image_get_md5 (AsImage *image)
184 {
185 	AsImagePrivate *priv = GET_PRIVATE (image);
186 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
187 	return priv->md5;
188 }
189 
190 /**
191  * as_image_get_width:
192  * @image: a #AsImage instance.
193  *
194  * Gets the image width.
195  *
196  * Returns: width in pixels
197  *
198  * Since: 0.1.0
199  **/
200 guint
as_image_get_width(AsImage * image)201 as_image_get_width (AsImage *image)
202 {
203 	AsImagePrivate *priv = GET_PRIVATE (image);
204 	g_return_val_if_fail (AS_IS_IMAGE (image), 0);
205 	return priv->width;
206 }
207 
208 /**
209  * as_image_get_height:
210  * @image: a #AsImage instance.
211  *
212  * Gets the image height.
213  *
214  * Returns: height in pixels
215  *
216  * Since: 0.1.0
217  **/
218 guint
as_image_get_height(AsImage * image)219 as_image_get_height (AsImage *image)
220 {
221 	AsImagePrivate *priv = GET_PRIVATE (image);
222 	g_return_val_if_fail (AS_IS_IMAGE (image), 0);
223 	return priv->height;
224 }
225 
226 /**
227  * as_image_get_kind:
228  * @image: a #AsImage instance.
229  *
230  * Gets the image kind.
231  *
232  * Returns: the #AsImageKind
233  *
234  * Since: 0.1.0
235  **/
236 AsImageKind
as_image_get_kind(AsImage * image)237 as_image_get_kind (AsImage *image)
238 {
239 	AsImagePrivate *priv = GET_PRIVATE (image);
240 	g_return_val_if_fail (AS_IS_IMAGE (image), AS_IMAGE_KIND_UNKNOWN);
241 	return priv->kind;
242 }
243 
244 /**
245  * as_image_get_pixbuf:
246  * @image: a #AsImage instance.
247  *
248  * Gets the image pixbuf if set.
249  *
250  * Returns: (transfer none): the #GdkPixbuf, or %NULL
251  *
252  * Since: 0.1.6
253  **/
254 GdkPixbuf *
as_image_get_pixbuf(AsImage * image)255 as_image_get_pixbuf (AsImage *image)
256 {
257 	AsImagePrivate *priv = GET_PRIVATE (image);
258 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
259 	return priv->pixbuf;
260 }
261 
262 /**
263  * as_image_set_url:
264  * @image: a #AsImage instance.
265  * @url: the URL.
266  *
267  * Sets the fully-qualified mirror URL to use for the image.
268  *
269  * Since: 0.1.0
270  **/
271 void
as_image_set_url(AsImage * image,const gchar * url)272 as_image_set_url (AsImage *image, const gchar *url)
273 {
274 	AsImagePrivate *priv = GET_PRIVATE (image);
275 	g_return_if_fail (AS_IS_IMAGE (image));
276 	as_ref_string_assign_safe (&priv->url, url);
277 }
278 
279 void
as_image_set_url_rstr(AsImage * image,AsRefString * rstr)280 as_image_set_url_rstr (AsImage *image, AsRefString *rstr)
281 {
282 	AsImagePrivate *priv = GET_PRIVATE (image);
283 	g_return_if_fail (AS_IS_IMAGE (image));
284 	as_ref_string_assign (&priv->url, rstr);
285 }
286 
287 /**
288  * as_image_set_basename:
289  * @image: a #AsImage instance.
290  * @basename: the new filename basename.
291  *
292  * Sets the image basename filename.
293  *
294  * Since: 0.1.6
295  **/
296 void
as_image_set_basename(AsImage * image,const gchar * basename)297 as_image_set_basename (AsImage *image, const gchar *basename)
298 {
299 	AsImagePrivate *priv = GET_PRIVATE (image);
300 	g_return_if_fail (AS_IS_IMAGE (image));
301 	as_ref_string_assign_safe (&priv->basename, basename);
302 }
303 
304 /**
305  * as_image_set_locale:
306  * @image: a #AsImage instance.
307  * @locale: the new image locale, e.g. "en_GB" or %NULL.
308  *
309  * Sets the image locale.
310  *
311  * Since: 0.5.14
312  **/
313 void
as_image_set_locale(AsImage * image,const gchar * locale)314 as_image_set_locale (AsImage *image, const gchar *locale)
315 {
316 	AsImagePrivate *priv = GET_PRIVATE (image);
317 	g_return_if_fail (AS_IS_IMAGE (image));
318 	as_ref_string_assign_safe (&priv->locale, locale);
319 }
320 
321 /**
322  * as_image_set_width:
323  * @image: a #AsImage instance.
324  * @width: the width in pixels.
325  *
326  * Sets the image width.
327  *
328  * Since: 0.1.0
329  **/
330 void
as_image_set_width(AsImage * image,guint width)331 as_image_set_width (AsImage *image, guint width)
332 {
333 	AsImagePrivate *priv = GET_PRIVATE (image);
334 	g_return_if_fail (AS_IS_IMAGE (image));
335 	priv->width = width;
336 }
337 
338 /**
339  * as_image_set_height:
340  * @image: a #AsImage instance.
341  * @height: the height in pixels.
342  *
343  * Sets the image height.
344  *
345  * Since: 0.1.0
346  **/
347 void
as_image_set_height(AsImage * image,guint height)348 as_image_set_height (AsImage *image, guint height)
349 {
350 	AsImagePrivate *priv = GET_PRIVATE (image);
351 	g_return_if_fail (AS_IS_IMAGE (image));
352 	priv->height = height;
353 }
354 
355 /**
356  * as_image_set_kind:
357  * @image: a #AsImage instance.
358  * @kind: the #AsImageKind, e.g. %AS_IMAGE_KIND_THUMBNAIL.
359  *
360  * Sets the image kind.
361  *
362  * Since: 0.1.0
363  **/
364 void
as_image_set_kind(AsImage * image,AsImageKind kind)365 as_image_set_kind (AsImage *image, AsImageKind kind)
366 {
367 	AsImagePrivate *priv = GET_PRIVATE (image);
368 	g_return_if_fail (AS_IS_IMAGE (image));
369 	priv->kind = kind;
370 }
371 
372 /**
373  * as_image_set_pixbuf:
374  * @image: a #AsImage instance.
375  * @pixbuf: the #GdkPixbuf, or %NULL
376  *
377  * Sets the image pixbuf.
378  *
379  * Since: 0.1.6
380  **/
381 void
as_image_set_pixbuf(AsImage * image,GdkPixbuf * pixbuf)382 as_image_set_pixbuf (AsImage *image, GdkPixbuf *pixbuf)
383 {
384 	AsImagePrivate *priv = GET_PRIVATE (image);
385 	guchar *data;
386 	guint len;
387 
388 	g_return_if_fail (AS_IS_IMAGE (image));
389 
390 	g_set_object (&priv->pixbuf, pixbuf);
391 	if (pixbuf == NULL)
392 		return;
393 	if (priv->md5 == NULL) {
394 		g_autofree gchar *md5_tmp = NULL;
395 		data = gdk_pixbuf_get_pixels_with_length (pixbuf, &len);
396 		md5_tmp = g_compute_checksum_for_data (G_CHECKSUM_MD5,
397 						       data, len);
398 		as_ref_string_assign_safe (&priv->md5, md5_tmp);
399 	}
400 	priv->width = (guint) gdk_pixbuf_get_width (pixbuf);
401 	priv->height = (guint) gdk_pixbuf_get_height (pixbuf);
402 }
403 
404 /**
405  * as_image_node_insert: (skip)
406  * @image: a #AsImage instance.
407  * @parent: the parent #GNode to use..
408  * @ctx: the #AsNodeContext
409  *
410  * Inserts the image into the DOM tree.
411  *
412  * Returns: (transfer none): A populated #GNode
413  *
414  * Since: 0.1.0
415  **/
416 GNode *
as_image_node_insert(AsImage * image,GNode * parent,AsNodeContext * ctx)417 as_image_node_insert (AsImage *image, GNode *parent, AsNodeContext *ctx)
418 {
419 	AsImagePrivate *priv = GET_PRIVATE (image);
420 	GNode *n;
421 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
422 	n = as_node_insert (parent, "image", priv->url,
423 			    AS_NODE_INSERT_FLAG_NONE,
424 			    NULL);
425 	if (priv->width > 0)
426 		as_node_add_attribute_as_uint (n, "width", priv->width);
427 	if (priv->height > 0)
428 		as_node_add_attribute_as_uint (n, "height", priv->height);
429 	if (priv->kind > AS_IMAGE_KIND_UNKNOWN)
430 		as_node_add_attribute (n, "type", as_image_kind_to_string (priv->kind));
431 	if (priv->locale != NULL)
432 		as_node_add_attribute (n, "xml:lang", priv->locale);
433 	return n;
434 }
435 
436 /**
437  * as_image_node_parse:
438  * @image: a #AsImage instance.
439  * @node: a #GNode.
440  * @ctx: a #AsNodeContext.
441  * @error: A #GError or %NULL.
442  *
443  * Populates the object from a DOM node.
444  *
445  * Returns: %TRUE for success
446  *
447  * Since: 0.1.0
448  **/
449 gboolean
as_image_node_parse(AsImage * image,GNode * node,AsNodeContext * ctx,GError ** error)450 as_image_node_parse (AsImage *image, GNode *node,
451 		     AsNodeContext *ctx, GError **error)
452 {
453 	AsImagePrivate *priv = GET_PRIVATE (image);
454 	const gchar *tmp;
455 	guint size;
456 
457 	g_return_val_if_fail (AS_IS_IMAGE (image), FALSE);
458 
459 	size = as_node_get_attribute_as_uint (node, "width");
460 	if (size != G_MAXUINT)
461 		as_image_set_width (image, size);
462 	size = as_node_get_attribute_as_uint (node, "height");
463 	if (size != G_MAXUINT)
464 		as_image_set_height (image, size);
465 	tmp = as_node_get_attribute (node, "type");
466 	if (tmp == NULL)
467 		as_image_set_kind (image, AS_IMAGE_KIND_SOURCE);
468 	else
469 		as_image_set_kind (image, as_image_kind_from_string (tmp));
470 	as_ref_string_assign (&priv->url, as_node_get_data_as_refstr (node));
471 	as_ref_string_assign (&priv->locale, as_node_get_attribute_as_refstr (node, "xml:lang"));
472 	return TRUE;
473 }
474 
475 /**
476  * as_image_node_parse_dep11:
477  * @image: a #AsImage instance.
478  * @node: a #GNode.
479  * @ctx: a #AsNodeContext.
480  * @error: A #GError or %NULL.
481  *
482  * Populates the object from a DEP-11 node.
483  *
484  * Returns: %TRUE for success
485  *
486  * Since: 0.3.0
487  **/
488 gboolean
as_image_node_parse_dep11(AsImage * im,GNode * node,AsNodeContext * ctx,GError ** error)489 as_image_node_parse_dep11 (AsImage *im, GNode *node,
490 			   AsNodeContext *ctx, GError **error)
491 {
492 	GNode *n;
493 	const gchar *tmp;
494 
495 	for (n = node->children; n != NULL; n = n->next) {
496 		tmp = as_yaml_node_get_key (n);
497 		if (g_strcmp0 (tmp, "height") == 0)
498 			as_image_set_height (im, as_yaml_node_get_value_as_uint (n));
499 		else if (g_strcmp0 (tmp, "width") == 0)
500 			as_image_set_width (im, as_yaml_node_get_value_as_uint (n));
501 		else if (g_strcmp0 (tmp, "url") == 0) {
502 			const gchar *media_base_url = as_node_context_get_media_base_url (ctx);
503 			if (media_base_url != NULL) {
504 				g_autofree gchar *url = NULL;
505 				url = g_build_path ("/", media_base_url, as_yaml_node_get_value (n), NULL);
506 				as_image_set_url (im, url);
507 			} else {
508 				as_image_set_url (im, as_yaml_node_get_value (n));
509 			}
510 		}
511 	}
512 	return TRUE;
513 }
514 
515 /**
516  * as_image_load_filename_full:
517  * @image: a #AsImage instance.
518  * @filename: filename to read from
519  * @dest_size: The size of the constructed pixbuf, or 0 for the native size
520  * @src_size_min: The smallest source size allowed, or 0 for none
521  * @flags: a #AsImageLoadFlags, e.g. %AS_IMAGE_LOAD_FLAG_NONE
522  * @error: A #GError or %NULL.
523  *
524  * Reads an image from a file.
525  *
526  * Returns: %TRUE for success
527  *
528  * Since: 0.5.6
529  **/
530 gboolean
as_image_load_filename_full(AsImage * image,const gchar * filename,guint dest_size,guint src_size_min,AsImageLoadFlags flags,GError ** error)531 as_image_load_filename_full (AsImage *image,
532 			     const gchar *filename,
533 			     guint dest_size,
534 			     guint src_size_min,
535 			     AsImageLoadFlags flags,
536 			     GError **error)
537 {
538 	AsImagePrivate *priv = GET_PRIVATE (image);
539 	guint pixbuf_height;
540 	guint pixbuf_width;
541 	guint tmp_height;
542 	guint tmp_width;
543 	g_autoptr(GdkPixbuf) pixbuf = NULL;
544 	g_autoptr(GdkPixbuf) pixbuf_src = NULL;
545 	g_autoptr(GdkPixbuf) pixbuf_tmp = NULL;
546 
547 	g_return_val_if_fail (AS_IS_IMAGE (image), FALSE);
548 
549 	/* only support non-deprecated types */
550 	if (flags & AS_IMAGE_LOAD_FLAG_ONLY_SUPPORTED) {
551 		GdkPixbufFormat *fmt;
552 		g_autofree gchar *name = NULL;
553 		fmt = gdk_pixbuf_get_file_info (filename, NULL, NULL);
554 		if (fmt == NULL) {
555 			g_set_error_literal (error,
556 					     AS_UTILS_ERROR,
557 					     AS_UTILS_ERROR_FAILED,
558 					     "image format was not recognized");
559 			return FALSE;
560 		}
561 		name = gdk_pixbuf_format_get_name (fmt);
562 		if (g_strcmp0 (name, "png") != 0 &&
563 		    g_strcmp0 (name, "jpeg") != 0 &&
564 		    g_strcmp0 (name, "xpm") != 0 &&
565 		    g_strcmp0 (name, "svg") != 0) {
566 			g_set_error (error,
567 				     AS_UTILS_ERROR,
568 				     AS_UTILS_ERROR_FAILED,
569 				     "image format %s is not supported",
570 				     name);
571 			return FALSE;
572 		}
573 	}
574 
575 	/* update basename */
576 	if (flags & AS_IMAGE_LOAD_FLAG_SET_BASENAME) {
577 		g_autofree gchar *basename = NULL;
578 		basename = g_path_get_basename (filename);
579 		as_image_set_basename (image, basename);
580 	}
581 
582 	/* update checksum */
583 	if (flags & AS_IMAGE_LOAD_FLAG_SET_CHECKSUM) {
584 		gsize len;
585 		g_autofree gchar *data = NULL;
586 		g_autofree gchar *md5_tmp = NULL;
587 
588 		/* get the contents so we can hash the predictable file data,
589 		 * rather than the unpredicatable (for JPEG) pixel data */
590 		if (!g_file_get_contents (filename, &data, &len, error))
591 			return FALSE;
592 		md5_tmp = g_compute_checksum_for_data (G_CHECKSUM_MD5,
593 						       (guchar * )data, len);
594 		as_ref_string_assign_safe (&priv->md5, md5_tmp);
595 	}
596 
597 	/* load the image of the native size */
598 	if (dest_size == 0) {
599 		pixbuf = gdk_pixbuf_new_from_file (filename, error);
600 		if (pixbuf == NULL)
601 			return FALSE;
602 		as_image_set_pixbuf (image, pixbuf);
603 		return TRUE;
604 	}
605 
606 	/* open file in native size */
607 	if (g_str_has_suffix (filename, ".svg")) {
608 		pixbuf_src = gdk_pixbuf_new_from_file_at_scale (filename,
609 								(gint) dest_size,
610 								(gint) dest_size,
611 								TRUE, error);
612 	} else {
613 		pixbuf_src = gdk_pixbuf_new_from_file (filename, error);
614 	}
615 	if (pixbuf_src == NULL)
616 		return FALSE;
617 
618 	/* check size */
619 	if (gdk_pixbuf_get_width (pixbuf_src) < (gint) src_size_min &&
620 	    gdk_pixbuf_get_height (pixbuf_src) < (gint) src_size_min) {
621 		g_set_error (error,
622 			     AS_UTILS_ERROR,
623 			     AS_UTILS_ERROR_FAILED,
624 			     "icon was too small %ix%i",
625 			     gdk_pixbuf_get_width (pixbuf_src),
626 			     gdk_pixbuf_get_height (pixbuf_src));
627 		return FALSE;
628 	}
629 
630 	/* don't do anything to an icon with the perfect size */
631 	pixbuf_width = (guint) gdk_pixbuf_get_width (pixbuf_src);
632 	pixbuf_height = (guint) gdk_pixbuf_get_height (pixbuf_src);
633 	if (pixbuf_width == dest_size && pixbuf_height == dest_size) {
634 		as_image_set_pixbuf (image, pixbuf_src);
635 		return TRUE;
636 	}
637 
638 	/* this makes icons look blurry, but allows the software center to look
639 	 * good as icons are properly aligned in the UI layout */
640 	if (flags & AS_IMAGE_LOAD_FLAG_ALWAYS_RESIZE) {
641 		pixbuf = gdk_pixbuf_scale_simple (pixbuf_src,
642 						  (gint) dest_size,
643 						  (gint) dest_size,
644 						  GDK_INTERP_HYPER);
645 		as_image_set_pixbuf (image, pixbuf);
646 		return TRUE;
647 	}
648 
649 	/* never scale up, just pad */
650 	if (pixbuf_width < dest_size && pixbuf_height < dest_size) {
651 		g_debug ("icon padded to %ux%u as size %ux%u",
652 			 dest_size, dest_size,
653 			 pixbuf_width, pixbuf_height);
654 		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
655 					 (gint) dest_size, (gint) dest_size);
656 		gdk_pixbuf_fill (pixbuf, 0x00000000);
657 		gdk_pixbuf_copy_area (pixbuf_src,
658 				      0, 0, /* of src */
659 				      (gint) pixbuf_width,
660 				      (gint) pixbuf_height,
661 				      pixbuf,
662 				      (gint) (dest_size - pixbuf_width) / 2,
663 				      (gint) (dest_size - pixbuf_height) / 2);
664 		as_image_set_pixbuf (image, pixbuf);
665 		return TRUE;
666 	}
667 
668 	/* is the aspect ratio perfectly square */
669 	if (pixbuf_width == pixbuf_height) {
670 		pixbuf = gdk_pixbuf_scale_simple (pixbuf_src,
671 						  (gint) dest_size,
672 						  (gint) dest_size,
673 						  GDK_INTERP_HYPER);
674 		as_image_set_pixbuf (image, pixbuf);
675 		return TRUE;
676 	}
677 
678 	/* create new square pixbuf with alpha padding */
679 	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
680 				 (gint) dest_size, (gint) dest_size);
681 	gdk_pixbuf_fill (pixbuf, 0x00000000);
682 	if (pixbuf_width > pixbuf_height) {
683 		tmp_width = dest_size;
684 		tmp_height = dest_size * pixbuf_height / pixbuf_width;
685 	} else {
686 		tmp_width = dest_size * pixbuf_width / pixbuf_height;
687 		tmp_height = dest_size;
688 	}
689 	pixbuf_tmp = gdk_pixbuf_scale_simple (pixbuf_src,
690 					      (gint) tmp_width,
691 					      (gint) tmp_height,
692 					      GDK_INTERP_HYPER);
693 	if (flags & AS_IMAGE_LOAD_FLAG_SHARPEN)
694 		as_pixbuf_sharpen (pixbuf_tmp, 1, -0.5);
695 	gdk_pixbuf_copy_area (pixbuf_tmp,
696 			      0, 0, /* of src */
697 			      (gint) tmp_width, (gint) tmp_height,
698 			      pixbuf,
699 			      (gint) (dest_size - tmp_width) / 2,
700 			      (gint) (dest_size - tmp_height) / 2);
701 	as_image_set_pixbuf (image, pixbuf);
702 	return TRUE;
703 }
704 
705 /**
706  * as_image_load_filename:
707  * @image: a #AsImage instance.
708  * @filename: filename to read from
709  * @error: A #GError or %NULL.
710  *
711  * Reads a pixbuf from a file.
712  *
713  * NOTE: This function also sets the suggested filename which can be retrieved
714  * using as_image_get_basename(). This can be overridden if required.
715  *
716  * Returns: %TRUE for success
717  *
718  * Since: 0.1.6
719  **/
720 gboolean
as_image_load_filename(AsImage * image,const gchar * filename,GError ** error)721 as_image_load_filename (AsImage *image,
722 			const gchar *filename,
723 			GError **error)
724 {
725 	return as_image_load_filename_full (image,
726 					    filename,
727 					    0, 0,
728 					    AS_IMAGE_LOAD_FLAG_SET_BASENAME |
729 					    AS_IMAGE_LOAD_FLAG_SET_CHECKSUM,
730 					    error);
731 }
732 
733 /**
734  * as_image_save_pixbuf:
735  * @image: a #AsImage instance.
736  * @width: target width, or 0 for default
737  * @height: target height, or 0 for default
738  * @flags: some #AsImageSaveFlags values, e.g. %AS_IMAGE_SAVE_FLAG_PAD_16_9
739  *
740  * Resamples a pixbuf to a specific size.
741  *
742  * Returns: (transfer full): A #GdkPixbuf of the specified size
743  *
744  * Since: 0.1.6
745  **/
746 GdkPixbuf *
as_image_save_pixbuf(AsImage * image,guint width,guint height,AsImageSaveFlags flags)747 as_image_save_pixbuf (AsImage *image,
748 		      guint width,
749 		      guint height,
750 		      AsImageSaveFlags flags)
751 {
752 	AsImagePrivate *priv = GET_PRIVATE (image);
753 	GdkPixbuf *pixbuf = NULL;
754 	guint tmp_height;
755 	guint tmp_width;
756 	guint pixbuf_height;
757 	guint pixbuf_width;
758 	g_autoptr(GdkPixbuf) pixbuf_tmp = NULL;
759 
760 	g_return_val_if_fail (AS_IS_IMAGE (image), NULL);
761 
762 	/* never set */
763 	if (priv->pixbuf == NULL)
764 		return NULL;
765 
766 	/* 0 means 'default' */
767 	if (width == 0)
768 		width = (guint) gdk_pixbuf_get_width (priv->pixbuf);
769 	if (height == 0)
770 		height = (guint) gdk_pixbuf_get_height (priv->pixbuf);
771 
772 	/* don't do anything to an image with the correct size */
773 	pixbuf_width = (guint) gdk_pixbuf_get_width (priv->pixbuf);
774 	pixbuf_height = (guint) gdk_pixbuf_get_height (priv->pixbuf);
775 	if (width == pixbuf_width && height == pixbuf_height)
776 		return g_object_ref (priv->pixbuf);
777 
778 	/* is the aspect ratio of the source perfectly 16:9 */
779 	if (flags == AS_IMAGE_SAVE_FLAG_NONE ||
780 	    (pixbuf_width / 16) * 9 == pixbuf_height) {
781 		pixbuf = gdk_pixbuf_scale_simple (priv->pixbuf,
782 						  (gint) width, (gint) height,
783 						  GDK_INTERP_HYPER);
784 		if ((flags & AS_IMAGE_SAVE_FLAG_SHARPEN) > 0)
785 			as_pixbuf_sharpen (pixbuf, 1, -0.5);
786 		if ((flags & AS_IMAGE_SAVE_FLAG_BLUR) > 0)
787 			as_pixbuf_blur (pixbuf, 5, 3);
788 		return pixbuf;
789 	}
790 
791 	/* create new 16:9 pixbuf with alpha padding */
792 	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
793 				 TRUE, 8,
794 				 (gint) width,
795 				 (gint) height);
796 	gdk_pixbuf_fill (pixbuf, 0x00000000);
797 	/* check the ratio to see which property needs to be fitted and which needs
798 	 * to be reduced */
799 	if (pixbuf_width * 9 > pixbuf_height * 16) {
800 		tmp_width = width;
801 		tmp_height = width * pixbuf_height / pixbuf_width;
802 	} else {
803 		tmp_width = height * pixbuf_width / pixbuf_height;
804 		tmp_height = height;
805 	}
806 	pixbuf_tmp = gdk_pixbuf_scale_simple (priv->pixbuf,
807 					      (gint) tmp_width,
808 					      (gint) tmp_height,
809 					      GDK_INTERP_HYPER);
810 	if ((flags & AS_IMAGE_SAVE_FLAG_SHARPEN) > 0)
811 		as_pixbuf_sharpen (pixbuf_tmp, 1, -0.5);
812 	if ((flags & AS_IMAGE_SAVE_FLAG_BLUR) > 0)
813 		as_pixbuf_blur (pixbuf_tmp, 5, 3);
814 	gdk_pixbuf_copy_area (pixbuf_tmp,
815 			      0, 0, /* of src */
816 			      (gint) tmp_width,
817 			      (gint) tmp_height,
818 			      pixbuf,
819 			      (gint) (width - tmp_width) / 2,
820 			      (gint) (height - tmp_height) / 2);
821 	return pixbuf;
822 }
823 
824 /**
825  * as_image_save_filename:
826  * @image: a #AsImage instance.
827  * @filename: filename to write to
828  * @width: target width, or 0 for default
829  * @height: target height, or 0 for default
830  * @flags: some #AsImageSaveFlags values, e.g. %AS_IMAGE_SAVE_FLAG_PAD_16_9
831  * @error: A #GError or %NULL.
832  *
833  * Saves a pixbuf to a file.
834  *
835  * Returns: %TRUE for success
836  *
837  * Since: 0.1.6
838  **/
839 gboolean
as_image_save_filename(AsImage * image,const gchar * filename,guint width,guint height,AsImageSaveFlags flags,GError ** error)840 as_image_save_filename (AsImage *image,
841 		        const gchar *filename,
842 		        guint width,
843 		        guint height,
844 		        AsImageSaveFlags flags,
845 		        GError **error)
846 {
847 	g_autoptr(GdkPixbuf) pixbuf = NULL;
848 
849 	/* save source file */
850 	pixbuf = as_image_save_pixbuf (image, width, height, flags);
851 	return gdk_pixbuf_save (pixbuf,
852 				filename,
853 				"png",
854 				error,
855 				NULL);
856 }
857 
858 static gboolean
is_pixel_alpha(GdkPixbuf * pixbuf,guint x,guint y)859 is_pixel_alpha (GdkPixbuf *pixbuf, guint x, guint y)
860 {
861 	guint rowstride, n_channels;
862 	guchar *pixels, *p;
863 
864 	n_channels = (guint) gdk_pixbuf_get_n_channels (pixbuf);
865 	rowstride = (guint) gdk_pixbuf_get_rowstride (pixbuf);
866 	pixels = gdk_pixbuf_get_pixels (pixbuf);
867 
868 	p = pixels + y * rowstride + x * n_channels;
869 	return p[3] == 0;
870 }
871 
872 typedef enum {
873 	AS_IMAGE_ALPHA_MODE_START,
874 	AS_IMAGE_ALPHA_MODE_PADDING,
875 	AS_IMAGE_ALPHA_MODE_CONTENT,
876 } AsImageAlphaMode;
877 
878 /**
879  * as_image_get_alpha_flags: (skip)
880  * @image: a #AsImage instance.
881  *
882  * Gets the alpha flags for the image. The following image would have all flags
883  * set, where 'x' is alpha and '@' is non-alpha.
884  *
885  * xxxxxxxxxxxxxxxxxxxxxxxxxxxx
886  * xx@@@@@@@@@@@@@@@@@@@@@@@@xx
887  * xx@@@@@@@xxxxxx@@@@@@@@@@@xx
888  * xx@@@@@@@xxxxxx@@@@@@@@@@@xx
889  * xx@@@@@@@@@@@@@@@@@@@@@@@@xx
890  * xxxxxxxxxxxxxxxxxxxxxxxxxxxx
891  *
892  * Returns: #AsImageAlphaFlags, e.g. %AS_IMAGE_ALPHA_FLAG_LEFT
893  *
894  * Since: 0.2.2
895  **/
896 AsImageAlphaFlags
as_image_get_alpha_flags(AsImage * image)897 as_image_get_alpha_flags (AsImage *image)
898 {
899 	AsImageAlphaFlags flags = AS_IMAGE_ALPHA_FLAG_TOP |
900 				  AS_IMAGE_ALPHA_FLAG_BOTTOM |
901 				  AS_IMAGE_ALPHA_FLAG_LEFT |
902 				  AS_IMAGE_ALPHA_FLAG_RIGHT;
903 	AsImageAlphaMode mode_h;
904 	AsImageAlphaMode mode_v = AS_IMAGE_ALPHA_MODE_START;
905 	AsImagePrivate *priv = GET_PRIVATE (image);
906 	gboolean complete_line_of_alpha;
907 	gboolean is_alpha;
908 	guint width, height;
909 	guint x, y;
910 	guint cnt_content_to_alpha_h;
911 	guint cnt_content_to_alpha_v = 0;
912 
913 	g_return_val_if_fail (AS_IS_IMAGE (image), AS_IMAGE_ALPHA_FLAG_NONE);
914 
915 	if (!gdk_pixbuf_get_has_alpha (priv->pixbuf))
916 		return AS_IMAGE_ALPHA_FLAG_NONE;
917 
918 	width = (guint) gdk_pixbuf_get_width (priv->pixbuf);
919 	height = (guint) gdk_pixbuf_get_height (priv->pixbuf);
920 	for (y = 0; y < height; y++) {
921 		mode_h = AS_IMAGE_ALPHA_MODE_START;
922 		complete_line_of_alpha = TRUE;
923 		cnt_content_to_alpha_h = 0;
924 		for (x = 0; x < width; x++) {
925 			is_alpha = is_pixel_alpha (priv->pixbuf, x, y);
926 			/* use the frame */
927 			if (!is_alpha) {
928 				if (x == 0)
929 					flags &= ~AS_IMAGE_ALPHA_FLAG_LEFT;
930 				if (x == width - 1)
931 					flags &= ~AS_IMAGE_ALPHA_FLAG_RIGHT;
932 				if (y == 0)
933 					flags &= ~AS_IMAGE_ALPHA_FLAG_TOP;
934 				if (y == height - 1)
935 					flags &= ~AS_IMAGE_ALPHA_FLAG_BOTTOM;
936 				complete_line_of_alpha = FALSE;
937 			}
938 
939 			/* use line state machine */
940 			switch (mode_h) {
941 			case AS_IMAGE_ALPHA_MODE_START:
942 				mode_h = is_alpha ? AS_IMAGE_ALPHA_MODE_PADDING :
943 						    AS_IMAGE_ALPHA_MODE_CONTENT;
944 				break;
945 			case AS_IMAGE_ALPHA_MODE_PADDING:
946 				if (!is_alpha)
947 					mode_h = AS_IMAGE_ALPHA_MODE_CONTENT;
948 				break;
949 			case AS_IMAGE_ALPHA_MODE_CONTENT:
950 				if (is_alpha) {
951 					mode_h = AS_IMAGE_ALPHA_MODE_PADDING;
952 					cnt_content_to_alpha_h++;
953 				}
954 				break;
955 			default:
956 				g_assert_not_reached ();
957 			}
958 		}
959 
960 		/* use column state machine */
961 		switch (mode_v) {
962 		case AS_IMAGE_ALPHA_MODE_START:
963 			if (complete_line_of_alpha) {
964 				mode_v = AS_IMAGE_ALPHA_MODE_PADDING;
965 			} else {
966 				mode_v = AS_IMAGE_ALPHA_MODE_CONTENT;
967 			}
968 			break;
969 		case AS_IMAGE_ALPHA_MODE_PADDING:
970 			if (!complete_line_of_alpha)
971 				mode_v = AS_IMAGE_ALPHA_MODE_CONTENT;
972 			break;
973 		case AS_IMAGE_ALPHA_MODE_CONTENT:
974 			if (complete_line_of_alpha) {
975 				mode_v = AS_IMAGE_ALPHA_MODE_PADDING;
976 				cnt_content_to_alpha_v++;
977 			}
978 			break;
979 		default:
980 			g_assert_not_reached ();
981 		}
982 
983 		/* detect internal alpha */
984 		if (mode_h == AS_IMAGE_ALPHA_MODE_PADDING) {
985 			if (cnt_content_to_alpha_h >= 2)
986 				flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL;
987 		} else if (mode_h == AS_IMAGE_ALPHA_MODE_CONTENT) {
988 			if (cnt_content_to_alpha_h >= 1)
989 				flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL;
990 		}
991 	}
992 
993 	/* detect internal alpha */
994 	if (mode_v == AS_IMAGE_ALPHA_MODE_PADDING) {
995 		if (cnt_content_to_alpha_v >= 2)
996 			flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL;
997 	} else if (mode_v == AS_IMAGE_ALPHA_MODE_CONTENT) {
998 		if (cnt_content_to_alpha_v >= 1)
999 			flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL;
1000 	}
1001 	return flags;
1002 }
1003 
1004 /**
1005  * as_image_equal:
1006  * @image1: a #AsImage instance.
1007  * @image2: a #AsImage instance.
1008  *
1009  * Checks if two images are the same.
1010  *
1011  * Returns: %TRUE for success
1012  *
1013  * Since: 0.5.7
1014  **/
1015 gboolean
as_image_equal(AsImage * image1,AsImage * image2)1016 as_image_equal (AsImage *image1, AsImage *image2)
1017 {
1018 	AsImagePrivate *priv1 = GET_PRIVATE (image1);
1019 	AsImagePrivate *priv2 = GET_PRIVATE (image2);
1020 
1021 	g_return_val_if_fail (AS_IS_IMAGE (image1), FALSE);
1022 	g_return_val_if_fail (AS_IS_IMAGE (image2), FALSE);
1023 
1024 	/* trivial */
1025 	if (image1 == image2)
1026 		return TRUE;
1027 
1028 	/* check for equality */
1029 	if (priv1->kind != priv2->kind)
1030 		return FALSE;
1031 	if (priv1->width != priv2->width)
1032 		return FALSE;
1033 	if (priv1->height != priv2->height)
1034 		return FALSE;
1035 	if (g_strcmp0 (priv1->url, priv2->url) != 0)
1036 		return FALSE;
1037 	if (g_strcmp0 (priv1->md5, priv2->md5) != 0)
1038 		return FALSE;
1039 
1040 	/* success */
1041 	return TRUE;
1042 }
1043 
1044 /**
1045  * as_image_new:
1046  *
1047  * Creates a new #AsImage.
1048  *
1049  * Returns: (transfer full): a #AsImage
1050  *
1051  * Since: 0.1.0
1052  **/
1053 AsImage *
as_image_new(void)1054 as_image_new (void)
1055 {
1056 	AsImage *image;
1057 	image = g_object_new (AS_TYPE_IMAGE, NULL);
1058 	return AS_IMAGE (image);
1059 }
1060 
1061