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