diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e4aab1..261fa14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.2) project(libravatarserv - VERSION 0.4.3 + VERSION 0.5.0 LANGUAGES CXX ) diff --git a/README.md b/README.md index 0aaa876..2936d09 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ images tied to email or OpenID addresses. * MD5 hashes * SHA256 hashes * Variable image size (`s` or `size`) -* Default actions (`d` or `default`): 404, URL, mp/mm +* Default actions (`d` or `default`): 404, URL, mp/mm, identicon The default behaviour for unknown users is to return a 404 error. If a default image is put in the avatar directory, that is returned instead. The default diff --git a/src/image.cpp b/src/image.cpp index 7a9835a..2be47b3 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -15,6 +15,8 @@ */ #include +#include +#include #include "libravatarserv.hpp" using std::cout; @@ -69,3 +71,61 @@ void image::write(Image &image) cout.flush(); // We need to flush before we use /dev/stdout directly. image.image.write("/dev/stdout"); } + +Image image::identicon(const string &digest) +{ + const string sha256 = hash::sha256(digest); + Magick::Image img(Magick::Geometry(4, 4), Magick::Color("white")); + // The 16 named colors specified in HTML 14.01 minus white, silver and gray. + const std::array colors = + { + Magick::Color("black"), + Magick::Color("red"), + Magick::Color("maroon"), + Magick::Color("yellow"), + Magick::Color("olive"), + Magick::Color("lime"), + Magick::Color("green"), + Magick::Color("aqua"), + Magick::Color("teal"), + Magick::Color("blue"), + Magick::Color("navy"), + Magick::Color("fuchsia"), + Magick::Color("purple") + }; + + try + { + std::uint64_t random = 0xffff; + for (uint64_t chunk = 0; chunk < 4; ++chunk) + { + std::stringstream ss; + ss << std::hex << sha256.substr(chunk * 16, 16); + random = (random / 2) + (static_cast(ss.get()) / 2); + } + + Magick::Color dotcolor = colors[random % 13]; + for (uint8_t row = 0; row < 4; ++row) + { + for (uint8_t column = 0; column < 4; ++column) + { + std::stringstream ss; + uint16_t px; + ss << std::hex << sha256.substr(row * 4 + column, 4); + ss >> px; + if (px > 0x8000) + { + img.pixelColor(column, row, dotcolor); + } + } + } + } + catch (const std::exception &e) + { + cerr << "Error: " << e.what() << endl; + return { 5, img }; + } + + img.magick("png"); + return { 0, img }; +} diff --git a/src/libravatarserv.cpp b/src/libravatarserv.cpp index 554ef15..3863e15 100644 --- a/src/libravatarserv.cpp +++ b/src/libravatarserv.cpp @@ -71,8 +71,8 @@ int main() avatar.fallback.substr(0, 2) == "mm") { // MD5 hash of 'mp' - image = image::get("1f2dfa567dcf95833eddf7aec167fec7", - avatar.size); + image = image::get("1f2dfa567dcf95833eddf7aec167fec7", + avatar.size); if (image.error == 0) { image::write(image); @@ -80,7 +80,21 @@ int main() else { cout << "Status: 404 Not Found\n\n"; - cerr << "Mystery person not found.\n"; + cerr << "Error: Mystery person not found.\n"; + } + } + else if (avatar.fallback.substr(0, 9) == "identicon") + { + image = image::identicon(avatar.digest); + if (image.error == 0) + { + image.image.scale(Magick::Geometry(avatar.size, avatar.size)); + image::write(image); + } + else + { + cout << "Status: 404 Not Found\n\n"; + cerr << "Error: Couldn't generate identicon.\n"; } } else diff --git a/src/libravatarserv.hpp b/src/libravatarserv.hpp index abc5465..ced0036 100644 --- a/src/libravatarserv.hpp +++ b/src/libravatarserv.hpp @@ -75,6 +75,7 @@ namespace image // image.cpp const Image get(const string &digest, const uint16_t size); void write(Image &image); + Image identicon(const string &digest); } #endif // LIBRAVATARSERV_HPP