/* * Copyright 2010 Ivan Cukic * * 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 #include #include #include #include #include #include #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; }