aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui/avatar_drawer.vala
blob: c14d7fda30c8c4aa92e19ab4619aa8982caef9ec (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using Cairo;
using Gee;
using Gdk;
using Gtk;
using Xmpp.Util;

namespace Dino.Ui {

public class AvatarDrawer {
    public const string GRAY = "555753";

    private Gee.List<AvatarTile> tiles = new ArrayList<AvatarTile>();
    private int height = 35;
    private int width = 35;
    private bool gray;
    private int base_factor = 1;
    private string font_family = "Sans";

    public AvatarDrawer size(int height, int width = height) {
        this.height = height;
        this.width = width;
        return this;
    }

    public AvatarDrawer grayscale() {
        this.gray = true;
        return this;
    }

    public AvatarDrawer tile(Pixbuf? image, string? name, string? hex_color) {
        tiles.add(new AvatarTile(image, name, hex_color));
        return this;
    }

    public AvatarDrawer plus() {
        tiles.add(new AvatarTile(null, "…", GRAY));
        return this;
    }

    public AvatarDrawer scale(int base_factor) {
        this.base_factor = base_factor;
        return this;
    }

    public AvatarDrawer font(string font_family) {
        this.font_family = font_family;
        return this;
    }

    public ImageSurface draw_image_surface() {
        ImageSurface surface = new ImageSurface(Format.ARGB32, width, height);
        draw_on_context(new Context(surface));
        return surface;
    }

    public void draw_on_context(Cairo.Context ctx) {
        double radius = 3 * base_factor;
        double degrees = Math.PI / 180.0;
        ctx.new_sub_path();
        ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees);
        ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees);
        ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees);
        ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees);
        ctx.close_path();
        ctx.clip();

        if (this.tiles.size == 4) {
            Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
            Cairo.Context bufctx = new Cairo.Context(buffer);
            bufctx.scale(0.5, 0.5);
            bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0);
            bufctx.paint();
            bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0);
            bufctx.paint();
            bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1);
            bufctx.paint();
            bufctx.set_source_surface(sub_surface_idx(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1);
            bufctx.paint();

            ctx.set_source_surface(buffer, 0, 0);
            ctx.paint();
        } else if (this.tiles.size == 3) {
            Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
            Cairo.Context bufctx = new Cairo.Context(buffer);
            bufctx.scale(0.5, 0.5);
            bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0);
            bufctx.paint();
            bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0);
            bufctx.paint();
            bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1);
            bufctx.paint();

            ctx.set_source_surface(buffer, 0, 0);
            ctx.paint();
        } else if (this.tiles.size == 2) {
            Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
            Cairo.Context bufctx = new Cairo.Context(buffer);
            bufctx.scale(0.5, 0.5);
            bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0);
            bufctx.paint();
            bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0);
            bufctx.paint();

            ctx.set_source_surface(buffer, 0, 0);
            ctx.paint();
        } else if (this.tiles.size == 1) {
            ctx.set_source_surface(sub_surface_idx(ctx, 0, width, height, base_factor), 0, 0);
            ctx.paint();
        } else if (this.tiles.size == 0) {
            ctx.set_source_surface(sub_surface_idx(ctx, -1, width, height, base_factor), 0, 0);
            ctx.paint();
        }

        if (gray) {
            // convert to greyscale
            ctx.set_operator(Cairo.Operator.HSL_COLOR);
            ctx.set_source_rgb(1, 1, 1);
            ctx.rectangle(0, 0, width, height);
            ctx.fill();
            // make the visible part more light
            ctx.set_operator(Cairo.Operator.ATOP);
            ctx.set_source_rgba(1, 1, 1, 0.7);
            ctx.rectangle(0, 0, width, height);
            ctx.fill();
        }
        ctx.set_source_rgb(0, 0, 0);
    }

    private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) {
        Gdk.Pixbuf? avatar = idx >= 0 ? tiles[idx].image : null;
        string? name = idx >= 0 ? tiles[idx].name : "";
        string hex_color = !gray && idx >= 0 ? tiles[idx].hex_color : GRAY;
        return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor);
    }

    private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, string? hex_color, int width, int height, int font_factor = 1) {
        Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
        Cairo.Context bufctx = new Cairo.Context(buffer);
        if (avatar == null) {
            set_source_hex_color(bufctx, hex_color ?? GRAY);
            bufctx.rectangle(0, 0, width, height);
            bufctx.fill();

            string text = name == null ? "…" : name.get_char(0).toupper().to_string();
            bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
            bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25);
            Cairo.TextExtents extents;
            bufctx.text_extents(text, out extents);
            double x_pos = width/2 - (extents.width/2 + extents.x_bearing);
            double y_pos = height/2 - (extents.height/2 + extents.y_bearing);
            bufctx.move_to(x_pos, y_pos);
            bufctx.set_source_rgba(1, 1, 1, 1);
            bufctx.show_text(text);
        } else {
            double w_scale = (double) width / avatar.width;
            double h_scale = (double) height / avatar.height;
            double scale = double.max(w_scale, h_scale);
            bufctx.scale(scale, scale);

            double x_off = 0, y_off = 0;
            if (scale == h_scale) {
                x_off = (width / scale - avatar.width) / 2.0;
            } else {
                y_off = (height / scale - avatar.height) / 2.0;
            }
            Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off);
            bufctx.get_source().set_filter(Cairo.Filter.BEST);
            bufctx.paint();
        }
        return buffer;
    }

    private static void set_source_hex_color(Cairo.Context ctx, string hex_color) {
        ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255,
                    (double) from_hex(hex_color.substring(2, 2)) / 255,
                    (double) from_hex(hex_color.substring(4, 2)) / 255,
                    hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1);
    }
}

private class AvatarTile {
    public Pixbuf? image { get; private set; }
    public string? name { get; private set; }
    public string? hex_color { get; private set; }

    public AvatarTile(Pixbuf? image, string? name, string? hex_color) {
        this.image = image;
        this.name = name;
        this.hex_color = hex_color;
    }
}

}