feat(identicon): add algorithm to create identicons

ported from https://github.com/sudden6/rust-identicon/blob/master/src/lib.rs
but slightly changed the algorithm
reviewable/pr4700/r11
sudden6 2017-09-29 00:02:13 +02:00
parent 68e0b462fc
commit 61b36d1bce
No known key found for this signature in database
GPG Key ID: 279509B499E032B9
3 changed files with 175 additions and 0 deletions

View File

@ -413,6 +413,8 @@ set(${PROJECT_NAME}_SOURCES
src/widget/tool/flyoutoverlaywidget.h
src/widget/tool/friendrequestdialog.cpp
src/widget/tool/friendrequestdialog.h
src/widget/tool/identicon.cpp
src/widget/tool/identicon.h
src/widget/tool/movablewidget.cpp
src/widget/tool/movablewidget.h
src/widget/tool/profileimporter.cpp

View File

@ -0,0 +1,143 @@
/*
Copyright © 2017 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox 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 General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "identicon.h"
#include "src/core/toxpk.h"
#include <cassert>
#include <QColor>
#include <QCryptographicHash>
#include <QDebug>
#include <QImage>
// The following constants change the appearance of the identicon
// they have been choosen by trying to make the output look nice.
/**
* @var Identicon::IDENTICON_COLOR_BYTES
* Specifies how many bytes should define the foreground color
* must be smaller than 8, else there'll be overflows
*/
/**
* @var Identicon::COLORS
* Number of colors to use for the identicon
*/
/**
* @var Identicon::IDENTICON_ROWS
* Specifies how many rows of blocks the identicon should have
*/
/**
* @var Identicon::ACTIVE_COLS
* Width from the center to the outside, for 5 columns it's 3, 6 -> 3, 7 -> 4
*/
/**
* @var Identicon::HASH_MIN_LEN
* Min length of the hash in bytes, 7 bytes control the color,
* the rest controls the pixel placement
*/
/**
* @brief Creates an Identicon, that visualizes a hash in graphical form.
* @param data Data to visualize
*/
Identicon::Identicon(const QByteArray& data)
{
static_assert(Identicon::COLORS == 2, "Only two colors are implemented");
// hash with sha256
QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Sha256);
for (int colorIndex = 0; colorIndex < COLORS; ++colorIndex) {
const QByteArray hashPart = hash.right(IDENTICON_COLOR_BYTES);
hash.truncate(hash.length() - IDENTICON_COLOR_BYTES);
const qreal hue = bytesToColor(hashPart);
// change offset when COLORS != 2
const qreal lig = static_cast<qreal>(colorIndex) / COLORS + 0.3;
const qreal sat = 0.5;
colors[colorIndex].setHslF(hue, sat, lig);
}
const uint8_t* const hashBytes = reinterpret_cast<const uint8_t*>(hash.constData());
// compute the block colors from the hash
for (int row = 0; row < IDENTICON_ROWS; ++row) {
for (int col = 0; col < ACTIVE_COLS; ++col) {
const int hashIdx = row * ACTIVE_COLS + col;
const uint8_t colorIndex = hashBytes[hashIdx] % COLORS;
identiconColors[row][col] = colorIndex;
}
}
}
/**
* @brief Converts a series of IDENTICON_COLOR_BYTES bytes to a value in the range 0.0..1.0
* @param bytes Bytes to convert to a color
* @return Value in the range of 0.0..1.0
*/
float Identicon::bytesToColor(QByteArray bytes)
{
static_assert(IDENTICON_COLOR_BYTES <= 8, "IDENTICON_COLOR max value is 8");
const uint8_t* const bytesChr = reinterpret_cast<const uint8_t*>(bytes.constData());
assert(bytes.length() == IDENTICON_COLOR_BYTES);
// get foreground color
uint64_t hue = bytesChr[0];
// convert the last bytes to an uint
for (int i = 1; i < IDENTICON_COLOR_BYTES; ++i) {
hue = hue << 8;
hue += bytesChr[i];
}
// normalize to 0.0 ... 1.0
return (static_cast<float>(hue))
/ (((static_cast<uint64_t>(1)) << (8 * IDENTICON_COLOR_BYTES)) - 1);
}
/**
* @brief Writes the Identicon to a QImage
* @param scaleFactor the image will be a square with scaleFactor * IDENTICON_ROWS pixels,
* must be >= 1
* @return a QImage with the identicon
*/
QImage Identicon::toImage(int scaleFactor)
{
if (scaleFactor < 1) {
qDebug() << "Can't scale with values <1, clamping to 1";
scaleFactor = 1;
}
scaleFactor *= IDENTICON_ROWS;
QImage pixels(IDENTICON_ROWS, IDENTICON_ROWS, QImage::Format_RGB888);
for (int row = 0; row < IDENTICON_ROWS; ++row) {
for (int col = 0; col < IDENTICON_ROWS; ++col) {
// mirror on vertical axis
const int columnIdx = abs((col * 2 - (IDENTICON_ROWS - 1)) / 2);
const int colorIdx = identiconColors[row][columnIdx];
pixels.setPixel(col, row, colors[colorIdx].rgb());
}
}
// scale up without smoothing to make it look sharp
return pixels.scaled(scaleFactor, scaleFactor, Qt::IgnoreAspectRatio, Qt::FastTransformation);
}

View File

@ -0,0 +1,30 @@
#ifndef IDENTICON_H
#define IDENTICON_H
#include <QColor>
#include <QImage>
class Identicon
{
public:
Identicon(const QByteArray& data);
QImage toImage(int scaleFactor = 1);
public:
static constexpr int IDENTICON_ROWS = 5;
private:
float bytesToColor(QByteArray bytes);
private:
static constexpr int COLORS = 2;
static constexpr int ACTIVE_COLS = (IDENTICON_ROWS + 1) / 2;
static constexpr int IDENTICON_COLOR_BYTES = 6;
static constexpr int HASH_MIN_LEN = ACTIVE_COLS * IDENTICON_ROWS
+ COLORS * IDENTICON_COLOR_BYTES;
uint8_t identiconColors[IDENTICON_ROWS][ACTIVE_COLS];
QColor colors[COLORS];
};
#endif // IDENTICON_H