kde-workspace/plasma/desktop/shell/activitymanager/kidenticongenerator.cpp
2014-11-13 19:30:51 +02:00

295 lines
7.8 KiB
C++

/*
* Copyright 2010 Ivan Cukic <ivan.cukic(at)kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library/Lesser General Public License
* version 2, or (at your option) any later version, as published by the
* Free Software Foundation
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library/Lesser General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kidenticongenerator.h"
#include <QHash>
#include <QPainter>
#include <QDebug>
#include <QCryptographicHash>
#include <KIconEffect>
#include <Plasma/Svg>
#include <Plasma/Theme>
#define VALUE_LIMIT_UP 192
#define VALUE_LIMIT_DOWN 64
class KIdenticonGenerator::Private {
public:
QPixmap generatePattern(int size, quint32 hash, QIcon::Mode mode);
QString elementName(const QString & element, QIcon::Mode mode);
QColor colorForHash(quint32 hash) const;
quint32 hash(const QString & data);
static KIdenticonGenerator * instance;
Plasma::Svg shapes;
Plasma::Svg theme;
};
QPixmap KIdenticonGenerator::Private::generatePattern(int size, quint32 hash, QIcon::Mode mode)
{
// We are dividing the pixmap into 9 blocks - 3 x 3
int blockSize = size / 3;
// pulling parts of the hash
quint32 tmp = hash;
quint8 block[4];
block[0] = tmp & 31; tmp >>= 5;
block[1] = tmp & 31; tmp >>= 5;
block[2] = tmp & 31; tmp >>= 5;
// Painting alpha channel
QPixmap pixmapAlpha(size, size);
pixmapAlpha.fill(Qt::black);
QPainter painterAlpha(& pixmapAlpha);
QRectF rect(0, 0, blockSize + 0.5, blockSize + 0.5);
for (int i = 0; i < 4; i++) {
// Painting the corner item
rect.moveTopLeft(QPoint(0, 0));
shapes.paint(& painterAlpha, rect, "shape" + QString::number(block[0] + 1));
// Painting side item
rect.moveTopLeft(QPoint(blockSize, 0));
shapes.paint(& painterAlpha, rect, "shape" + QString::number(block[1] + 1));
// Rotating the canvas to paint other edges
painterAlpha.translate(size, 0);
painterAlpha.rotate(90);
}
// Painting center item
rect.moveTopLeft(QPoint(blockSize, blockSize));
shapes.paint(& painterAlpha, rect, "shape" + QString::number(block[2] + 1));
painterAlpha.end();
// Painting final pixmap
QPixmap pixmapResult(size, size);
pixmapResult.fill(Qt::transparent);
// QRadialGradient gradient(50, 50, 100);
// gradient.setColorAt(0, color.lighter());
// gradient.setColorAt(1, color.darker());
QPainter resultPainter(& pixmapResult);
// resultPainter.fillRect(0, 0, size, size, gradient);
theme.paint(& resultPainter, QRect(0, 0, size, size), elementName("content", mode));
resultPainter.end();
pixmapResult.setAlphaChannel(pixmapAlpha);
// QImage itmp = pixmapResult.toImage();
// KIconEffect::colorize(itmp, colorForHash(hash), 1.0);
// pixmapResult = pixmapResult.fromImage(itmp);
return pixmapResult;
}
QColor KIdenticonGenerator::Private::colorForHash(quint32 hash) const
{
// Color is chosen according to hash
QColor color;
// Getting the value from color theme, but we must restrain it to
// values in range from VALUE_LIMIT_DOWN to VALUE_LIMIT_UP
int value = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor).value();
if (value < VALUE_LIMIT_DOWN) {
value = VALUE_LIMIT_DOWN;
} else if (value > VALUE_LIMIT_UP) {
value = VALUE_LIMIT_UP;
}
color.setHsv(
hash % 359 + 1, // hue depending on hash
250, // high saturation level
value
);
return color;
}
QString KIdenticonGenerator::Private::elementName(const QString & element, QIcon::Mode mode)
{
QString prefix;
switch (mode) {
case QIcon::Normal:
prefix = "normal-";
break;
case QIcon::Disabled:
prefix = "disabled-";
break;
case QIcon::Selected:
prefix = "selected-";
break;
case QIcon::Active:
prefix = "active-";
break;
default:
break;
}
if (theme.hasElement(prefix + element)) {
return prefix + element;
} else {
return element;
}
}
quint32 KIdenticonGenerator::Private::hash(const QString & data)
{
// qHash function doesn't give random enough results
// and gives similar hashes for similar strings.
QByteArray bytes = QCryptographicHash::hash(data.toUtf8(), QCryptographicHash::Md5);
// Generating hash
quint32 hash = 0;
char * hashBytes = (char *) & hash;
for (int i = 0; i < bytes.size(); i++) {
// Using XOR for mixing the bytes because
// it is fast and cryptographically safe
// (more than enough for our use-case)
hashBytes[i % 4] ^= bytes.at(i);
}
return hash;
}
KIdenticonGenerator * KIdenticonGenerator::Private::instance = NULL;
KIdenticonGenerator * KIdenticonGenerator::self()
{
if (!Private::instance) {
Private::instance = new KIdenticonGenerator();
}
return Private::instance;
}
KIdenticonGenerator::KIdenticonGenerator()
: d(new Private())
{
// loading SVGs
d->shapes.setImagePath("widgets/identiconshapes");
d->shapes.setContainsMultipleImages(true);
d->theme.setImagePath("widgets/identicontheme");
d->theme.setContainsMultipleImages(true);
}
#define generateIconModes( PARAM ) \
for (int omode = QIcon::Normal; omode <= QIcon::Selected; omode++) { \
QIcon::Mode mode = (QIcon::Mode)omode; \
result.addPixmap(generatePixmap(size, PARAM, mode), mode); \
}
QIcon KIdenticonGenerator::generate(int size, quint32 hash)
{
QIcon result;
generateIconModes(hash);
return result;
}
QIcon KIdenticonGenerator::generate(int size, const QString & data)
{
QIcon result;
generateIconModes(data);
return result;
}
QIcon KIdenticonGenerator::generate(int size, const QIcon & icon)
{
QIcon result;
generateIconModes(icon);
return result;
}
QPixmap KIdenticonGenerator::generatePixmap(int size, QString id, QIcon::Mode mode)
{
return generatePixmap(size, d->hash(id), mode);
}
QPixmap KIdenticonGenerator::generatePixmap(int size, quint32 hash, QIcon::Mode mode)
{
QPixmap pixmap(size, size);
pixmap.fill(Qt::transparent);
// Painting background and the pattern
{
QPainter painter(& pixmap);
d->theme.paint(& painter, QRect(0, 0, size, size), d->elementName("background", mode));
painter.drawPixmap(0, 0, d->generatePattern(size, hash, mode));
painter.end();
}
// coloring the painted image
QImage itmp = pixmap.toImage();
KIconEffect::colorize(itmp, d->colorForHash(hash), 1.0);
if (mode == QIcon::Disabled) {
KIconEffect::toGray(itmp, 0.7);
}
pixmap = pixmap.fromImage(itmp);
// Drawing the overlay
{
QPainter painter(& pixmap);
d->theme.paint(& painter, QRect(0, 0, size, size), d->elementName("overlay", mode));
}
return pixmap;
}
QPixmap KIdenticonGenerator::generatePixmap(int size, const QIcon & icon, QIcon::Mode mode)
{
QPixmap pixmap(size, size);
pixmap.fill(Qt::transparent);
QRect paintRect(0, 0, size, size);
// Painting background and the pattern
QPainter painter(& pixmap);
d->theme.paint(& painter, QRect(0, 0, size, size), d->elementName("background", mode));
icon.paint(& painter, paintRect, Qt::AlignCenter, mode);
painter.end();
return pixmap;
}