[gnome-contacts] Avatar: use GNOME 3.32 avatar styles for fallback
- From: Niels De Graef <nielsdg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-contacts] Avatar: use GNOME 3.32 avatar styles for fallback
- Date: Mon, 29 Jul 2019 16:35:40 +0000 (UTC)
commit aac409f7e53676eefb2a55044d958005c21d96dd
Author: Julian Sparber <julian sparber net>
Date: Thu Jul 25 10:30:40 2019 +0200
Avatar: use GNOME 3.32 avatar styles for fallback
src/contacts-avatar-utils.vala | 167 +++++++++++++++++++++++++++++++++++++++++
src/contacts-avatar.vala | 122 ++++++++++++++++--------------
src/meson.build | 1 +
3 files changed, 232 insertions(+), 58 deletions(-)
---
diff --git a/src/contacts-avatar-utils.vala b/src/contacts-avatar-utils.vala
new file mode 100644
index 0000000..ff51d2d
--- /dev/null
+++ b/src/contacts-avatar-utils.vala
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2019 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+namespace Contacts.AvatarUtils {
+
+ // The following was based on code written by Felipe Borges for
+ // gnome-control-enter in panels/user-accounts/user-utils.c commit
+ // 02c288ab6f069a0c106323a93400f192a63cb67e. The copyright in that
+ // file is: "Copyright 2009-2010 Red Hat, Inc,"
+
+ public Gdk.Pixbuf generate_user_picture(string name, int size, bool label = true) {
+ Cairo.Surface surface = new Cairo.ImageSurface(
+ Cairo.Format.ARGB32, size, size
+ );
+ Cairo.Context cr = new Cairo.Context(surface);
+ cr.rectangle(0, 0, size, size);
+
+ /* Fill the background with a colour for the name */
+ Gdk.RGBA color = get_color_for_name(name);
+ cr.set_source_rgb(
+ color.red / 255.0, color.green / 255.0, color.blue / 255.0
+ );
+ cr.fill();
+
+ /* Draw the initials on top */
+ if (label) {
+ string? initials = extract_initials_from_name(name);
+ if (initials != null) {
+ string font = "Sans %d".printf((int) GLib.Math.ceil(size / 2.5));
+
+ cr.set_source_rgb(1.0, 1.0, 1.0);
+ Pango.Layout layout = Pango.cairo_create_layout(cr);
+ layout.set_text(initials, -1);
+ layout.set_font_description(Pango.FontDescription.from_string(font));
+
+ int width, height;
+ layout.get_size(out width, out height);
+ cr.translate(size / 2, size / 2);
+ cr.move_to(
+ -((double) width / Pango.SCALE) / 2,
+ -((double) height / Pango.SCALE) / 2
+ );
+ Pango.cairo_show_layout(cr, layout);
+ }
+ }
+
+ return Gdk.pixbuf_get_from_surface(
+ surface, 0, 0, size, size
+ );
+ }
+
+ public Gdk.Pixbuf round_image(Gdk.Pixbuf source) {
+ int size = source.width;
+ Cairo.Surface surface = new Cairo.ImageSurface(
+ Cairo.Format.ARGB32, size, size
+ );
+ Cairo.Context cr = new Cairo.Context(surface);
+
+ /* Clip a circle */
+ cr.arc(size / 2, size / 2, size / 2, 0, 2 * GLib.Math.PI);
+ cr.clip();
+ cr.new_path();
+
+ Gdk.cairo_set_source_pixbuf(cr, source, 0, 0);
+ cr.paint();
+
+ return Gdk.pixbuf_get_from_surface(
+ surface, 0, 0, size, size
+ );
+ }
+
+ public string? extract_initials_from_name(string name) {
+ string normalized = name.strip().up().normalize();
+ string? initials = null;
+ if (normalized != "") {
+ GLib.StringBuilder buf = new GLib.StringBuilder();
+ unichar c = 0;
+ int index = 0;
+
+ // Get the first alphanumeric char of the string
+ for (int i = 0; normalized.get_next_char(ref index, out c); i++) {
+ if (c.isalnum()) {
+ buf.append_unichar(c);
+ break;
+ }
+ }
+
+ // Get the first alphanumeric char of the last word of the string
+ index = normalized.last_index_of_char(' ');
+ if (index >= 0) {
+ for (int i = 0; normalized.get_next_char(ref index, out c); i++) {
+ if (c.isalnum()) {
+ buf.append_unichar(c);
+ break;
+ }
+ }
+ }
+
+ if (buf.data.length > 0) {
+ initials = (string) buf.data;
+ }
+ }
+ return initials;
+ }
+
+
+ public Gdk.RGBA get_color_for_name(string name) {
+ // https://gitlab.gnome.org/Community/Design/HIG-app-icons/blob/master/GNOME%20HIG.gpl
+ const double[,3] GNOME_COLOR_PALETTE = {
+ { 98, 160, 234 },
+ { 53, 132, 228 },
+ { 28, 113, 216 },
+ { 26, 95, 180 },
+ { 87, 227, 137 },
+ { 51, 209, 122 },
+ { 46, 194, 126 },
+ { 38, 162, 105 },
+ { 248, 228, 92 },
+ { 246, 211, 45 },
+ { 245, 194, 17 },
+ { 229, 165, 10 },
+ { 255, 163, 72 },
+ { 255, 120, 0 },
+ { 230, 97, 0 },
+ { 198, 70, 0 },
+ { 237, 51, 59 },
+ { 224, 27, 36 },
+ { 192, 28, 40 },
+ { 165, 29, 45 },
+ { 192, 97, 203 },
+ { 163, 71, 186 },
+ { 129, 61, 156 },
+ { 97, 53, 131 },
+ { 181, 131, 90 },
+ { 152, 106, 68 },
+ { 134, 94, 60 },
+ { 99, 69, 44 }
+ };
+
+ Gdk.RGBA color = { 255, 255, 255, 1.0 };
+ uint hash;
+ uint number_of_colors = GNOME_COLOR_PALETTE.length[0];
+ uint idx;
+
+ if (name == "") {
+ // Return a random color if we don't have a name
+ idx = Random.int_range (0, (int32) number_of_colors);
+ color.red = GNOME_COLOR_PALETTE[idx,0];
+ color.green = GNOME_COLOR_PALETTE[idx,1];
+ color.blue = GNOME_COLOR_PALETTE[idx,2];
+ return color;
+ }
+
+ hash = name.hash();
+ idx = hash % number_of_colors;
+
+ color.red = GNOME_COLOR_PALETTE[idx,0];
+ color.green = GNOME_COLOR_PALETTE[idx,1];
+ color.blue = GNOME_COLOR_PALETTE[idx,2];
+
+ return color;
+ }
+}
diff --git a/src/contacts-avatar.vala b/src/contacts-avatar.vala
index b3bec2f..d5d0c25 100644
--- a/src/contacts-avatar.vala
+++ b/src/contacts-avatar.vala
@@ -26,18 +26,13 @@ using Gee;
public class Contacts.Avatar : DrawingArea {
private int size;
private Gdk.Pixbuf? pixbuf = null;
- private Cairo.Surface? cache = null;
+ private Gdk.Pixbuf? cache = null;
private Contact? contact = null;
// We want to lazily load the Pixbuf to make sure we don't draw all contact avatars at once.
// As long as there is no need for it to be drawn, keep this to false.
private bool avatar_loaded = false;
- // The background color used in case of a fallback avatar
- private Gdk.RGBA? bg_color = null;
- // The color used for an initial or the fallback icon
- private const Gdk.RGBA fg_color = { 0, 0, 0, 0.25 };
-
public Avatar (int size, Contact? contact = null) {
this.contact = contact;
if (contact != null) {
@@ -96,69 +91,80 @@ public class Contacts.Avatar : DrawingArea {
}
private void draw_cached_avatar (Cairo.Context cr) {
- cr.set_source_surface (this.cache, 0, 0);
+ Gdk.cairo_set_source_pixbuf (cr, this.cache, 0, 0);
cr.paint ();
}
- private Cairo.Surface create_contact_avatar () {
- Cairo.ImageSurface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
- Cairo.Context cr = new Cairo.Context (surface);
- Gdk.cairo_set_source_pixbuf (cr, this.pixbuf, 0, 0);
- // Clip with a circle
- create_circle (cr);
- cr.clip_preserve ();
- cr.paint ();
- return surface;
+ private Gdk.Pixbuf create_contact_avatar () {
+ return AvatarUtils.round_image(this.pixbuf);
}
- private Cairo.Surface create_fallback () {
- Cairo.ImageSurface surface = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
- Cairo.Context cr = new Cairo.Context (surface);
- // The background color
- if (this.bg_color == null)
- calculate_color ();
+ private Gdk.Pixbuf create_fallback () {
+ string name = "";
+ bool show_label = false;
+ if (this.contact != null && this.contact.individual != null) {
+ name = find_display_name ();
+ /* If we don't have a usable name use the display_name
+ * to generate the color but don't show any label
+ */
+ if (name == "") {
+ name = this.contact.individual.display_name;
+ } else {
+ show_label = true;
+ }
+ }
+ var pixbuf = AvatarUtils.generate_user_picture(name, this.size, show_label);
+ pixbuf = AvatarUtils.round_image(pixbuf);
- // Fill the background circle
- cr.set_source_rgb (this.bg_color.red, this.bg_color.green, this.bg_color.blue);
- cr.arc (this.size / 2, this.size / 2, this.size / 2, 0, 2*Math.PI);
- create_circle (cr);
- cr.fill_preserve ();
+ return pixbuf;
+ }
- // Draw the icon
- try {
- var theme = IconTheme.get_default ();
- var fallback_avatar = theme.lookup_icon ("avatar-default",
- this.size * 4 / 5,
- IconLookupFlags.FORCE_SYMBOLIC);
- var icon_pixbuf = fallback_avatar.load_symbolic (fg_color);
- create_circle (cr);
- cr.clip_preserve ();
- Gdk.cairo_set_source_pixbuf (cr, icon_pixbuf, 1 + this.size / 10, 1 + this.size / 5);
- cr.paint ();
- } catch (Error e) {
- warning ("Couldn't get default avatar icon: %s", e.message);
+ /* Find a nice name to generate the label and color for the fallback avatar
+ * This code is mostly copied from folks, but folks also tries email and phone number
+ * as a display name which we don't want to have as a label
+ */
+ private string find_display_name () {
+ string name = "";
+ Persona primary_persona = null;
+ foreach (var p in this.contact.individual.personas) {
+ if (p.store.is_primary_store) {
+ primary_persona = p;
+ break;
+ }
+ }
+ name = look_up_alias_for_display_name (primary_persona);
+ if (name == "") {
+ foreach (var p in this.contact.individual.personas) {
+ name = look_up_alias_for_display_name (p);
+ }
+ }
+ if (name == "") {
+ foreach (var p in this.contact.individual.personas) {
+ name = look_up_name_details_for_display_name (p);
+ }
}
- return surface;
+ return name;
}
- private void calculate_color () {
- // We use the hash of the id so we get the same color each time for the same contact
- var hash = (this.contact != null)? str_hash (this.contact.individual.id) : Gdk.CURRENT_TIME;
-
- var r = ((hash & 0xFF0000) >> 16) / 255.0;
- var g = ((hash & 0x00FF00) >> 8) / 255.0;
- var b = (hash & 0x0000FF) / 255.0;
-
- // Make it a bit lighter by default (and since the foreground will be darker)
- this.bg_color = Gdk.RGBA () {
- red = (r + 2) / 3.0,
- green = (g + 2) / 3.0,
- blue = (b + 2) / 3.0,
- alpha = 0
- };
+ private string look_up_alias_for_display_name (Persona? p) {
+ var a = p as AliasDetails;
+ if (a != null && a.alias != null)
+ return a.alias;
+
+ return "";
}
- private void create_circle (Cairo.Context cr) {
- cr.arc (this.size / 2, this.size / 2, this.size / 2, 0, 2*Math.PI);
+ private string look_up_name_details_for_display_name (Persona? p) {
+ var n = p as NameDetails;
+ if (n != null) {
+ if (n.full_name != null && n.full_name != "")
+ return n.full_name;
+ else if (n.structured_name != null)
+ return n.structured_name.to_string ();
+ else if (n.nickname != "")
+ return n.nickname;
+ }
+
+ return "";
}
}
diff --git a/src/meson.build b/src/meson.build
index 773c3dc..0401f78 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -14,6 +14,7 @@ libcontacts_sources = files(
'contacts-typeset.vala',
'contacts-type-descriptor.vala',
'contacts-utils.vala',
+ 'contacts-avatar-utils.vala',
'contacts-vcard-type-mapping.vala',
)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]