1 /*
2  * Copyright (C) 2020 Purism SPC
3  * Copyright (C) 2020 Felipe Borges
4  *
5  * Authors:
6  * Felipe Borges <felipeborges@gnome.org>
7  * Julian Sparber <julian@sparber.net>
8  *
9  * SPDX-License-Identifier: LGPL-2.1-or-later
10  *
11  */
12 
13 #include "config.h"
14 #include <math.h>
15 
16 #include "adw-avatar.h"
17 #include "adw-gizmo-private.h"
18 
19 #define NUMBER_OF_COLORS 14
20 /**
21  * AdwAvatar:
22  *
23  * A widget displaying an image, with a generated fallback.
24  *
25  * `AdwAvatar` is a widget that shows a round avatar.
26  *
27  * `AdwAvatar` generates an avatar with the initials of  the
28  * [property@Adw.Avatar:text] on top of a colored background.
29  *
30  * The color is picked based on the hash of the [property@Adw.Avatar:text].
31  *
32  * If [property@Adw.Avatar:show-initials] is set to `FALSE`,
33  * [property@Adw.Avatar:icon-name] or `avatar-default-symbolic` is shown instead
34  * of the initials.
35  *
36  * Use [property@Adw.Avatar:custom-image] to set a custom image.
37  *
38  * ## CSS nodes
39  *
40  * `AdwAvatar` has a single CSS node with name `avatar`.
41  *
42  * Since: 1.0
43  */
44 
45 G_DEFINE_AUTOPTR_CLEANUP_FUNC (cairo_t, cairo_destroy);
46 G_DEFINE_AUTOPTR_CLEANUP_FUNC (cairo_surface_t, cairo_surface_destroy);
47 
48 struct _AdwAvatar
49 {
50   GtkWidget parent_instance;
51 
52   GtkWidget *gizmo;
53   GtkLabel *label;
54   GtkImage *icon;
55   GtkImage *custom_image;
56 
57   char *icon_name;
58   char *text;
59   gboolean show_initials;
60   guint color_class;
61   int size;
62 };
63 
64 G_DEFINE_TYPE (AdwAvatar, adw_avatar, GTK_TYPE_WIDGET);
65 
66 enum {
67   PROP_0,
68   PROP_ICON_NAME,
69   PROP_TEXT,
70   PROP_SHOW_INITIALS,
71   PROP_CUSTOM_IMAGE,
72   PROP_SIZE,
73   PROP_LAST_PROP,
74 };
75 static GParamSpec *props[PROP_LAST_PROP];
76 
77 static char *
extract_initials_from_text(const char * text)78 extract_initials_from_text (const char *text)
79 {
80   GString *initials;
81   g_autofree char *p = g_utf8_strup (text, -1);
82   g_autofree char *normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE);
83   gunichar unichar;
84   char *q = NULL;
85 
86   if (normalized == NULL)
87     return NULL;
88 
89   initials = g_string_new ("");
90 
91   unichar = g_utf8_get_char (normalized);
92   g_string_append_unichar (initials, unichar);
93 
94   q = g_utf8_strrchr (normalized, -1, ' ');
95   if (q != NULL && g_utf8_next_char (q) != NULL) {
96     q = g_utf8_next_char (q);
97 
98     unichar = g_utf8_get_char (q);
99     g_string_append_unichar (initials, unichar);
100   }
101 
102   return g_string_free (initials, FALSE);
103 }
104 
105 static void
update_visibility(AdwAvatar * self)106 update_visibility (AdwAvatar *self)
107 {
108   gboolean has_custom_image = gtk_image_get_paintable (self->custom_image) != NULL;
109   gboolean has_initials = self->show_initials && self->text && strlen (self->text);
110 
111   gtk_widget_set_visible (GTK_WIDGET (self->label), !has_custom_image && has_initials);
112   gtk_widget_set_visible (GTK_WIDGET (self->icon), !has_custom_image && !has_initials);
113   gtk_widget_set_visible (GTK_WIDGET (self->custom_image), has_custom_image);
114 }
115 
116 static void
set_class_color(AdwAvatar * self)117 set_class_color (AdwAvatar *self)
118 {
119   g_autofree GRand *rand = NULL;
120   g_autofree char *new_class = NULL;
121   g_autofree char *old_class = g_strdup_printf ("color%d", self->color_class);
122 
123   gtk_widget_remove_css_class (self->gizmo, old_class);
124 
125   if (self->text == NULL || strlen (self->text) == 0) {
126     /* Use a random color if we don't have a text */
127     rand = g_rand_new ();
128     self->color_class = g_rand_int_range (rand, 1, NUMBER_OF_COLORS);
129   } else {
130     self->color_class = (g_str_hash (self->text) % NUMBER_OF_COLORS) + 1;
131   }
132 
133   new_class = g_strdup_printf ("color%d", self->color_class);
134 
135   gtk_widget_add_css_class (self->gizmo, new_class);
136 }
137 
138 static void
update_initials(AdwAvatar * self)139 update_initials (AdwAvatar *self)
140 {
141   g_autofree char *initials = NULL;
142 
143   if (gtk_image_get_paintable (self->custom_image) != NULL ||
144       !self->show_initials ||
145       self->text == NULL ||
146       strlen (self->text) == 0)
147     return;
148 
149   initials = extract_initials_from_text (self->text);
150 
151   gtk_label_set_label (self->label, initials);
152 }
153 
154 static void
update_icon(AdwAvatar * self)155 update_icon (AdwAvatar *self)
156 {
157   if (self->icon_name)
158     gtk_image_set_from_icon_name (self->icon, self->icon_name);
159   else
160     gtk_image_set_from_icon_name (self->icon, "avatar-default-symbolic");
161 }
162 
163 static void
update_font_size(AdwAvatar * self)164 update_font_size (AdwAvatar *self)
165 {
166   int width, height;
167   double padding;
168   double sqr_size;
169   double max_size;
170   double new_font_size;
171   PangoAttrList *attributes;
172 
173   if (gtk_image_get_paintable (self->custom_image) != NULL ||
174       !self->show_initials ||
175       self->text == NULL ||
176       strlen (self->text) == 0)
177     return;
178 
179   /* Reset font size first to avoid rounding errors */
180   attributes = pango_attr_list_new ();
181   gtk_label_set_attributes (self->label, attributes);
182 
183   pango_layout_get_pixel_size (gtk_label_get_layout (self->label), &width, &height);
184 
185   /* This is the size of the biggest square fitting inside the circle */
186   sqr_size = (double) self->size / 1.4142;
187   /* The padding has to be a function of the overall size.
188    * The 0.4 is how steep the linear function grows and the -5 is just
189    * an adjustment for smaller sizes which doesn't have a big impact on bigger sizes.
190    * Make also sure we don't have a negative padding */
191   padding = MAX (self->size * 0.4 - 5, 0);
192   max_size = sqr_size - padding;
193   new_font_size = (double) height * (max_size / (double) width);
194 
195   pango_attr_list_change (attributes, pango_attr_size_new_absolute (CLAMP (new_font_size, 0, max_size) * PANGO_SCALE));
196   gtk_label_set_attributes (self->label, attributes);
197 
198   pango_attr_list_unref (attributes);
199 }
200 
201 static void
adw_avatar_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)202 adw_avatar_get_property (GObject    *object,
203                          guint       property_id,
204                          GValue     *value,
205                          GParamSpec *pspec)
206 {
207   AdwAvatar *self = ADW_AVATAR (object);
208 
209   switch (property_id) {
210   case PROP_ICON_NAME:
211     g_value_set_string (value, adw_avatar_get_icon_name (self));
212     break;
213 
214   case PROP_TEXT:
215     g_value_set_string (value, adw_avatar_get_text (self));
216     break;
217 
218   case PROP_SHOW_INITIALS:
219     g_value_set_boolean (value, adw_avatar_get_show_initials (self));
220     break;
221 
222   case PROP_CUSTOM_IMAGE:
223     g_value_set_object (value, adw_avatar_get_custom_image (self));
224     break;
225 
226   case PROP_SIZE:
227     g_value_set_int (value, adw_avatar_get_size (self));
228     break;
229 
230   default:
231     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
232     break;
233   }
234 }
235 
236 static void
adw_avatar_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)237 adw_avatar_set_property (GObject      *object,
238                          guint         property_id,
239                          const GValue *value,
240                          GParamSpec   *pspec)
241 {
242   AdwAvatar *self = ADW_AVATAR (object);
243 
244   switch (property_id) {
245   case PROP_ICON_NAME:
246     adw_avatar_set_icon_name (self, g_value_get_string (value));
247     break;
248 
249   case PROP_TEXT:
250     adw_avatar_set_text (self, g_value_get_string (value));
251     break;
252 
253   case PROP_SHOW_INITIALS:
254     adw_avatar_set_show_initials (self, g_value_get_boolean (value));
255     break;
256 
257   case PROP_CUSTOM_IMAGE:
258     adw_avatar_set_custom_image (self, g_value_get_object (value));
259     break;
260 
261   case PROP_SIZE:
262     adw_avatar_set_size (self, g_value_get_int (value));
263     break;
264 
265   default:
266     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
267     break;
268   }
269 }
270 
271 static void
adw_avatar_dispose(GObject * object)272 adw_avatar_dispose (GObject *object)
273 {
274   AdwAvatar *self = ADW_AVATAR (object);
275 
276   g_clear_pointer (&self->gizmo, gtk_widget_unparent);
277 
278   self->label = NULL;
279   self->icon = NULL;
280   self->custom_image = NULL;
281 
282   G_OBJECT_CLASS (adw_avatar_parent_class)->dispose (object);
283 }
284 
285 static void
adw_avatar_finalize(GObject * object)286 adw_avatar_finalize (GObject *object)
287 {
288   AdwAvatar *self = ADW_AVATAR (object);
289 
290   g_clear_pointer (&self->icon_name, g_free);
291   g_clear_pointer (&self->text, g_free);
292 
293   G_OBJECT_CLASS (adw_avatar_parent_class)->finalize (object);
294 }
295 
296 static void
adw_avatar_class_init(AdwAvatarClass * klass)297 adw_avatar_class_init (AdwAvatarClass *klass)
298 {
299   GObjectClass *object_class = G_OBJECT_CLASS (klass);
300   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
301 
302   object_class->dispose = adw_avatar_dispose;
303   object_class->finalize = adw_avatar_finalize;
304   object_class->set_property = adw_avatar_set_property;
305   object_class->get_property = adw_avatar_get_property;
306 
307   /**
308    * AdwAvatar:icon-name: (attributes org.gtk.Property.get=adw_avatar_get_icon_name org.gtk.Property.set=adw_avatar_set_icon_name)
309    *
310    * The name of an icon to use as a fallback.
311    *
312    * If no name is set, `avatar-default-symbolic` will be used.
313    *
314    * Since: 1.0
315    */
316   props[PROP_ICON_NAME] =
317     g_param_spec_string ("icon-name",
318                          "Icon name",
319                          "The name of an icon to use as a fallback",
320                          NULL,
321                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
322 
323   /**
324    * AdwAvatar:text: (attributes org.gtk.Property.get=adw_avatar_get_text org.gtk.Property.set=adw_avatar_set_text)
325    *
326    * Sets the text used to generate the fallback initials and color.
327    *
328    * It's only used to generate the color if [property@Adw.Avatar:show-initials]
329    * is `FALSE`.
330    *
331    * Since: 1.0
332    */
333   props[PROP_TEXT] =
334     g_param_spec_string ("text",
335                          "Text",
336                          "The text used to generate the color and the initials",
337                          "",
338                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
339 
340   /**
341    * AdwAvatar:show-initials: (attributes org.gtk.Property.get=adw_avatar_get_show_initials org.gtk.Property.set=adw_avatar_set_show_initials)
342    *
343    * Whether initials are used instead of an icon on the fallback avatar.
344    *
345    * See [property@Adw.Avatar:icon-name] for how to change the fallback icon.
346    *
347    * Since: 1.0
348    */
349   props[PROP_SHOW_INITIALS] =
350     g_param_spec_boolean ("show-initials",
351                           "Show initials",
352                           "Whether initials are used instead of an icon on the fallback avatar",
353                           FALSE,
354                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
355 
356   /**
357    * AdwAvatar:custom-image: (attributes org.gtk.Property.get=adw_avatar_get_custom_image org.gtk.Property.set=adw_avatar_set_custom_image)
358    *
359    * A custom image to use instead of initials or icon.
360    *
361    * Since: 1.0
362    */
363   props[PROP_CUSTOM_IMAGE] =
364     g_param_spec_object ("custom-image",
365                          "Custom image",
366                          "A custom image to use instead of initials or icon",
367                          GDK_TYPE_PAINTABLE,
368                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
369 
370   /**
371    * AdwAvatar:size: (attributes org.gtk.Property.get=adw_avatar_get_size org.gtk.Property.set=adw_avatar_set_size)
372    *
373    * The size of the avatar.
374    *
375    * Since: 1.0
376    */
377   props[PROP_SIZE] =
378     g_param_spec_int ("size",
379                       "Size",
380                       "The size of the avatar",
381                       -1, INT_MAX, -1,
382                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
383 
384   g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
385 
386   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
387 }
388 
389 static void
adw_avatar_init(AdwAvatar * self)390 adw_avatar_init (AdwAvatar *self)
391 {
392   self->gizmo = adw_gizmo_new ("avatar", NULL, NULL, NULL, NULL, NULL, NULL);
393   gtk_widget_set_overflow (self->gizmo, GTK_OVERFLOW_HIDDEN);
394   gtk_widget_set_halign (self->gizmo, GTK_ALIGN_CENTER);
395   gtk_widget_set_valign (self->gizmo, GTK_ALIGN_CENTER);
396   gtk_widget_set_layout_manager (self->gizmo, gtk_bin_layout_new ());
397   gtk_widget_set_parent (self->gizmo, GTK_WIDGET (self));
398 
399   self->label = GTK_LABEL (gtk_label_new (NULL));
400   gtk_widget_set_parent (GTK_WIDGET (self->label), self->gizmo);
401 
402   self->icon = GTK_IMAGE (gtk_image_new ());
403   gtk_widget_set_parent (GTK_WIDGET (self->icon), self->gizmo);
404 
405   self->custom_image = GTK_IMAGE (gtk_image_new ());
406   gtk_widget_set_parent (GTK_WIDGET (self->custom_image), self->gizmo);
407 
408   self->text = g_strdup ("");
409 
410   set_class_color (self);
411   update_initials (self);
412   update_font_size (self);
413   update_icon (self);
414   update_visibility (self);
415 
416   g_signal_connect (self, "notify::root", G_CALLBACK (update_font_size), NULL);
417 }
418 
419 /**
420  * adw_avatar_new:
421  * @size: The size of the avatar
422  * @text: (nullable): the text used to get the initials and color
423  * @show_initials: whether to use initials instead of an icon as fallback
424  *
425  * Creates a new `AdwAvatar`.
426  *
427  * Returns: the newly created `AdwAvatar`
428  *
429  * Since: 1.0
430  */
431 GtkWidget *
adw_avatar_new(int size,const char * text,gboolean show_initials)432 adw_avatar_new (int         size,
433                 const char *text,
434                 gboolean    show_initials)
435 {
436   return g_object_new (ADW_TYPE_AVATAR,
437                        "size", size,
438                        "text", text,
439                        "show-initials", show_initials,
440                        NULL);
441 }
442 
443 /**
444  * adw_avatar_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
445  * @self: a `AdwAvatar`
446  *
447  * Gets the name of an icon to use as a fallback.
448  *
449  * Returns: (nullable): the icon name
450  *
451  * Since: 1.0
452  */
453 const char *
adw_avatar_get_icon_name(AdwAvatar * self)454 adw_avatar_get_icon_name (AdwAvatar *self)
455 {
456   g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
457 
458   return self->icon_name;
459 }
460 
461 /**
462  * adw_avatar_set_icon_name: (attributes org.gtk.Method.set_property=icon-name)
463  * @self: a `AdwAvatar`
464  * @icon_name: (nullable): the icon name
465  *
466  * Sets the name of an icon to use as a fallback.
467  *
468  * If no name is set, `avatar-default-symbolic` will be used.
469  *
470  * Since: 1.0
471  */
472 void
adw_avatar_set_icon_name(AdwAvatar * self,const char * icon_name)473 adw_avatar_set_icon_name (AdwAvatar  *self,
474                           const char *icon_name)
475 {
476   g_return_if_fail (ADW_IS_AVATAR (self));
477 
478   if (g_strcmp0 (self->icon_name, icon_name) == 0)
479     return;
480 
481   g_clear_pointer (&self->icon_name, g_free);
482   self->icon_name = g_strdup (icon_name);
483 
484   update_icon (self);
485 
486   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
487 }
488 
489 /**
490  * adw_avatar_get_text: (attributes org.gtk.Method.get_property=text)
491  * @self: a `AdwAvatar`
492  *
493  * Gets the text used to generate the fallback initials and color.
494  *
495  * Returns: (nullable): the text used to generate the fallback initials and
496  *   color
497  *
498  * Since: 1.0
499  */
500 const char *
adw_avatar_get_text(AdwAvatar * self)501 adw_avatar_get_text (AdwAvatar *self)
502 {
503   g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
504 
505   return self->text;
506 }
507 
508 /**
509  * adw_avatar_set_text: (attributes org.gtk.Method.set_property=text)
510  * @self: a `AdwAvatar`
511  * @text: (nullable): the text used to get the initials and color
512  *
513  * Sets the text used to generate the fallback initials and color.
514  *
515  * Since: 1.0
516  */
517 void
adw_avatar_set_text(AdwAvatar * self,const char * text)518 adw_avatar_set_text (AdwAvatar  *self,
519                      const char *text)
520 {
521   g_return_if_fail (ADW_IS_AVATAR (self));
522 
523   if (g_strcmp0 (self->text, text) == 0)
524     return;
525 
526   g_clear_pointer (&self->text, g_free);
527   self->text = g_strdup (text ? text : "");
528 
529   set_class_color (self);
530 
531   update_initials (self);
532   update_font_size (self);
533   update_visibility (self);
534 
535   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TEXT]);
536 }
537 
538 /**
539  * adw_avatar_get_show_initials: (attributes org.gtk.Method.get_property=show-initials)
540  * @self: a `AdwAvatar`
541  *
542  * Gets whether initials are used instead of an icon on the fallback avatar.
543  *
544  * Returns: whether initials are used instead of an icon as fallback
545  *
546  * Since: 1.0
547  */
548 gboolean
adw_avatar_get_show_initials(AdwAvatar * self)549 adw_avatar_get_show_initials (AdwAvatar *self)
550 {
551   g_return_val_if_fail (ADW_IS_AVATAR (self), FALSE);
552 
553   return self->show_initials;
554 }
555 
556 /**
557  * adw_avatar_set_show_initials: (attributes org.gtk.Method.set_property=show-initials)
558  * @self: a `AdwAvatar`
559  * @show_initials: whether to use initials instead of an icon as fallback
560  *
561  * Sets whether to use initials instead of an icon on the fallback avatar.
562  *
563  * Since: 1.0
564  */
565 void
adw_avatar_set_show_initials(AdwAvatar * self,gboolean show_initials)566 adw_avatar_set_show_initials (AdwAvatar *self,
567                               gboolean   show_initials)
568 {
569   g_return_if_fail (ADW_IS_AVATAR (self));
570 
571   if (self->show_initials == show_initials)
572     return;
573 
574   self->show_initials = show_initials;
575 
576   update_initials (self);
577   update_font_size (self);
578   update_visibility (self);
579 
580   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_INITIALS]);
581 }
582 
583 /**
584  * adw_avatar_get_custom_image: (attributes org.gtk.Method.get_property=custom-image)
585  * @self: a `AdwAvatar`
586  *
587  * Gets the custom image paintable.
588  *
589  * Returns: (nullable) (transfer none): the custom image
590  *
591  * Since: 1.0
592  */
593 GdkPaintable *
adw_avatar_get_custom_image(AdwAvatar * self)594 adw_avatar_get_custom_image (AdwAvatar *self)
595 {
596   g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
597 
598   return gtk_image_get_paintable (self->custom_image);
599 }
600 
601 /**
602  * adw_avatar_set_custom_image: (attributes org.gtk.Method.set_property=custom-image)
603  * @self: a `AdwAvatar`
604  * @custom_image: (nullable) (transfer none): a custom image
605  *
606  * Sets the custom image paintable.
607  *
608  * Since: 1.0
609  */
610 void
adw_avatar_set_custom_image(AdwAvatar * self,GdkPaintable * custom_image)611 adw_avatar_set_custom_image (AdwAvatar    *self,
612                              GdkPaintable *custom_image)
613 {
614   g_return_if_fail (ADW_IS_AVATAR (self));
615   g_return_if_fail (GDK_IS_PAINTABLE (custom_image) || custom_image == NULL);
616 
617   if (gtk_image_get_paintable (self->custom_image) == custom_image)
618     return;
619 
620   gtk_image_set_from_paintable (self->custom_image, custom_image);
621 
622   if (custom_image)
623     gtk_widget_add_css_class (self->gizmo, "image");
624   else
625     gtk_widget_remove_css_class (self->gizmo, "image");
626 
627   update_visibility (self);
628 
629   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUSTOM_IMAGE]);
630 }
631 
632 /**
633  * adw_avatar_get_size: (attributes org.gtk.Method.get_property=size)
634  * @self: a `AdwAvatar`
635  *
636  * Gets the size of the avatar.
637  *
638  * Returns: the size of the avatar
639  *
640  * Since: 1.0
641  */
642 int
adw_avatar_get_size(AdwAvatar * self)643 adw_avatar_get_size (AdwAvatar *self)
644 {
645   g_return_val_if_fail (ADW_IS_AVATAR (self), 0);
646 
647   return self->size;
648 }
649 
650 /**
651  * adw_avatar_set_size: (attributes org.gtk.Method.set_property=size)
652  * @self: a `AdwAvatar`
653  * @size: The size of the avatar
654  *
655  * Sets the size of the avatar.
656  *
657  * Since: 1.0
658  */
659 void
adw_avatar_set_size(AdwAvatar * self,int size)660 adw_avatar_set_size (AdwAvatar *self,
661                      int        size)
662 {
663   g_return_if_fail (ADW_IS_AVATAR (self));
664   g_return_if_fail (size >= -1);
665 
666   if (self->size == size)
667     return;
668 
669   self->size = size;
670 
671   gtk_widget_set_size_request (self->gizmo, size, size);
672   gtk_image_set_pixel_size (self->icon, size / 2);
673 
674   if (size < 25)
675     gtk_widget_add_css_class (self->gizmo, "contrasted");
676   else
677     gtk_widget_remove_css_class (self->gizmo, "contrasted");
678 
679   update_font_size (self);
680 
681   gtk_widget_queue_resize (GTK_WIDGET (self));
682   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIZE]);
683 }
684 
685 /**
686  * adw_avatar_draw_to_pixbuf:
687  * @self: a `AdwAvatar`
688  * @size: The size of the pixbuf
689  * @scale_factor: The scale factor
690  *
691  * Renders @self into a [class@GdkPixbuf.Pixbuf] at @size and @scale_factor.
692  *
693  * This can be used to export the fallback avatar.
694  *
695  * Returns: (transfer full): the pixbuf
696  *
697  * Since: 1.0
698  */
699 GdkPixbuf *
adw_avatar_draw_to_pixbuf(AdwAvatar * self,int size,int scale_factor)700 adw_avatar_draw_to_pixbuf (AdwAvatar *self,
701                            int        size,
702                            int        scale_factor)
703 {
704   GtkSnapshot *snapshot;
705   g_autoptr (GskRenderNode) node = NULL;
706   g_autoptr (cairo_surface_t) surface = NULL;
707   g_autoptr (cairo_t) cr = NULL;
708   graphene_rect_t bounds;
709 
710   g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
711   g_return_val_if_fail (size > 0, NULL);
712   g_return_val_if_fail (scale_factor > 0, NULL);
713 
714   snapshot = gtk_snapshot_new ();
715   GTK_WIDGET_GET_CLASS (self)->snapshot (GTK_WIDGET (self), snapshot);
716 
717   node = gtk_snapshot_free_to_node (snapshot);
718 
719   gsk_render_node_get_bounds (node, &bounds);
720   graphene_rect_round_to_pixel (&bounds);
721   graphene_rect_scale (&bounds, scale_factor, scale_factor, &bounds);
722 
723   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
724                                         bounds.size.width,
725                                         bounds.size.height);
726   cairo_surface_set_device_scale (surface, scale_factor, scale_factor);
727   cr = cairo_create (surface);
728 
729   cairo_translate (cr, -bounds.origin.x, -bounds.origin.y);
730 
731   gsk_render_node_draw (node, cr);
732 
733   return gdk_pixbuf_get_from_surface (surface, 0, 0,
734                                       bounds.size.width,
735                                       bounds.size.height);
736 }
737