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