blob: 7a3b6d957f18a0714a4fb97ab4d1bf526b490534 [file] [log] [blame]
Edric Milareta0ebd062016-01-13 12:18:23 -05001/***************************************************************************
Anthony Léonard2fde81d2017-04-17 10:06:55 -04002 * Copyright (C) 2015-2017 by Savoir-faire Linux *
Edric Milareta0ebd062016-01-13 12:18:23 -05003 * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com>*
Olivier SOLDANO47aa97f2017-04-04 10:40:00 -04004 * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com> *
5 * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com> *
Andreas Traczyk072291f2018-08-16 16:48:58 -04006 * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> *
Edric Milareta0ebd062016-01-13 12:18:23 -05007 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 3 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
20 **************************************************************************/
21
22#include "pixbufmanipulator.h"
23
Edric Milaret25236d92016-03-28 09:40:58 -040024#include <QSize>
25#include <QMetaType>
26#include <QImage>
27#include <QIODevice>
28#include <QByteArray>
29#include <QBuffer>
Anthony Léonard3d920d82017-07-31 13:51:16 -040030#include <QPainter>
Andreas Traczyk072291f2018-08-16 16:48:58 -040031#include <QCryptographicHash>
Edric Milareta0ebd062016-01-13 12:18:23 -050032
Edric Milaret25236d92016-03-28 09:40:58 -040033#include "person.h"
34#include "call.h"
35#include "contactmethod.h"
36#include "profilemodel.h"
37#include "profile.h"
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040038#include "globalinstances.h"
39
40 // new LRC
41#include <api/contactmodel.h>
42#include <api/conversation.h>
43#include <api/account.h>
44#include <api/contact.h>
Edric Milaret25236d92016-03-28 09:40:58 -040045
46#include "utils.h"
Anthony Léonard3d920d82017-07-31 13:51:16 -040047#include "ringthemeutils.h"
Edric Milaret25236d92016-03-28 09:40:58 -040048#undef interface
49
Andreas Traczyk072291f2018-08-16 16:48:58 -040050static const QSize IMAGE_SIZE {128, 128};
51
52QColor
53getAvatarColor(const QString& canonicalUri) {
54 if (canonicalUri.isEmpty()) {
55 return RingTheme::defaultAvatarColor_;
56 }
57 auto h = QString(QCryptographicHash::hash(canonicalUri.toLocal8Bit(), QCryptographicHash::Md5).toHex());
58 if (h.isEmpty() || h.isNull()) {
59 return RingTheme::defaultAvatarColor_;
60 }
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040061 auto colorIndex = std::string("0123456789abcdef").find(h.at(0).toLatin1());
Andreas Traczyk072291f2018-08-16 16:48:58 -040062 return RingTheme::avatarColors_[colorIndex];
63}
Guillaume Roguez79cab802017-08-25 09:59:13 -040064
Guillaume Roguez79cab802017-08-25 09:59:13 -040065// Generate a QImage representing a dummy user avatar, when user doesn't provide it.
66// Current rendering is a flat colored circle with a centered letter.
67// The color of the letter is computed from the circle color to be visible whaterver be the circle color.
Guillaume Roguez79cab802017-08-25 09:59:13 -040068static QImage
Andreas Traczyk072291f2018-08-16 16:48:58 -040069fallbackAvatar(const QSize size, const QString& canonicalUriStr, const QString& letterStr = QString())
Guillaume Roguez79cab802017-08-25 09:59:13 -040070{
71 // We start with a transparent avatar
72 QImage avatar(size, QImage::Format_ARGB32);
73 avatar.fill(Qt::transparent);
74
75 // We pick a color based on the passed character
Andreas Traczyk072291f2018-08-16 16:48:58 -040076 QColor avColor = getAvatarColor(canonicalUriStr);
Guillaume Roguez79cab802017-08-25 09:59:13 -040077
78 // We draw a circle with this color
79 QPainter painter(&avatar);
Andreas Traczyk072291f2018-08-16 16:48:58 -040080 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
Guillaume Roguez79cab802017-08-25 09:59:13 -040081 painter.setPen(Qt::transparent);
Andreas Traczyk072291f2018-08-16 16:48:58 -040082 painter.setBrush(avColor.lighter(110));
Guillaume Roguez79cab802017-08-25 09:59:13 -040083 painter.drawEllipse(avatar.rect());
84
Andreas Traczyk072291f2018-08-16 16:48:58 -040085 // If a letter was passed, then we paint a letter in the circle,
86 // otherwise we draw the default avatar icon
Andreas Traczykb8b13ba2018-08-21 16:30:16 -040087 QString letterStrCleaned(letterStr);
88 letterStrCleaned.remove(QRegExp("[\\n\\t\\r]"));
Andreas Traczyk072291f2018-08-16 16:48:58 -040089 if (!letterStr.isEmpty()) {
90 auto letter = letterStr.at(0).toUpper().toLatin1();
91 QFont font("Arial", avatar.height() / 2.66667, QFont::Medium);
92 painter.setFont(font);
93 painter.setPen(Qt::white);
94 painter.drawText(avatar.rect(), QString(letter), QTextOption(Qt::AlignCenter));
95 } else {
96 QRect overlayRect = avatar.rect();
97 qreal margin = (0.05 * overlayRect.width());
98 overlayRect.moveLeft(overlayRect.left() + margin * 0.5);
99 overlayRect.moveTop(overlayRect.top() + margin * 0.5);
100 overlayRect.setWidth(overlayRect.width() - margin);
101 overlayRect.setHeight(overlayRect.height() - margin);
Andreas Traczyk912242e2018-10-29 14:44:44 -0400102 painter.drawPixmap(overlayRect, QPixmap(":/images/default_avatar_overlay.svg"));
Andreas Traczyk072291f2018-08-16 16:48:58 -0400103 }
Guillaume Roguez79cab802017-08-25 09:59:13 -0400104
105 return avatar;
106}
107
Andreas Traczyk072291f2018-08-16 16:48:58 -0400108QImage
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400109fallbackAvatar(const QSize size, const ContactMethod* cm)
Guillaume Roguez79cab802017-08-25 09:59:13 -0400110{
Andreas Traczyk072291f2018-08-16 16:48:58 -0400111 if (cm == nullptr) {
112 return QImage();
113 }
114 QImage image;
115 auto letterStr = QString();
116 if (cm->uri().userinfo() != cm->bestName()) {
117 letterStr = cm->bestName();
118 }
119 image = fallbackAvatar( size,
120 cm->uri().full(),
121 letterStr);
122 return image;
Guillaume Roguez79cab802017-08-25 09:59:13 -0400123}
124
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400125QImage
126fallbackAvatar(const QSize size, const std::string& alias, const std::string& uri)
127{
128 return fallbackAvatar(size,
129 QString::fromStdString(uri),
130 QString::fromStdString(alias));
131}
132
Edric Milaret25236d92016-03-28 09:40:58 -0400133/*Namespace Interfaces collide with QBuffer*/
134QByteArray QImageToByteArray(QImage image)
135{
136 QByteArray ba;
137 QBuffer buffer(&ba);
138 buffer.open(QIODevice::WriteOnly);
139 image.save(&buffer, "PNG");
140 return ba;
141}
Edric Milareta0ebd062016-01-13 12:18:23 -0500142
Edric Milareta0ebd062016-01-13 12:18:23 -0500143QImage
144PixbufManipulator::scaleAndFrame(const QImage photo, const QSize& size)
145{
146 return photo.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
147}
148
149QVariant
150PixbufManipulator::callPhoto(Call* c, const QSize& size, bool displayPresence)
151{
Olivier SOLDANOad0fabb2016-11-25 09:08:01 -0500152 if (!c || c->type() == Call::Type::CONFERENCE){
Anthony Léonard3d920d82017-07-31 13:51:16 -0400153 return QVariant::fromValue(fallbackAvatar(size,
Andreas Traczyk072291f2018-08-16 16:48:58 -0400154 c->peerContactMethod()->uri().full(),
Guillaume Roguez79cab802017-08-25 09:59:13 -0400155 c->peerContactMethod()->bestName()));
Olivier SOLDANOad0fabb2016-11-25 09:08:01 -0500156 }
Edric Milareta0ebd062016-01-13 12:18:23 -0500157 return callPhoto(c->peerContactMethod(), size, displayPresence);
158}
159
160QVariant
Andreas Traczyk072291f2018-08-16 16:48:58 -0400161PixbufManipulator::callPhoto(const ContactMethod* cm, const QSize& size, bool displayPresence)
Edric Milareta0ebd062016-01-13 12:18:23 -0500162{
Andreas Traczyk072291f2018-08-16 16:48:58 -0400163 if (cm && cm->contact()) {
164 return contactPhoto(cm->contact(), size, displayPresence);
Edric Milareta0ebd062016-01-13 12:18:23 -0500165 } else {
Andreas Traczyk072291f2018-08-16 16:48:58 -0400166 return QVariant::fromValue(fallbackAvatar(size, cm));
Edric Milareta0ebd062016-01-13 12:18:23 -0500167 }
168}
169
170QVariant
Andreas Traczyk072291f2018-08-16 16:48:58 -0400171PixbufManipulator::contactPhoto(Person* p, const QSize& size, bool displayPresence)
Edric Milareta0ebd062016-01-13 12:18:23 -0500172{
173 Q_UNUSED(displayPresence);
Edric Milareta0ebd062016-01-13 12:18:23 -0500174 QImage photo;
Andreas Traczyk072291f2018-08-16 16:48:58 -0400175 if (p->photo().isValid()) {
176 photo = p->photo().value<QImage>();
Olivier SOLDANOad0fabb2016-11-25 09:08:01 -0500177 } else {
Andreas Traczyk072291f2018-08-16 16:48:58 -0400178 photo = fallbackAvatar(IMAGE_SIZE, p->phoneNumbers().at(0));
Olivier SOLDANOad0fabb2016-11-25 09:08:01 -0500179 }
Edric Milareta0ebd062016-01-13 12:18:23 -0500180 return QVariant::fromValue(scaleAndFrame(photo, size));
181}
182
183QVariant PixbufManipulator::personPhoto(const QByteArray& data, const QString& type)
184{
Edric Milaret25236d92016-03-28 09:40:58 -0400185 QImage avatar;
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400186 const bool ret = avatar.loadFromData(QByteArray::fromBase64(data), type.toLatin1());
187 if (!ret) {
188 qDebug() << "vCard image loading failed";
189 return QVariant();
190 }
191 return QPixmap::fromImage(Utils::getCirclePhoto(avatar, avatar.size().width()));
Edric Milareta0ebd062016-01-13 12:18:23 -0500192}
193
194QVariant
195PixbufManipulator::numberCategoryIcon(const QVariant& p, const QSize& size, bool displayPresence, bool isPresent)
196{
197 Q_UNUSED(p)
198 Q_UNUSED(size)
199 Q_UNUSED(displayPresence)
200 Q_UNUSED(isPresent)
201 return QVariant();
202}
203
204QVariant
205PixbufManipulator::securityIssueIcon(const QModelIndex& index)
206{
207 Q_UNUSED(index)
208 return QVariant();
209}
210
211QByteArray
212PixbufManipulator::toByteArray(const QVariant& pxm)
213{
Edric Milaret25236d92016-03-28 09:40:58 -0400214 auto image = pxm.value<QImage>();
215 QByteArray ba = QImageToByteArray(image);
216 return ba;
Edric Milareta0ebd062016-01-13 12:18:23 -0500217}
218
219QVariant
Olivier SOLDANO41e61ab2017-08-04 16:47:16 -0400220PixbufManipulator::collectionIcon(const CollectionInterface* colItf, PixmapManipulatorI::CollectionIconHint hint) const
Edric Milareta0ebd062016-01-13 12:18:23 -0500221{
Olivier SOLDANO41e61ab2017-08-04 16:47:16 -0400222 Q_UNUSED(colItf)
Edric Milareta0ebd062016-01-13 12:18:23 -0500223 Q_UNUSED(hint)
224 return QVariant();
225}
226QVariant
227PixbufManipulator::securityLevelIcon(const SecurityEvaluationModel::SecurityLevel level) const
228{
229 Q_UNUSED(level)
230 return QVariant();
231}
232QVariant
233PixbufManipulator::historySortingCategoryIcon(const CategorizedHistoryModel::SortedProxy::Categories cat) const
234{
235 Q_UNUSED(cat)
236 return QVariant();
237}
238QVariant
239PixbufManipulator::contactSortingCategoryIcon(const CategorizedContactModel::SortedProxy::Categories cat) const
240{
241 Q_UNUSED(cat)
242 return QVariant();
243}
244QVariant
245PixbufManipulator::userActionIcon(const UserActionElement& state) const
246{
247 Q_UNUSED(state)
248 return QVariant();
249}
250
251QVariant PixbufManipulator::decorationRole(const QModelIndex& index)
252{
253 Q_UNUSED(index)
254 return QVariant();
255}
256
257QVariant PixbufManipulator::decorationRole(const Call* c)
258{
Edric Milareta13b48c2016-01-18 14:42:35 -0500259 QImage photo;
Andreas Traczyk072291f2018-08-16 16:48:58 -0400260 if (c && c->peerContactMethod()
Edric Milareta0ebd062016-01-13 12:18:23 -0500261 && c->peerContactMethod()->contact()
262 && c->peerContactMethod()->contact()->photo().isValid()) {
Edric Milareta13b48c2016-01-18 14:42:35 -0500263 photo = c->peerContactMethod()->contact()->photo().value<QImage>();
Andreas Traczyk072291f2018-08-16 16:48:58 -0400264 } else {
265 fallbackAvatar(IMAGE_SIZE, c->peerContactMethod());
Edric Milareta0ebd062016-01-13 12:18:23 -0500266 }
Guillaume Roguez79cab802017-08-25 09:59:13 -0400267 return QVariant::fromValue(scaleAndFrame(photo, IMAGE_SIZE));
Edric Milareta0ebd062016-01-13 12:18:23 -0500268}
269
270QVariant PixbufManipulator::decorationRole(const ContactMethod* cm)
271{
Edric Milareta13b48c2016-01-18 14:42:35 -0500272 QImage photo;
Andreas Traczyk072291f2018-08-16 16:48:58 -0400273 if (cm && cm->contact()
274 && cm->contact()->photo().isValid()) {
Edric Milareta13b48c2016-01-18 14:42:35 -0500275 photo = cm->contact()->photo().value<QImage>();
Olivier SOLDANO6c46d352017-08-24 10:54:55 -0400276 } else {
Andreas Traczyk072291f2018-08-16 16:48:58 -0400277 photo = fallbackAvatar(IMAGE_SIZE, cm);
Olivier SOLDANO6c46d352017-08-24 10:54:55 -0400278 }
Guillaume Roguez79cab802017-08-25 09:59:13 -0400279 return QVariant::fromValue(scaleAndFrame(photo, IMAGE_SIZE));
Edric Milareta0ebd062016-01-13 12:18:23 -0500280}
281
282QVariant PixbufManipulator::decorationRole(const Person* p)
283{
Edric Milareta13b48c2016-01-18 14:42:35 -0500284 QImage photo;
Andreas Traczyk072291f2018-08-16 16:48:58 -0400285 if (p && p->photo().isValid()) {
Edric Milareta13b48c2016-01-18 14:42:35 -0500286 photo = p->photo().value<QImage>();
Andreas Traczyk072291f2018-08-16 16:48:58 -0400287 } else {
288 photo = fallbackAvatar(IMAGE_SIZE, p->phoneNumbers().at(0));
289 }
Guillaume Roguez79cab802017-08-25 09:59:13 -0400290 return QVariant::fromValue(scaleAndFrame(photo, IMAGE_SIZE));
Edric Milareta0ebd062016-01-13 12:18:23 -0500291}
292
Edric Milareta7cf8652016-04-19 15:37:51 -0400293QVariant PixbufManipulator::decorationRole(const Account* acc)
294{
295 Q_UNUSED(acc)
Edric Milaret25236d92016-03-28 09:40:58 -0400296 return Utils::getCirclePhoto(ProfileModel::instance().
Guillaume Roguez79cab802017-08-25 09:59:13 -0400297 selectedProfile()->person()->photo().value<QImage>(),
298 IMAGE_SIZE.width());
Anthony Léonard3d920d82017-07-31 13:51:16 -0400299}
Andreas Traczykb8b13ba2018-08-21 16:30:16 -0400300
301QVariant
302PixbufManipulator::decorationRole(const lrc::api::conversation::Info & conversationInfo,
303 const lrc::api::account::Info & accountInfo)
304{
305 QImage photo;
306 auto contacts = conversationInfo.participants;
307 if (!contacts.empty()) {
308 try {
309 // Get first contact photo
310 auto contactUri = contacts.front();
311 auto contactInfo = accountInfo.contactModel->getContact(contactUri);
312 auto contactPhoto = contactInfo.profileInfo.avatar;
313 auto bestName = Utils::bestNameForContact(contactInfo);
314 auto bestId = Utils::bestIdForContact(contactInfo);
315 if (accountInfo.profileInfo.type == lrc::api::profile::Type::SIP &&
316 contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY) {
317 photo = fallbackAvatar(IMAGE_SIZE, QString(), QString());
318 }
319 else if (accountInfo.profileInfo.type == lrc::api::profile::Type::SIP) {
320 photo = fallbackAvatar(IMAGE_SIZE,
321 QString::fromStdString("sip:" + bestId),
322 QString());
323 }
324 else if (contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY && contactInfo.profileInfo.uri.empty()) {
325 photo = fallbackAvatar(IMAGE_SIZE, QString(), QString());
326 }
327 else if (!contactPhoto.empty()) {
328 QByteArray byteArray(contactPhoto.c_str(), contactPhoto.length());
329 photo = personPhoto(byteArray, nullptr).value<QImage>();
330 }
331 else {
332 auto avatarName = contactInfo.profileInfo.uri == bestName ?
333 QString() :
334 QString::fromStdString(bestName);
335 photo = fallbackAvatar(IMAGE_SIZE,
336 QString::fromStdString("ring:" + bestId),
337 avatarName);
338 }
339 }
340 catch (...) {}
341 }
342 return QVariant::fromValue(scaleAndFrame(photo, IMAGE_SIZE));
343}
344
345QVariant
346PixbufManipulator::accountPhoto(const lrc::api::account::Info& accountInfo)
347{
348 QImage photo;
349 if (!accountInfo.profileInfo.avatar.empty()) {
350 QByteArray ba = QByteArray::fromStdString(accountInfo.profileInfo.avatar);
351 photo = GlobalInstances::pixmapManipulator().personPhoto(ba, nullptr).value<QImage>();
352 }
353 else {
354 auto bestId = Utils::bestIdForAccount(accountInfo);
355 auto bestName = Utils::bestNameForAccount(accountInfo);
356 photo = fallbackAvatar( IMAGE_SIZE,
357 QString::fromStdString("ring:" + bestId),
358 QString::fromStdString(bestName));
359 }
360 return QVariant::fromValue(scaleAndFrame(photo, IMAGE_SIZE));
361}