blob: 99e653be348001f646d8eaeb802abee9328da3f1 [file] [log] [blame]
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05001/*
Guillaume Roguez2a6150d2017-07-19 18:24:47 -04002 * Copyright (C) 2015-2017 Savoir-faire Linux Inc.
Stepan Salenikovich297b5d12015-02-26 17:51:13 -05003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050018 */
19
20#include "drawing.h"
21
22#include <gtk/gtk.h>
23#include <math.h>
AmarOkb37e92b2017-07-19 09:40:00 -040024#include <algorithm>
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050025
Stepan Salenikovichd8765072016-01-14 10:58:51 -050026static constexpr const char* MSG_COUNT_FONT = "Sans";
27static constexpr int MSG_COUNT_FONT_SIZE = 12;
28static constexpr GdkRGBA MSG_COUNT_FONT_COLOUR = {1.0, 1.0, 1.0, 1.0}; // white
Nicolas Jagerd64d88e2017-12-11 14:26:00 -050029static constexpr GdkRGBA MSG_COUNT_BACKGROUND = {0.984, 0.282, 0.278, 1.0}; // red 251, 72, 71, 1.0
AmarOkb37e92b2017-07-19 09:40:00 -040030static constexpr GdkRGBA PRESENCE_PRESENT_BACKGROUND = {0, 0.4156827, 0.8, 1.0}; // green 112, 217, 6, 0.9
31static constexpr GdkRGBA PRESENCE_ABSENT_BACKGROUND = {0.984, 0.282, 0.278, 1.0}; // red 251, 72, 71, 0.9
Sébastien Blin9684dd32017-07-24 11:15:19 -040032// This is the color palette for default avatars
33static constexpr GdkRGBA COLOR_PALETTE[] = {{0.956862, 0.262745, 0.211764, 1.0}, // red 244, green 67, blue 54, 1 (red)
34 {0.913725, 0.117647, 0.388235, 1.0}, // red 233, green 30, blue 99, 1 (pink)
35 {0.611764, 0.152941, 0.690196, 1.0}, // red 156, green 39, blue 176, 1 (purple)
36 {0.403921, 0.227450, 0.717648, 1.0}, // red 244, green 67, blue 54, 1 (deep purple)
37 {0.247058, 0.317647, 0.709803, 1.0}, // red 103, green 58, blue 183, 1 (indigo)
38 {0.129411, 0.588235, 0.952941, 1.0}, // red 63, green 81, blue 54, 1 (blue)
39 {0, 0.838254, 0.831372, 1.0}, // red 0, green 188, blue 212, 1 (cyan)
40 {0, 0.588235, 0.533333, 1.0}, // red 0, green 150, blue 136, 1 (teal)
41 {0.298039, 0.682745, 0.313725, 1.0}, // red 244, green 67, blue 54, 1 (green)
42 {0.545098, 0.764705, 0.290196, 1.0}, // red 138, green 194, blue 73, 1 (light green)
43 {0.619607, 0.619607, 0.619607, 1.0}, // red 157, green 157, blue 157, 1 (grey)
44 {0.803921, 0.862745, 0.223529, 1.0}, // red 204, green 219, blue 56, 1 (lime)
45 {1, 0.756862, 0.027450, 1.0}, // red 255, green 192, blue 6, 1 (amber)
46 {1, 0.341176, 0.133333, 1.0}, // red 255, green 86, blue 33, 1 (deep orange)
47 {0.474509, 0.333333, 0.282352, 1.0}, // red 120, green 84, blue 71, 1 (brown)
48 {0.376470, 0.490196, 0.545098, 1.0}};// red 95, green 124, blue 138, 1 (blue grey)
Stepan Salenikovichd8765072016-01-14 10:58:51 -050049
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050050GdkPixbuf *
Sébastien Blin9684dd32017-07-24 11:15:19 -040051ring_draw_fallback_avatar(int size, const char letter, const char color) {
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050052 cairo_surface_t *surface;
53 cairo_t *cr;
54
Sébastien Blin9684dd32017-07-24 11:15:19 -040055 // Fill the background
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050056 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size);
57 cr = cairo_create(surface);
Sébastien Blin9684dd32017-07-24 11:15:19 -040058 auto bg_color = COLOR_PALETTE[color % 16];
59 cairo_set_source_rgb (cr, bg_color.red, bg_color.green, bg_color.blue);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050060 cairo_paint(cr);
61
Sébastien Blin9684dd32017-07-24 11:15:19 -040062 // Draw a letter at the center of the avatar
63 cairo_text_extents_t extents;
64 cairo_select_font_face (cr, "monospace", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
65 cairo_set_font_size(cr, size / 2);
66 cairo_set_source_rgb (cr, 1, 1, 1);
67 char first_letter[2] = {0};
68 first_letter[0] = letter;
69 cairo_text_extents (cr, first_letter, &extents);
70 auto x = size/2-(extents.width/2 + extents.x_bearing);
71 auto y = size/2-(extents.height/2 + extents.y_bearing);
72 cairo_move_to (cr, x, y);
73 cairo_show_text(cr, first_letter);
Stepan Salenikovich2beca292015-08-17 17:03:21 -040074
Sébastien Blin9684dd32017-07-24 11:15:19 -040075 GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(cairo_get_target(cr), 0, 0, size, size);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050076
77 /* free resources */
78 cairo_destroy(cr);
79 cairo_surface_destroy(surface);
80
81 return pixbuf;
82}
83
84GdkPixbuf *
Stepan Salenikovichee8506e2015-08-13 11:35:14 -040085ring_draw_conference_avatar(int size) {
86 cairo_surface_t *surface;
87 cairo_t *cr;
88
89 surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size);
90 cr = cairo_create(surface);
91
92 cairo_pattern_t *linpat = cairo_pattern_create_linear(0, 0, 0, size);
93 cairo_pattern_add_color_stop_rgb(linpat, 0, 0.937, 0.937, 0.937);
94 cairo_pattern_add_color_stop_rgb(linpat, 1, 0.969, 0.969, 0.969);
95
96 cairo_set_source(cr, linpat);
97 cairo_paint(cr);
98
99 int avatar_size = size * 0.5;
100 GtkIconInfo *icon_info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), "system-users-symbolic",
101 avatar_size, GTK_ICON_LOOKUP_GENERIC_FALLBACK);
102 GdkPixbuf *pixbuf_icon = gtk_icon_info_load_icon(icon_info, NULL);
103 g_object_unref(icon_info);
104
105 if (pixbuf_icon != NULL) {
106 gdk_cairo_set_source_pixbuf(cr, pixbuf_icon, (size - avatar_size) / 2, (size - avatar_size) / 2);
107 g_object_unref(pixbuf_icon);
108 cairo_rectangle(cr, (size - avatar_size) / 2, (size - avatar_size) / 2, avatar_size, avatar_size);
109 cairo_fill(cr);
110 }
111
112 GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, size, size);
113
114 /* free resources */
115 cairo_destroy(cr);
116 cairo_surface_destroy(surface);
117
118 return pixbuf;
119}
120
Sébastien Blin3367b662017-07-24 11:15:19 -0400121#include <iostream>
AmarOkb37e92b2017-07-19 09:40:00 -0400122
Stepan Salenikovichee8506e2015-08-13 11:35:14 -0400123GdkPixbuf *
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500124ring_frame_avatar(GdkPixbuf *avatar) {
AmarOkb37e92b2017-07-19 09:40:00 -0400125
126 auto w = gdk_pixbuf_get_width(avatar);
127 auto h = gdk_pixbuf_get_height(avatar);
Sébastien Blin3367b662017-07-24 11:15:19 -0400128 auto crop_size = std::min(h, w);
129 auto new_size = std::max(h, w);
130 auto scale = (double)new_size/(double)crop_size;
131 GdkPixbuf *crop_avatar = gdk_pixbuf_new (
132 gdk_pixbuf_get_colorspace (avatar),
133 gdk_pixbuf_get_has_alpha (avatar),
134 gdk_pixbuf_get_bits_per_sample (avatar),
135 new_size, new_size);
AmarOkb37e92b2017-07-19 09:40:00 -0400136 gdk_pixbuf_scale (avatar, crop_avatar, 0, 0, new_size, new_size,
Sébastien Blin3367b662017-07-24 11:15:19 -0400137 (w/2)-(new_size/2), (h/2)-(new_size/2), scale, scale,
AmarOkb37e92b2017-07-19 09:40:00 -0400138 GDK_INTERP_BILINEAR);
139 auto extra_space = 10;
140 auto offset = extra_space/2;
141 auto s_surface = new_size + extra_space;
142 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, s_surface, s_surface);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500143 cairo_t *cr = cairo_create(surface);
144
145 cairo_set_source_rgba(cr, 0, 0, 0, 0);
AmarOkb37e92b2017-07-19 09:40:00 -0400146 cairo_rectangle(cr, 0, 0, s_surface, s_surface);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500147 cairo_fill(cr);
148
AmarOkb37e92b2017-07-19 09:40:00 -0400149 double radius = new_size/2;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500150 double degrees = M_PI / 180.0;
151
Stepan Salenikovich89e3d9f2016-06-06 11:57:31 -0400152 // create the square path with ronded corners
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500153 cairo_new_sub_path (cr);
AmarOkb37e92b2017-07-19 09:40:00 -0400154 cairo_arc (cr, offset + new_size - radius, offset + radius, radius, -90 * degrees, 0 * degrees);
155 cairo_arc (cr, offset + new_size - radius, offset + new_size - radius, radius, 0 * degrees, 90 * degrees);
156 cairo_arc (cr, offset + radius, offset + new_size - radius, radius, 90 * degrees, 180 * degrees);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500157 cairo_arc (cr, offset + radius, offset + radius, radius, 180 * degrees, 270 * degrees);
158 cairo_close_path (cr);
159
Stepan Salenikovich89e3d9f2016-06-06 11:57:31 -0400160 // in case the image has alpha, we want to first set the background of the part inside the
Stepan Salenikovich1dc123f2016-09-14 10:43:10 -0400161 // blue frame to white; otherwise the resulting image will show whatever is in the background,
Stepan Salenikovich89e3d9f2016-06-06 11:57:31 -0400162 // which can be weird in certain cases (eg: the image displayed over a video)
Stepan Salenikovich1dc123f2016-09-14 10:43:10 -0400163 cairo_set_source_rgba(cr, 1, 1, 1, 1);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500164 cairo_fill_preserve(cr);
165
Stepan Salenikovich89e3d9f2016-06-06 11:57:31 -0400166 // now draw the image over this black square
AmarOkb37e92b2017-07-19 09:40:00 -0400167 gdk_cairo_set_source_pixbuf(cr, crop_avatar, offset, offset);
Stepan Salenikovich89e3d9f2016-06-06 11:57:31 -0400168 cairo_fill_preserve(cr);
169
AmarOkb37e92b2017-07-19 09:40:00 -0400170 auto pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, s_surface, s_surface);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500171
172 /* free resources */
173 cairo_destroy(cr);
174 cairo_surface_destroy(surface);
175
176 return pixbuf;
Stepan Salenikovich2beca292015-08-17 17:03:21 -0400177}
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500178
179static void
180create_rounded_rectangle_path(cairo_t *cr, double corner_radius, double x, double y, double w, double h)
181{
182 double radius = corner_radius;
183 double degrees = M_PI / 180.0;
184
185 cairo_new_sub_path (cr);
186 cairo_arc (cr, x + w - radius, y + radius, radius, -90 * degrees, 0 * degrees);
187 cairo_arc (cr, x + w - radius, y + h - radius, radius, 0 * degrees, 90 * degrees);
188 cairo_arc (cr, x + radius, y + h - radius, radius, 90 * degrees, 180 * degrees);
189 cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
190 cairo_close_path (cr);
191}
192
193/**
aviauc372e812016-12-01 16:13:16 -0500194 * Draws the presence icon in the top right corner of the given image.
195 */
196GdkPixbuf *
197ring_draw_presence(const GdkPixbuf *avatar, bool present) {
198 if (!present) {
199 // simply return a copy of the original pixbuf
200 return gdk_pixbuf_copy(avatar);
201 }
202
203 int w = gdk_pixbuf_get_width(avatar);
204 int h = gdk_pixbuf_get_height(avatar);
205 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
206 cairo_t *cr = cairo_create(surface);
207 cairo_surface_destroy(surface);
208
209 /* draw original image */
210 gdk_cairo_set_source_pixbuf(cr, avatar, 0, 0);
211 cairo_paint(cr);
212
213 /* draw rounded rectangle, with 3 pixel border
214 * ie: 6 pixels higher, 6 pixels wider */
215 int border_width = 5;
AmarOkb37e92b2017-07-19 09:40:00 -0400216 double rec_x = w - border_width * 3;
217 double rec_y = h - border_width * 3;
aviauc372e812016-12-01 16:13:16 -0500218 double rec_w = border_width * 2;
219 double rec_h = border_width * 2;
220 double corner_radius = rec_h/2.5;
221 create_rounded_rectangle_path(cr, corner_radius, rec_x, rec_y, rec_w, rec_h);
222
223 // For now we don't draw the absent background.
224 auto background = present ? PRESENCE_PRESENT_BACKGROUND : PRESENCE_ABSENT_BACKGROUND;
225 cairo_set_source_rgba(
226 cr,
227 background.red,
228 background.blue,
229 background.green,
230 background.alpha
231 );
AmarOkb37e92b2017-07-19 09:40:00 -0400232 cairo_fill_preserve(cr);
233 cairo_set_source_rgb(cr, 1, 1, 1);
234 cairo_set_line_width(cr, 1.2);
235 cairo_stroke(cr);
aviauc372e812016-12-01 16:13:16 -0500236
237 GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, w, h);
238
239 /* free resources */
240 cairo_destroy(cr);
241
242 return pixbuf;
243}
244
245/**
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500246 * Draws the unread message count in the bottom right corner of the given image.
247 * In the case that the count is less than or equal to 0, nothing is drawn.
248 */
249GdkPixbuf *
250ring_draw_unread_messages(const GdkPixbuf *avatar, int unread_count) {
251 if (unread_count <= 0) {
252 // simply return a copy of the original pixbuf
253 return gdk_pixbuf_copy(avatar);
254 }
255 int w = gdk_pixbuf_get_width(avatar);
256 int h = gdk_pixbuf_get_height(avatar);
257 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
258 cairo_t *cr = cairo_create(surface);
259 cairo_surface_destroy(surface);
260
261 /* draw original image */
262 gdk_cairo_set_source_pixbuf(cr, avatar, 0, 0);
263 cairo_paint(cr);
264
265 /* make text */
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500266 char *text = g_strdup_printf("%s", unread_count > 9 ? "9+" : std::to_string(unread_count).c_str());
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500267 cairo_text_extents_t extents;
268
269 cairo_select_font_face (cr, MSG_COUNT_FONT,
270 CAIRO_FONT_SLANT_NORMAL,
271 CAIRO_FONT_WEIGHT_NORMAL);
272
273 cairo_set_font_size (cr, MSG_COUNT_FONT_SIZE);
274 cairo_text_extents (cr, text, &extents);
275
276 /* draw rounded rectangle around the text, with 3 pixel border
277 * ie: 6 pixels higher, 6 pixels wider */
278 int border_width = 3;
279 double rec_x = w - extents.width - border_width * 2;
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500280 double rec_y = 0;
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500281 double rec_w = extents.width + border_width * 2;
282 double rec_h = extents.height + border_width * 2;
283 double corner_radius = rec_h/2.5;
284 create_rounded_rectangle_path(cr, corner_radius, rec_x, rec_y, rec_w, rec_h);
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500285 cairo_set_source_rgba(cr,
286 MSG_COUNT_BACKGROUND.red,
287 MSG_COUNT_BACKGROUND.blue, MSG_COUNT_BACKGROUND.green, MSG_COUNT_BACKGROUND.alpha);
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500288 cairo_fill(cr);
289
290 /* draw text */
Nicolas Jagerd64d88e2017-12-11 14:26:00 -0500291 cairo_move_to (cr, w - extents.width-border_width, extents.height + border_width );
Stepan Salenikovichd8765072016-01-14 10:58:51 -0500292 cairo_set_source_rgb(cr, MSG_COUNT_FONT_COLOUR.red, MSG_COUNT_FONT_COLOUR.blue, MSG_COUNT_FONT_COLOUR.green);
293 cairo_show_text (cr, text);
294
295 GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, w, h);
296
297 /* free resources */
298 cairo_destroy(cr);
299 g_free(text);
300
301 return pixbuf;
302}