blob: dcda3c33fe5490410007694722455fbbbca068c0 [file] [log] [blame]
Alexandre Lision3b0bd332015-03-15 18:43:07 -04001/*
Alexandre Lision9fe374b2016-01-06 10:17:31 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Alexandre Lision3b0bd332015-03-15 18:43:07 -04003 * Author: Alexandre Lision <alexandre.lision@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.
Alexandre Lision3b0bd332015-03-15 18:43:07 -040018 */
19#import "ImageManipulationDelegate.h"
20
21#import <Cocoa/Cocoa.h>
22#import <Foundation/Foundation.h>
23
24//Qt
25#import <QSize>
26#import <QBuffer>
27#import <QtGui/QColor>
28#import <QtGui/QPainter>
Alexandre Lision4f264622016-05-08 17:08:56 -040029#import <QHash>
Alexandre Lision3b0bd332015-03-15 18:43:07 -040030#import <QtGui/QBitmap>
31#import <QtWidgets/QApplication>
32#import <QtGui/QImage>
33#import <QtMacExtras/qmacfunctions.h>
34#import <QtGui/QPalette>
35
36//Ring
37#import <person.h>
Alexandre Lision43e91bc2016-04-19 18:04:52 -040038#import <profilemodel.h>
39#import <profile.h>
Alexandre Lision3b0bd332015-03-15 18:43:07 -040040#import <contactmethod.h>
Alexandre Lision3b0bd332015-03-15 18:43:07 -040041
Alexandre Lision7a166e42015-09-02 15:04:43 -040042namespace Interfaces {
Alexandre Lision3b0bd332015-03-15 18:43:07 -040043
Anthony Léonardc40b3472017-08-01 15:19:17 -040044 // Colors from material.io
45 const QColor ImageManipulationDelegate::avatarColors_[] = {
46 {"#fff44336"}, //Red
47 {"#ffe91e63"}, //Pink
48 {"#ff9c27b0"}, //Purple
49 {"#ff673ab7"}, //Deep Purple
50 {"#ff3f51b5"}, //Indigo
51 {"#ff2196f3"}, //Blue
52 {"#ff00bcd4"}, //Cyan
53 {"#ff009688"}, //Teal
54 {"#ff4caf50"}, //Green
55 {"#ff8bc34a"}, //Light Green
56 {"#ff9e9e9e"}, //Grey
57 {"#ffcddc39"}, //Lime
58 {"#ffffc107"}, //Amber
59 {"#ffff5722"}, //Deep Orange
60 {"#ff795548"}, //Brown
61 {"#ff607d8b"} //Blue Grey
62 };
63
Alexandre Lision4f264622016-05-08 17:08:56 -040064 ImageManipulationDelegate::ImageManipulationDelegate() {}
Alexandre Lision3b0bd332015-03-15 18:43:07 -040065
Alexandre Lision7a166e42015-09-02 15:04:43 -040066 QVariant ImageManipulationDelegate::contactPhoto(Person* c, const QSize& size, bool displayPresence) {
Alexandre Lisionde0314b2015-09-02 15:45:21 -040067 const int radius = size.height() / 2;
Alexandre Lision7a166e42015-09-02 15:04:43 -040068 QPixmap pxm;
Alexandre Lision4dfcafc2015-08-20 12:43:23 -040069 if (c && c->photo().isValid()) {
Alexandre Lision196545b2016-05-13 17:05:13 -040070 // Check cache
71 auto index = QStringLiteral("%1%2%3").arg(size.width())
72 .arg(size.height())
73 .arg(QString::fromUtf8(c->uid()));
74
75 if (m_hContactsPixmap.contains(index)) {
76 return m_hContactsPixmap.value(index).second;
77 }
78
Alexandre Lisionf02a32b2016-04-19 15:01:07 -040079 QPixmap contactPhoto(qvariant_cast<QPixmap>(c->photo()).scaled(size, Qt::KeepAspectRatioByExpanding,
80 Qt::SmoothTransformation));
81
82 QPixmap finalImg;
83 if (contactPhoto.size() != size) {
84 finalImg = crop(contactPhoto, size);
85 } else
86 finalImg = contactPhoto;
87
Alexandre Lision7a166e42015-09-02 15:04:43 -040088 pxm = QPixmap(size);
89 pxm.fill(Qt::transparent);
90 QPainter painter(&pxm);
91
92 //Clear the pixmap
Alexandre Lisionde0314b2015-09-02 15:45:21 -040093 painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
Alexandre Lision7a166e42015-09-02 15:04:43 -040094 painter.setCompositionMode(QPainter::CompositionMode_Clear);
95 painter.fillRect(0,0,size.width(),size.height(),QBrush(Qt::white));
96 painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
97
98 //Add corner radius to the Pixmap
Alexandre Lisionf02a32b2016-04-19 15:01:07 -040099 QRect pxRect = finalImg.rect();
Alexandre Lision7a166e42015-09-02 15:04:43 -0400100 QBitmap mask(pxRect.size());
101 QPainter customPainter(&mask);
Alexandre Lisionde0314b2015-09-02 15:45:21 -0400102 customPainter.setRenderHints (QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
Alexandre Lision7a166e42015-09-02 15:04:43 -0400103 customPainter.fillRect (pxRect , Qt::white );
104 customPainter.setBackground (Qt::black );
105 customPainter.setBrush (Qt::black );
Alexandre Lisionf02a32b2016-04-19 15:01:07 -0400106 customPainter.drawRoundedRect(pxRect,radius,radius );
107 finalImg.setMask (mask );
108 painter.drawPixmap (0,0,finalImg );
Alexandre Lisionde0314b2015-09-02 15:45:21 -0400109 painter.setBrush (Qt::NoBrush );
110 painter.setPen (Qt::black );
111 painter.setCompositionMode (QPainter::CompositionMode_SourceIn);
112 painter.drawRoundedRect(0,0,pxm.height(),pxm.height(),radius,radius);
Alexandre Lision7a166e42015-09-02 15:04:43 -0400113
Alexandre Lision196545b2016-05-13 17:05:13 -0400114 // Save in cache
115 QPair<QMetaObject::Connection, QPixmap> toInsert;
116 toInsert.first = QObject::connect(c,
117 &Person::changed,
118 [=]() {
119 if (c) {
120 auto index = QStringLiteral("%1%2%3").arg(size.width())
121 .arg(size.height())
122 .arg(QString::fromUtf8(c->uid()));
123 if (m_hContactsPixmap.contains(index)) {
124 QObject::disconnect(m_hContactsPixmap.value(index).first);
125 m_hContactsPixmap.remove(index);
126 }
127 }
128 });
129 toInsert.second = pxm;
130 m_hContactsPixmap.insert(index, toInsert);
131
132 } else {
Anthony Léonardc40b3472017-08-01 15:19:17 -0400133 return drawDefaultUserPixmap(size,
134 c->phoneNumbers().at(0)->uri().userinfo().at(0).toLatin1(),
135 c->phoneNumbers().at(0)->bestName().at(0).toUpper().toLatin1());
Alexandre Lision196545b2016-05-13 17:05:13 -0400136 }
Alexandre Lisionafa56dc2016-05-08 17:30:20 -0400137
Alexandre Lision7a166e42015-09-02 15:04:43 -0400138 return pxm;
139 }
140
Alexandre Lisionf02a32b2016-04-19 15:01:07 -0400141 QPixmap
142 ImageManipulationDelegate::crop(QPixmap& photo, const QSize& destSize)
143 {
144 auto initSize = photo.size();
145 float leftDelta = 0;
146 float topDelta = 0;
147
148 if (destSize.height() == initSize.height()) {
149 leftDelta = (destSize.width() - initSize.width()) / 2;
150 } else {
151 topDelta = (destSize.height() - initSize.height()) / 2;
152 }
153
154 float xScale = (float)destSize.width() / initSize.width();
155 float yScale = (float)destSize.height() / initSize.height();
156
157 QRectF destRect(leftDelta, topDelta,
158 initSize.width() - leftDelta, initSize.height() - topDelta);
159
160 destRect.setLeft(leftDelta * xScale);
161 destRect.setTop(topDelta * yScale);
162
163 destRect.setWidth((initSize.width() - leftDelta) * xScale);
164 destRect.setHeight((initSize.height() - topDelta) * yScale);
165
166 return photo.copy(destRect.toRect());
167 }
168
Alexandre Lision7a166e42015-09-02 15:04:43 -0400169 QVariant
170 ImageManipulationDelegate::callPhoto(Call* c, const QSize& size, bool displayPresence)
171 {
172 return callPhoto(c->peerContactMethod(), size, displayPresence);
173 }
174
175 QVariant
176 ImageManipulationDelegate::callPhoto(const ContactMethod* n, const QSize& size, bool displayPresence)
177 {
178 if (n->contact()) {
179 return contactPhoto(n->contact(), size, displayPresence);
180 } else {
Anthony Léonardc40b3472017-08-01 15:19:17 -0400181 return drawDefaultUserPixmap(size,
182 n->uri().userinfo().at(0).toLatin1(),
183 n->bestName().at(0).toUpper().toLatin1());
Alexandre Lision7a166e42015-09-02 15:04:43 -0400184 }
185 }
186
187 QVariant ImageManipulationDelegate::personPhoto(const QByteArray& data, const QString& type)
188 {
189 QImage image;
190 //For now, ENCODING is only base64 and image type PNG or JPG
191 const bool ret = image.loadFromData(QByteArray::fromBase64(data),type.toLatin1());
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400192 if (!ret) {
Alexandre Lision7a166e42015-09-02 15:04:43 -0400193 qDebug() << "vCard image loading failed";
Anthony Léonardc40b3472017-08-01 15:19:17 -0400194 return drawDefaultUserPixmap(decorationSize, '?', '?');
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400195 }
Alexandre Lision7a166e42015-09-02 15:04:43 -0400196
197 return QPixmap::fromImage(image);
198 }
199
200 QByteArray ImageManipulationDelegate::toByteArray(const QVariant& pxm)
201 {
202 //Preparation of our QPixmap
203 QByteArray bArray;
204 QBuffer buffer(&bArray);
205 buffer.open(QIODevice::WriteOnly);
206
207 //PNG ?
Kateryna Kostiukc1907fd2017-05-12 14:07:44 -0400208 (qvariant_cast<QPixmap>(pxm)).scaled({100,100}).save(&buffer, "PNG");
Alexandre Lision7a166e42015-09-02 15:04:43 -0400209 buffer.close();
210
211 return bArray;
212 }
213
Anthony Léonardc40b3472017-08-01 15:19:17 -0400214 QPixmap ImageManipulationDelegate::drawDefaultUserPixmap(const QSize& size, const char color, const char letter) {
215 // We start with a transparent avatar
216 QPixmap avatar(size);
217 avatar.fill(Qt::transparent);
Alexandre Lision4f264622016-05-08 17:08:56 -0400218
Anthony Léonardc40b3472017-08-01 15:19:17 -0400219 // We pick a color based on the passed character
220 QColor avColor = ImageManipulationDelegate::avatarColors_[color % 16];
Alexandre Lision4f264622016-05-08 17:08:56 -0400221
Anthony Léonardc40b3472017-08-01 15:19:17 -0400222 // We draw a circle with this color
223 QPainter painter(&avatar);
224 painter.setRenderHints(QPainter::Antialiasing|QPainter::SmoothPixmapTransform);
225 painter.setPen(Qt::transparent);
226 painter.setBrush(avColor);
227 painter.drawEllipse(avatar.rect());
Alexandre Lision3b0bd332015-03-15 18:43:07 -0400228
Anthony Léonardc40b3472017-08-01 15:19:17 -0400229 // Then we paint a letter in the circle
230 auto font = painter.font();
231 font.setPointSize(avatar.height()/2);
232 painter.setFont(font);
233 painter.setPen(Qt::white);
234 QRect textRect = avatar.rect();
235 painter.drawText(textRect, QString(letter), QTextOption(Qt::AlignCenter));
Alexandre Lision4f264622016-05-08 17:08:56 -0400236
Anthony Léonardc40b3472017-08-01 15:19:17 -0400237 return avatar;
Alexandre Lision3b0bd332015-03-15 18:43:07 -0400238 }
Alexandre Lision7a166e42015-09-02 15:04:43 -0400239
240 CGImageRef ImageManipulationDelegate::resizeCGImage(CGImageRef image, const QSize& size) {
241 // create context, keeping original image properties
Alexandre Lision7a166e42015-09-02 15:04:43 -0400242 CGContextRef context = CGBitmapContextCreate(NULL, size.width(), size.height(),
243 CGImageGetBitsPerComponent(image),
Alexandre Lisiond5229f32015-11-16 11:17:41 -0500244 CGImageGetBytesPerRow(image),
245 CGImageGetColorSpace(image),
246 kCGImageAlphaPremultipliedLast);
Alexandre Lision7a166e42015-09-02 15:04:43 -0400247
248 if(context == NULL)
249 return nil;
250
251 // draw image to context (resizing it)
252 CGContextDrawImage(context, CGRectMake(0, 0, size.width(), size.height()), image);
253 // extract resulting image from context
254 CGImageRef imgRef = CGBitmapContextCreateImage(context);
255 CGContextRelease(context);
256
257 return imgRef;
258 }
259
260 QVariant
261 ImageManipulationDelegate::numberCategoryIcon(const QVariant& p, const QSize& size, bool displayPresence, bool isPresent)
262 {
263 Q_UNUSED(p)
264 Q_UNUSED(size)
265 Q_UNUSED(displayPresence)
266 Q_UNUSED(isPresent)
267 return QVariant();
268 }
269
270 QVariant
271 ImageManipulationDelegate::securityIssueIcon(const QModelIndex& index)
272 {
273 Q_UNUSED(index)
274 return QVariant();
275 }
276
277 QVariant
278 ImageManipulationDelegate::collectionIcon(const CollectionInterface* interface, PixmapManipulatorI::CollectionIconHint hint) const
279 {
280 Q_UNUSED(interface)
281 Q_UNUSED(hint)
282 return QVariant();
283 }
284 QVariant
285 ImageManipulationDelegate::securityLevelIcon(const SecurityEvaluationModel::SecurityLevel level) const
286 {
287 Q_UNUSED(level)
288 return QVariant();
289 }
290 QVariant
291 ImageManipulationDelegate::historySortingCategoryIcon(const CategorizedHistoryModel::SortedProxy::Categories cat) const
292 {
293 Q_UNUSED(cat)
294 return QVariant();
295 }
296 QVariant
297 ImageManipulationDelegate::contactSortingCategoryIcon(const CategorizedContactModel::SortedProxy::Categories cat) const
298 {
299 Q_UNUSED(cat)
300 return QVariant();
301 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400302
Alexandre Lision7a166e42015-09-02 15:04:43 -0400303 QVariant
304 ImageManipulationDelegate::userActionIcon(const UserActionElement& state) const
305 {
306 Q_UNUSED(state)
307 return QVariant();
Alexandre Lision3b0bd332015-03-15 18:43:07 -0400308 }
Alexandre Lision4dfcafc2015-08-20 12:43:23 -0400309
Stepan Salenikovich3a6ea302016-01-08 13:54:34 -0500310 QVariant ImageManipulationDelegate::decorationRole(const QModelIndex& index)
311 {
312 Q_UNUSED(index)
313 return QVariant();
314 }
315
316 QVariant ImageManipulationDelegate::decorationRole(const Call* c)
317 {
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400318 if (c && c->peerContactMethod()
319 && c->peerContactMethod()->contact()) {
320 return contactPhoto(c->peerContactMethod()->contact(), decorationSize);
321 } else
Anthony Léonardc40b3472017-08-01 15:19:17 -0400322 return drawDefaultUserPixmap(decorationSize,
323 c->peerContactMethod()->uri().userinfo().at(0).toLatin1(),
324 c->peerContactMethod()->bestName().at(0).toUpper().toLatin1());
Stepan Salenikovich3a6ea302016-01-08 13:54:34 -0500325 }
326
327 QVariant ImageManipulationDelegate::decorationRole(const ContactMethod* cm)
328 {
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400329 QImage photo;
330 if (cm && cm->contact() && cm->contact()->photo().isValid())
331 return contactPhoto(cm->contact(), decorationSize);
332 else
Anthony Léonardc40b3472017-08-01 15:19:17 -0400333 return drawDefaultUserPixmap(decorationSize,
334 cm->uri().userinfo().at(0).toLatin1(),
335 cm->bestName().at(0).toUpper().toLatin1());
Stepan Salenikovich3a6ea302016-01-08 13:54:34 -0500336 }
337
338 QVariant ImageManipulationDelegate::decorationRole(const Person* p)
339 {
Alexandre Lision43e91bc2016-04-19 18:04:52 -0400340 return contactPhoto(const_cast<Person*>(p), decorationSize);
341 }
342
343 QVariant ImageManipulationDelegate::decorationRole(const Account* acc)
344 {
345 Q_UNUSED(acc)
346 if (auto pro = ProfileModel::instance().selectedProfile())
347 return contactPhoto(pro->person(), decorationSize);
Anthony Léonardc40b3472017-08-01 15:19:17 -0400348 return drawDefaultUserPixmap(decorationSize, '?', '?');
Stepan Salenikovich3a6ea302016-01-08 13:54:34 -0500349 }
350
Alexandre Lision7a166e42015-09-02 15:04:43 -0400351} // namespace Interfaces