/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2014-2016 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ /** * SECTION:as-image * @short_description: Object representing a single image used in a screenshot. * @include: appstream-glib.h * @stability: Stable * * Screenshot may have multiple versions of an image in different resolutions * or aspect ratios. This object allows access to the location and size of a * single image. * * See also: #AsScreenshot */ #include "config.h" #include "as-image-private.h" #include "as-node-private.h" #include "as-ref-string.h" #include "as-utils-private.h" #include "as-yaml.h" typedef struct { AsImageKind kind; AsRefString *locale; AsRefString *url; AsRefString *md5; AsRefString *basename; guint width; guint height; GdkPixbuf *pixbuf; } AsImagePrivate; G_DEFINE_TYPE_WITH_PRIVATE (AsImage, as_image, G_TYPE_OBJECT) #define GET_PRIVATE(o) (as_image_get_instance_private (o)) static void as_image_finalize (GObject *object) { AsImage *image = AS_IMAGE (object); AsImagePrivate *priv = GET_PRIVATE (image); if (priv->pixbuf != NULL) g_object_unref (priv->pixbuf); if (priv->url != NULL) as_ref_string_unref (priv->url); if (priv->md5 != NULL) as_ref_string_unref (priv->md5); if (priv->basename != NULL) as_ref_string_unref (priv->basename); if (priv->locale != NULL) as_ref_string_unref (priv->locale); G_OBJECT_CLASS (as_image_parent_class)->finalize (object); } static void as_image_init (AsImage *image) { } static void as_image_class_init (AsImageClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = as_image_finalize; } /** * as_image_kind_from_string: * @kind: the string. * * Converts the text representation to an enumerated value. * * Returns: (transfer full): a #AsImageKind, or %AS_IMAGE_KIND_UNKNOWN for unknown. * * Since: 0.1.0 **/ AsImageKind as_image_kind_from_string (const gchar *kind) { if (g_strcmp0 (kind, "source") == 0) return AS_IMAGE_KIND_SOURCE; if (g_strcmp0 (kind, "thumbnail") == 0) return AS_IMAGE_KIND_THUMBNAIL; return AS_IMAGE_KIND_UNKNOWN; } /** * as_image_kind_to_string: * @kind: the #AsImageKind. * * Converts the enumerated value to an text representation. * * Returns: string version of @kind * * Since: 0.1.0 **/ const gchar * as_image_kind_to_string (AsImageKind kind) { if (kind == AS_IMAGE_KIND_SOURCE) return "source"; if (kind == AS_IMAGE_KIND_THUMBNAIL) return "thumbnail"; return NULL; } /** * as_image_get_url: * @image: a #AsImage instance. * * Gets the full qualified URL for the image, usually pointing at some mirror. * * Returns: URL * * Since: 0.1.0 **/ const gchar * as_image_get_url (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), NULL); return priv->url; } /** * as_image_get_basename: * @image: a #AsImage instance. * * Gets the suggested basename the image, including file extension. * * Returns: filename * * Since: 0.1.6 **/ const gchar * as_image_get_basename (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), NULL); return priv->basename; } /** * as_image_get_locale: * @image: a #AsImage instance. * * Gets the locale of the image. * * Returns: locale, or %NULL * * Since: 0.5.14 **/ const gchar * as_image_get_locale (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), NULL); return priv->locale; } /** * as_image_get_md5: * @image: a #AsImage instance. * * Gets the string representation of the pixbuf hash value. * * Returns: string representing the MD5 sum, or %NULL if unset * * Since: 0.1.6 **/ const gchar * as_image_get_md5 (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), NULL); return priv->md5; } /** * as_image_get_width: * @image: a #AsImage instance. * * Gets the image width. * * Returns: width in pixels * * Since: 0.1.0 **/ guint as_image_get_width (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), 0); return priv->width; } /** * as_image_get_height: * @image: a #AsImage instance. * * Gets the image height. * * Returns: height in pixels * * Since: 0.1.0 **/ guint as_image_get_height (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), 0); return priv->height; } /** * as_image_get_kind: * @image: a #AsImage instance. * * Gets the image kind. * * Returns: the #AsImageKind * * Since: 0.1.0 **/ AsImageKind as_image_get_kind (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), AS_IMAGE_KIND_UNKNOWN); return priv->kind; } /** * as_image_get_pixbuf: * @image: a #AsImage instance. * * Gets the image pixbuf if set. * * Returns: (transfer none): the #GdkPixbuf, or %NULL * * Since: 0.1.6 **/ GdkPixbuf * as_image_get_pixbuf (AsImage *image) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_val_if_fail (AS_IS_IMAGE (image), NULL); return priv->pixbuf; } /** * as_image_set_url: * @image: a #AsImage instance. * @url: the URL. * * Sets the fully-qualified mirror URL to use for the image. * * Since: 0.1.0 **/ void as_image_set_url (AsImage *image, const gchar *url) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); as_ref_string_assign_safe (&priv->url, url); } void as_image_set_url_rstr (AsImage *image, AsRefString *rstr) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); as_ref_string_assign (&priv->url, rstr); } /** * as_image_set_basename: * @image: a #AsImage instance. * @basename: the new filename basename. * * Sets the image basename filename. * * Since: 0.1.6 **/ void as_image_set_basename (AsImage *image, const gchar *basename) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); as_ref_string_assign_safe (&priv->basename, basename); } /** * as_image_set_locale: * @image: a #AsImage instance. * @locale: the new image locale, e.g. "en_GB" or %NULL. * * Sets the image locale. * * Since: 0.5.14 **/ void as_image_set_locale (AsImage *image, const gchar *locale) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); as_ref_string_assign_safe (&priv->locale, locale); } /** * as_image_set_width: * @image: a #AsImage instance. * @width: the width in pixels. * * Sets the image width. * * Since: 0.1.0 **/ void as_image_set_width (AsImage *image, guint width) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); priv->width = width; } /** * as_image_set_height: * @image: a #AsImage instance. * @height: the height in pixels. * * Sets the image height. * * Since: 0.1.0 **/ void as_image_set_height (AsImage *image, guint height) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); priv->height = height; } /** * as_image_set_kind: * @image: a #AsImage instance. * @kind: the #AsImageKind, e.g. %AS_IMAGE_KIND_THUMBNAIL. * * Sets the image kind. * * Since: 0.1.0 **/ void as_image_set_kind (AsImage *image, AsImageKind kind) { AsImagePrivate *priv = GET_PRIVATE (image); g_return_if_fail (AS_IS_IMAGE (image)); priv->kind = kind; } /** * as_image_set_pixbuf: * @image: a #AsImage instance. * @pixbuf: the #GdkPixbuf, or %NULL * * Sets the image pixbuf. * * Since: 0.1.6 **/ void as_image_set_pixbuf (AsImage *image, GdkPixbuf *pixbuf) { AsImagePrivate *priv = GET_PRIVATE (image); guchar *data; guint len; g_return_if_fail (AS_IS_IMAGE (image)); g_set_object (&priv->pixbuf, pixbuf); if (pixbuf == NULL) return; if (priv->md5 == NULL) { g_autofree gchar *md5_tmp = NULL; data = gdk_pixbuf_get_pixels_with_length (pixbuf, &len); md5_tmp = g_compute_checksum_for_data (G_CHECKSUM_MD5, data, len); as_ref_string_assign_safe (&priv->md5, md5_tmp); } priv->width = (guint) gdk_pixbuf_get_width (pixbuf); priv->height = (guint) gdk_pixbuf_get_height (pixbuf); } /** * as_image_node_insert: (skip) * @image: a #AsImage instance. * @parent: the parent #GNode to use.. * @ctx: the #AsNodeContext * * Inserts the image into the DOM tree. * * Returns: (transfer none): A populated #GNode * * Since: 0.1.0 **/ GNode * as_image_node_insert (AsImage *image, GNode *parent, AsNodeContext *ctx) { AsImagePrivate *priv = GET_PRIVATE (image); GNode *n; g_return_val_if_fail (AS_IS_IMAGE (image), NULL); n = as_node_insert (parent, "image", priv->url, AS_NODE_INSERT_FLAG_NONE, NULL); if (priv->width > 0) as_node_add_attribute_as_uint (n, "width", priv->width); if (priv->height > 0) as_node_add_attribute_as_uint (n, "height", priv->height); if (priv->kind > AS_IMAGE_KIND_UNKNOWN) as_node_add_attribute (n, "type", as_image_kind_to_string (priv->kind)); if (priv->locale != NULL) as_node_add_attribute (n, "xml:lang", priv->locale); return n; } /** * as_image_node_parse: * @image: a #AsImage instance. * @node: a #GNode. * @ctx: a #AsNodeContext. * @error: A #GError or %NULL. * * Populates the object from a DOM node. * * Returns: %TRUE for success * * Since: 0.1.0 **/ gboolean as_image_node_parse (AsImage *image, GNode *node, AsNodeContext *ctx, GError **error) { AsImagePrivate *priv = GET_PRIVATE (image); const gchar *tmp; guint size; g_return_val_if_fail (AS_IS_IMAGE (image), FALSE); size = as_node_get_attribute_as_uint (node, "width"); if (size != G_MAXUINT) as_image_set_width (image, size); size = as_node_get_attribute_as_uint (node, "height"); if (size != G_MAXUINT) as_image_set_height (image, size); tmp = as_node_get_attribute (node, "type"); if (tmp == NULL) as_image_set_kind (image, AS_IMAGE_KIND_SOURCE); else as_image_set_kind (image, as_image_kind_from_string (tmp)); as_ref_string_assign (&priv->url, as_node_get_data_as_refstr (node)); as_ref_string_assign (&priv->locale, as_node_get_attribute_as_refstr (node, "xml:lang")); return TRUE; } /** * as_image_node_parse_dep11: * @image: a #AsImage instance. * @node: a #GNode. * @ctx: a #AsNodeContext. * @error: A #GError or %NULL. * * Populates the object from a DEP-11 node. * * Returns: %TRUE for success * * Since: 0.3.0 **/ gboolean as_image_node_parse_dep11 (AsImage *im, GNode *node, AsNodeContext *ctx, GError **error) { GNode *n; const gchar *tmp; for (n = node->children; n != NULL; n = n->next) { tmp = as_yaml_node_get_key (n); if (g_strcmp0 (tmp, "height") == 0) as_image_set_height (im, as_yaml_node_get_value_as_uint (n)); else if (g_strcmp0 (tmp, "width") == 0) as_image_set_width (im, as_yaml_node_get_value_as_uint (n)); else if (g_strcmp0 (tmp, "url") == 0) { const gchar *media_base_url = as_node_context_get_media_base_url (ctx); if (media_base_url != NULL) { g_autofree gchar *url = NULL; url = g_build_path ("/", media_base_url, as_yaml_node_get_value (n), NULL); as_image_set_url (im, url); } else { as_image_set_url (im, as_yaml_node_get_value (n)); } } } return TRUE; } /** * as_image_load_filename_full: * @image: a #AsImage instance. * @filename: filename to read from * @dest_size: The size of the constructed pixbuf, or 0 for the native size * @src_size_min: The smallest source size allowed, or 0 for none * @flags: a #AsImageLoadFlags, e.g. %AS_IMAGE_LOAD_FLAG_NONE * @error: A #GError or %NULL. * * Reads an image from a file. * * Returns: %TRUE for success * * Since: 0.5.6 **/ gboolean as_image_load_filename_full (AsImage *image, const gchar *filename, guint dest_size, guint src_size_min, AsImageLoadFlags flags, GError **error) { AsImagePrivate *priv = GET_PRIVATE (image); guint pixbuf_height; guint pixbuf_width; guint tmp_height; guint tmp_width; g_autoptr(GdkPixbuf) pixbuf = NULL; g_autoptr(GdkPixbuf) pixbuf_src = NULL; g_autoptr(GdkPixbuf) pixbuf_tmp = NULL; g_return_val_if_fail (AS_IS_IMAGE (image), FALSE); /* only support non-deprecated types */ if (flags & AS_IMAGE_LOAD_FLAG_ONLY_SUPPORTED) { GdkPixbufFormat *fmt; g_autofree gchar *name = NULL; fmt = gdk_pixbuf_get_file_info (filename, NULL, NULL); if (fmt == NULL) { g_set_error_literal (error, AS_UTILS_ERROR, AS_UTILS_ERROR_FAILED, "image format was not recognized"); return FALSE; } name = gdk_pixbuf_format_get_name (fmt); if (g_strcmp0 (name, "png") != 0 && g_strcmp0 (name, "jpeg") != 0 && g_strcmp0 (name, "xpm") != 0 && g_strcmp0 (name, "svg") != 0) { g_set_error (error, AS_UTILS_ERROR, AS_UTILS_ERROR_FAILED, "image format %s is not supported", name); return FALSE; } } /* update basename */ if (flags & AS_IMAGE_LOAD_FLAG_SET_BASENAME) { g_autofree gchar *basename = NULL; basename = g_path_get_basename (filename); as_image_set_basename (image, basename); } /* update checksum */ if (flags & AS_IMAGE_LOAD_FLAG_SET_CHECKSUM) { gsize len; g_autofree gchar *data = NULL; g_autofree gchar *md5_tmp = NULL; /* get the contents so we can hash the predictable file data, * rather than the unpredicatable (for JPEG) pixel data */ if (!g_file_get_contents (filename, &data, &len, error)) return FALSE; md5_tmp = g_compute_checksum_for_data (G_CHECKSUM_MD5, (guchar * )data, len); as_ref_string_assign_safe (&priv->md5, md5_tmp); } /* load the image of the native size */ if (dest_size == 0) { pixbuf = gdk_pixbuf_new_from_file (filename, error); if (pixbuf == NULL) return FALSE; as_image_set_pixbuf (image, pixbuf); return TRUE; } /* open file in native size */ if (g_str_has_suffix (filename, ".svg")) { pixbuf_src = gdk_pixbuf_new_from_file_at_scale (filename, (gint) dest_size, (gint) dest_size, TRUE, error); } else { pixbuf_src = gdk_pixbuf_new_from_file (filename, error); } if (pixbuf_src == NULL) return FALSE; /* check size */ if (gdk_pixbuf_get_width (pixbuf_src) < (gint) src_size_min && gdk_pixbuf_get_height (pixbuf_src) < (gint) src_size_min) { g_set_error (error, AS_UTILS_ERROR, AS_UTILS_ERROR_FAILED, "icon was too small %ix%i", gdk_pixbuf_get_width (pixbuf_src), gdk_pixbuf_get_height (pixbuf_src)); return FALSE; } /* don't do anything to an icon with the perfect size */ pixbuf_width = (guint) gdk_pixbuf_get_width (pixbuf_src); pixbuf_height = (guint) gdk_pixbuf_get_height (pixbuf_src); if (pixbuf_width == dest_size && pixbuf_height == dest_size) { as_image_set_pixbuf (image, pixbuf_src); return TRUE; } /* this makes icons look blurry, but allows the software center to look * good as icons are properly aligned in the UI layout */ if (flags & AS_IMAGE_LOAD_FLAG_ALWAYS_RESIZE) { pixbuf = gdk_pixbuf_scale_simple (pixbuf_src, (gint) dest_size, (gint) dest_size, GDK_INTERP_HYPER); as_image_set_pixbuf (image, pixbuf); return TRUE; } /* never scale up, just pad */ if (pixbuf_width < dest_size && pixbuf_height < dest_size) { g_debug ("icon padded to %ux%u as size %ux%u", dest_size, dest_size, pixbuf_width, pixbuf_height); pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, (gint) dest_size, (gint) dest_size); gdk_pixbuf_fill (pixbuf, 0x00000000); gdk_pixbuf_copy_area (pixbuf_src, 0, 0, /* of src */ (gint) pixbuf_width, (gint) pixbuf_height, pixbuf, (gint) (dest_size - pixbuf_width) / 2, (gint) (dest_size - pixbuf_height) / 2); as_image_set_pixbuf (image, pixbuf); return TRUE; } /* is the aspect ratio perfectly square */ if (pixbuf_width == pixbuf_height) { pixbuf = gdk_pixbuf_scale_simple (pixbuf_src, (gint) dest_size, (gint) dest_size, GDK_INTERP_HYPER); as_image_set_pixbuf (image, pixbuf); return TRUE; } /* create new square pixbuf with alpha padding */ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, (gint) dest_size, (gint) dest_size); gdk_pixbuf_fill (pixbuf, 0x00000000); if (pixbuf_width > pixbuf_height) { tmp_width = dest_size; tmp_height = dest_size * pixbuf_height / pixbuf_width; } else { tmp_width = dest_size * pixbuf_width / pixbuf_height; tmp_height = dest_size; } pixbuf_tmp = gdk_pixbuf_scale_simple (pixbuf_src, (gint) tmp_width, (gint) tmp_height, GDK_INTERP_HYPER); if (flags & AS_IMAGE_LOAD_FLAG_SHARPEN) as_pixbuf_sharpen (pixbuf_tmp, 1, -0.5); gdk_pixbuf_copy_area (pixbuf_tmp, 0, 0, /* of src */ (gint) tmp_width, (gint) tmp_height, pixbuf, (gint) (dest_size - tmp_width) / 2, (gint) (dest_size - tmp_height) / 2); as_image_set_pixbuf (image, pixbuf); return TRUE; } /** * as_image_load_filename: * @image: a #AsImage instance. * @filename: filename to read from * @error: A #GError or %NULL. * * Reads a pixbuf from a file. * * NOTE: This function also sets the suggested filename which can be retrieved * using as_image_get_basename(). This can be overridden if required. * * Returns: %TRUE for success * * Since: 0.1.6 **/ gboolean as_image_load_filename (AsImage *image, const gchar *filename, GError **error) { return as_image_load_filename_full (image, filename, 0, 0, AS_IMAGE_LOAD_FLAG_SET_BASENAME | AS_IMAGE_LOAD_FLAG_SET_CHECKSUM, error); } /** * as_image_save_pixbuf: * @image: a #AsImage instance. * @width: target width, or 0 for default * @height: target height, or 0 for default * @flags: some #AsImageSaveFlags values, e.g. %AS_IMAGE_SAVE_FLAG_PAD_16_9 * * Resamples a pixbuf to a specific size. * * Returns: (transfer full): A #GdkPixbuf of the specified size * * Since: 0.1.6 **/ GdkPixbuf * as_image_save_pixbuf (AsImage *image, guint width, guint height, AsImageSaveFlags flags) { AsImagePrivate *priv = GET_PRIVATE (image); GdkPixbuf *pixbuf = NULL; guint tmp_height; guint tmp_width; guint pixbuf_height; guint pixbuf_width; g_autoptr(GdkPixbuf) pixbuf_tmp = NULL; g_return_val_if_fail (AS_IS_IMAGE (image), NULL); /* never set */ if (priv->pixbuf == NULL) return NULL; /* 0 means 'default' */ if (width == 0) width = (guint) gdk_pixbuf_get_width (priv->pixbuf); if (height == 0) height = (guint) gdk_pixbuf_get_height (priv->pixbuf); /* don't do anything to an image with the correct size */ pixbuf_width = (guint) gdk_pixbuf_get_width (priv->pixbuf); pixbuf_height = (guint) gdk_pixbuf_get_height (priv->pixbuf); if (width == pixbuf_width && height == pixbuf_height) return g_object_ref (priv->pixbuf); /* is the aspect ratio of the source perfectly 16:9 */ if (flags == AS_IMAGE_SAVE_FLAG_NONE || (pixbuf_width / 16) * 9 == pixbuf_height) { pixbuf = gdk_pixbuf_scale_simple (priv->pixbuf, (gint) width, (gint) height, GDK_INTERP_HYPER); if ((flags & AS_IMAGE_SAVE_FLAG_SHARPEN) > 0) as_pixbuf_sharpen (pixbuf, 1, -0.5); if ((flags & AS_IMAGE_SAVE_FLAG_BLUR) > 0) as_pixbuf_blur (pixbuf, 5, 3); return pixbuf; } /* create new 16:9 pixbuf with alpha padding */ pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, (gint) width, (gint) height); gdk_pixbuf_fill (pixbuf, 0x00000000); /* check the ratio to see which property needs to be fitted and which needs * to be reduced */ if (pixbuf_width * 9 > pixbuf_height * 16) { tmp_width = width; tmp_height = width * pixbuf_height / pixbuf_width; } else { tmp_width = height * pixbuf_width / pixbuf_height; tmp_height = height; } pixbuf_tmp = gdk_pixbuf_scale_simple (priv->pixbuf, (gint) tmp_width, (gint) tmp_height, GDK_INTERP_HYPER); if ((flags & AS_IMAGE_SAVE_FLAG_SHARPEN) > 0) as_pixbuf_sharpen (pixbuf_tmp, 1, -0.5); if ((flags & AS_IMAGE_SAVE_FLAG_BLUR) > 0) as_pixbuf_blur (pixbuf_tmp, 5, 3); gdk_pixbuf_copy_area (pixbuf_tmp, 0, 0, /* of src */ (gint) tmp_width, (gint) tmp_height, pixbuf, (gint) (width - tmp_width) / 2, (gint) (height - tmp_height) / 2); return pixbuf; } /** * as_image_save_filename: * @image: a #AsImage instance. * @filename: filename to write to * @width: target width, or 0 for default * @height: target height, or 0 for default * @flags: some #AsImageSaveFlags values, e.g. %AS_IMAGE_SAVE_FLAG_PAD_16_9 * @error: A #GError or %NULL. * * Saves a pixbuf to a file. * * Returns: %TRUE for success * * Since: 0.1.6 **/ gboolean as_image_save_filename (AsImage *image, const gchar *filename, guint width, guint height, AsImageSaveFlags flags, GError **error) { g_autoptr(GdkPixbuf) pixbuf = NULL; /* save source file */ pixbuf = as_image_save_pixbuf (image, width, height, flags); return gdk_pixbuf_save (pixbuf, filename, "png", error, NULL); } static gboolean is_pixel_alpha (GdkPixbuf *pixbuf, guint x, guint y) { guint rowstride, n_channels; guchar *pixels, *p; n_channels = (guint) gdk_pixbuf_get_n_channels (pixbuf); rowstride = (guint) gdk_pixbuf_get_rowstride (pixbuf); pixels = gdk_pixbuf_get_pixels (pixbuf); p = pixels + y * rowstride + x * n_channels; return p[3] == 0; } typedef enum { AS_IMAGE_ALPHA_MODE_START, AS_IMAGE_ALPHA_MODE_PADDING, AS_IMAGE_ALPHA_MODE_CONTENT, } AsImageAlphaMode; /** * as_image_get_alpha_flags: (skip) * @image: a #AsImage instance. * * Gets the alpha flags for the image. The following image would have all flags * set, where 'x' is alpha and '@' is non-alpha. * * xxxxxxxxxxxxxxxxxxxxxxxxxxxx * xx@@@@@@@@@@@@@@@@@@@@@@@@xx * xx@@@@@@@xxxxxx@@@@@@@@@@@xx * xx@@@@@@@xxxxxx@@@@@@@@@@@xx * xx@@@@@@@@@@@@@@@@@@@@@@@@xx * xxxxxxxxxxxxxxxxxxxxxxxxxxxx * * Returns: #AsImageAlphaFlags, e.g. %AS_IMAGE_ALPHA_FLAG_LEFT * * Since: 0.2.2 **/ AsImageAlphaFlags as_image_get_alpha_flags (AsImage *image) { AsImageAlphaFlags flags = AS_IMAGE_ALPHA_FLAG_TOP | AS_IMAGE_ALPHA_FLAG_BOTTOM | AS_IMAGE_ALPHA_FLAG_LEFT | AS_IMAGE_ALPHA_FLAG_RIGHT; AsImageAlphaMode mode_h; AsImageAlphaMode mode_v = AS_IMAGE_ALPHA_MODE_START; AsImagePrivate *priv = GET_PRIVATE (image); gboolean complete_line_of_alpha; gboolean is_alpha; guint width, height; guint x, y; guint cnt_content_to_alpha_h; guint cnt_content_to_alpha_v = 0; g_return_val_if_fail (AS_IS_IMAGE (image), AS_IMAGE_ALPHA_FLAG_NONE); if (!gdk_pixbuf_get_has_alpha (priv->pixbuf)) return AS_IMAGE_ALPHA_FLAG_NONE; width = (guint) gdk_pixbuf_get_width (priv->pixbuf); height = (guint) gdk_pixbuf_get_height (priv->pixbuf); for (y = 0; y < height; y++) { mode_h = AS_IMAGE_ALPHA_MODE_START; complete_line_of_alpha = TRUE; cnt_content_to_alpha_h = 0; for (x = 0; x < width; x++) { is_alpha = is_pixel_alpha (priv->pixbuf, x, y); /* use the frame */ if (!is_alpha) { if (x == 0) flags &= ~AS_IMAGE_ALPHA_FLAG_LEFT; if (x == width - 1) flags &= ~AS_IMAGE_ALPHA_FLAG_RIGHT; if (y == 0) flags &= ~AS_IMAGE_ALPHA_FLAG_TOP; if (y == height - 1) flags &= ~AS_IMAGE_ALPHA_FLAG_BOTTOM; complete_line_of_alpha = FALSE; } /* use line state machine */ switch (mode_h) { case AS_IMAGE_ALPHA_MODE_START: mode_h = is_alpha ? AS_IMAGE_ALPHA_MODE_PADDING : AS_IMAGE_ALPHA_MODE_CONTENT; break; case AS_IMAGE_ALPHA_MODE_PADDING: if (!is_alpha) mode_h = AS_IMAGE_ALPHA_MODE_CONTENT; break; case AS_IMAGE_ALPHA_MODE_CONTENT: if (is_alpha) { mode_h = AS_IMAGE_ALPHA_MODE_PADDING; cnt_content_to_alpha_h++; } break; default: g_assert_not_reached (); } } /* use column state machine */ switch (mode_v) { case AS_IMAGE_ALPHA_MODE_START: if (complete_line_of_alpha) { mode_v = AS_IMAGE_ALPHA_MODE_PADDING; } else { mode_v = AS_IMAGE_ALPHA_MODE_CONTENT; } break; case AS_IMAGE_ALPHA_MODE_PADDING: if (!complete_line_of_alpha) mode_v = AS_IMAGE_ALPHA_MODE_CONTENT; break; case AS_IMAGE_ALPHA_MODE_CONTENT: if (complete_line_of_alpha) { mode_v = AS_IMAGE_ALPHA_MODE_PADDING; cnt_content_to_alpha_v++; } break; default: g_assert_not_reached (); } /* detect internal alpha */ if (mode_h == AS_IMAGE_ALPHA_MODE_PADDING) { if (cnt_content_to_alpha_h >= 2) flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL; } else if (mode_h == AS_IMAGE_ALPHA_MODE_CONTENT) { if (cnt_content_to_alpha_h >= 1) flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL; } } /* detect internal alpha */ if (mode_v == AS_IMAGE_ALPHA_MODE_PADDING) { if (cnt_content_to_alpha_v >= 2) flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL; } else if (mode_v == AS_IMAGE_ALPHA_MODE_CONTENT) { if (cnt_content_to_alpha_v >= 1) flags |= AS_IMAGE_ALPHA_FLAG_INTERNAL; } return flags; } /** * as_image_equal: * @image1: a #AsImage instance. * @image2: a #AsImage instance. * * Checks if two images are the same. * * Returns: %TRUE for success * * Since: 0.5.7 **/ gboolean as_image_equal (AsImage *image1, AsImage *image2) { AsImagePrivate *priv1 = GET_PRIVATE (image1); AsImagePrivate *priv2 = GET_PRIVATE (image2); g_return_val_if_fail (AS_IS_IMAGE (image1), FALSE); g_return_val_if_fail (AS_IS_IMAGE (image2), FALSE); /* trivial */ if (image1 == image2) return TRUE; /* check for equality */ if (priv1->kind != priv2->kind) return FALSE; if (priv1->width != priv2->width) return FALSE; if (priv1->height != priv2->height) return FALSE; if (g_strcmp0 (priv1->url, priv2->url) != 0) return FALSE; if (g_strcmp0 (priv1->md5, priv2->md5) != 0) return FALSE; /* success */ return TRUE; } /** * as_image_new: * * Creates a new #AsImage. * * Returns: (transfer full): a #AsImage * * Since: 0.1.0 **/ AsImage * as_image_new (void) { AsImage *image; image = g_object_new (AS_TYPE_IMAGE, NULL); return AS_IMAGE (image); }