diff --git a/CMakeLists.txt b/CMakeLists.txt index b03b79f..9e802ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,8 @@ set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +string(REPLACE ";" " " MAGICPP_CFLAGS_STRING "${MAGICPP_CFLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MAGICPP_CFLAGS_STRING}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -pedantic -Wextra -g -Og -fno-omit-frame-pointer") if(CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/README.md b/README.md index 15087c6..00d5735 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ **identiconpp** is a library to generate identicons. Written in C++. +There are currently 2 types of identicons supported. libravatar/sigil and +simple. + ## Features ## Usage diff --git a/example.cpp b/example.cpp index 46ee738..2b3c5df 100644 --- a/example.cpp +++ b/example.cpp @@ -28,7 +28,18 @@ int main(int argc, char *argv[]) return 1; } - Identiconpp identicon(5, 5, 0xffffffff, { 0x000000ff, 0x000000 }); - identicon.generate("testtest", Identiconpp::identicon_type::simple); + Identiconpp identicon(5, 5, 0xffffffff, + { + 0x000000ff, + 0xff0000ff, + 0xffff00ff, + 0x00ff00ff, + 0x00ffffff, + 0x0000ffff + }); + Identiconpp::Image image; + image = identicon.generate("2b7dd5def082abfca556d9e8feb1fc29", Identiconpp::identicon_type::simple); + cout.flush(); // We need to flush before we use /dev/stdout directly. + image.data.write("/dev/stdout"); return 0; } diff --git a/src/identiconpp.cpp b/src/identiconpp.cpp index 88d74ec..a1d39a8 100644 --- a/src/identiconpp.cpp +++ b/src/identiconpp.cpp @@ -16,6 +16,9 @@ #include #include +#include +#include +#include #include "identiconpp.hpp" #include "debug.hpp" @@ -30,31 +33,89 @@ Identiconpp::Identiconpp(const uint8_t rows, const uint8_t columns, } Identiconpp::Image Identiconpp::generate(const string &digest, - identicon_type type) + identicon_type type, + const uint16_t width, + const uint16_t height) { + check_entropy(digest, type); switch (type) { case identicon_type::simple: { - return generate_simple(digest); + return generate_simple(digest, width, height); } case identicon_type::libravatar: case identicon_type::sigil: { - return generate_libravatar(digest); + return generate_libravatar(digest, width, height); } } } -Identiconpp::Image Identiconpp::generate_simple(const string &digest) +Identiconpp::Image Identiconpp::generate_simple(const string &digest, + const uint16_t width, + const uint16_t height) +{ + std::stringstream ss; + ss << std::hex << _background; + const string bgcolor = "#" + ss.str(); + Magick::Image img(Magick::Geometry(_columns, _rows), + Magick::Color(bgcolor)); + uint8_t used_columns = _columns / 2 + _columns % 2; + Magick::Color dotcolor = get_color(used_columns * _rows + 1, digest); + + for (uint8_t row = 0; row < _rows; ++row) + { + for (uint8_t column = 0; column < used_columns; ++column) + { + if (get_bit(row * used_columns + column, digest)) + { + ttdebug << "col=" << std::to_string(column) + << ", row=" << std::to_string(row) << '\n'; + ttdebug << "col=" << std::to_string(used_columns - 1 + column) + << ", row=" << std::to_string(_rows - 1 - row) << '\n'; + img.pixelColor(column, row, dotcolor); + img.pixelColor(_columns - 1 - column, row, dotcolor); + } + } + } + + img.scale(Magick::Geometry(width, height)); + img.magick("png"); + return { 0, img }; +} + +Identiconpp::Image Identiconpp::generate_libravatar(const string &digest, + const uint16_t width, + const uint16_t height) { return { 1, Magick::Image() }; } -Identiconpp::Image Identiconpp::generate_libravatar(const string &digest) +void Identiconpp::check_entropy(const string &digest, identicon_type type) { - uint8_t entropy_provided = digest.length() / 2 * 8; - uint8_t entropy_required = (_columns / 2 + _columns % 2) * _rows + 8; + uint8_t entropy_provided; + uint8_t entropy_required; + switch (type) + { + case identicon_type::simple: + { + // Every char is 4 bit + entropy_provided = digest.length() * 4; + // We need bits for each field in half of the columns, +1 column if + // they are uneven. Then we need enough bits to pick a color. + entropy_required = (_columns / 2 + _columns % 2) * _rows + + (_foreground.size() / 2 + _foreground.size() % 2); + break; + } + case identicon_type::libravatar: + case identicon_type::sigil: + { + entropy_provided = digest.length() / 2 * 8; + entropy_required = (_columns / 2 + _columns % 2) * _rows + 8; + break; + } + } ttdebug << "entropy_provided=" << std::to_string(entropy_provided) << ", entropy_required=" << std::to_string(entropy_required) << '\n'; @@ -64,8 +125,52 @@ Identiconpp::Image Identiconpp::generate_libravatar(const string &digest) "Passed digest \"" + digest + "\" is not capable of providing " + std::to_string(entropy_required) + " bits of entropy."); } - - // TODO: implement - - return { 1, Magick::Image() }; +} + +bool Identiconpp::get_bit(const uint16_t bit, const string &digest) +{ + std::stringstream ss; + ss << std::hex << digest[bit / 4]; + // std::stringstream does not support writing into uint8_t + unsigned short buf; + ss >> buf; + std::bitset<4> nibble(buf); + + if (nibble[bit % 4]) + { + // ttdebug << "Bit " << std::to_string(bit) << " is set in " << digest << ".\n"; + return true; + } + + return false; +} + +Magick::Color Identiconpp::get_color(const uint16_t firstbit, + const string &digest) +{ + // Number of bits to use + const uint16_t colorbits = _foreground.size() / 2 + _foreground.size() % 2; + + // Extract approximation + std::stringstream ss; + if (colorbits % 4 == 0) + { + ss << std::hex << digest.substr(firstbit / 4, colorbits / 4); + } + else + { + ss << std::hex << digest.substr(firstbit / 4, colorbits / 4 + 1); + } + unsigned short bits; + ss >> bits; + + // Get rid of excess bits + bits = bits & (1 << colorbits) - 1; + + // Lookup und set color + ss.str(string()); + ss.clear(); + ss << std::hex << std::setw(8) << std::setfill('0') << _foreground[bits - 1]; + ttdebug << "Color: #" << ss.str() << '\n'; + return Magick::Color("#" + ss.str()); } diff --git a/src/identiconpp.hpp b/src/identiconpp.hpp index 65b2f64..9079624 100644 --- a/src/identiconpp.hpp +++ b/src/identiconpp.hpp @@ -21,6 +21,7 @@ #include using std::uint8_t; +using std::uint16_t; using std::uint32_t; using std::string; using std::vector; @@ -73,10 +74,14 @@ public: * @brief Generates identicon from digest. * * @param digest The pre-computed digest + * @param type The type of identicon + * @param width The width of the image + * @param height The height of the image * * @return 0 and an image on success, 1 and an empty image on error. */ - Image generate(const string &digest, identicon_type type); + Image generate(const string &digest, identicon_type type, + const uint16_t width = 100, const uint16_t height = 100); private: const uint8_t _rows; @@ -88,17 +93,55 @@ private: * @brief Generate simple identicon. * * @param digest The pre-computed digest + * @param width The width of the image + * @param height The height of the image * * @return 0 and an image on success, 1 and an empty image on error. */ - Image generate_simple(const string &digest); + Image generate_simple(const string &digest, + const uint16_t width, const uint16_t height); /*! - * @brief Generate libravatar-style identicon. + * @brief Generate libravatar-style / sigil identicon. * * @param digest The pre-computed digest + * @param width The width of the image + * @param height The height of the image * * @return 0 and an image on success, 1 and an empty image on error. */ - Image generate_libravatar(const string &digest); + Image generate_libravatar(const string &digest, + const uint16_t width, const uint16_t height); + + /*! + * @brief Check if the digest contains enough entropy. + * + * Throws `std::invalid_argument` if not. + * + * @param digest The pre-computed digest + * @param type The type of identicon + */ + void check_entropy(const string &digest, identicon_type type); + + /*! + * @brief Determines if the n-th bit of passed digest is 1 or 0. + * + * @param bit Bit to get + * @param digest The digest + * + * @return The bit. + */ + bool get_bit(const uint16_t bit, const string &digest); + + /*! + * @brief Chooses a foreground color. + * + * Extracts the right bits from the digest and returns a color. + * + * @param firstbit The first bit of the digest to choose a color + * @param digest The digest + * + * @return A foreground color. + */ + Magick::Color get_color(const uint16_t firstbit, const string &digest); };