mirror of https://github.com/Nheko-Reborn/nheko
parent
328a3c7ebd
commit
0fc98b2692
@ -0,0 +1,42 @@ |
|||||||
|
#include "BlurhashProvider.h" |
||||||
|
|
||||||
|
#include <algorithm> |
||||||
|
|
||||||
|
#include <QUrl> |
||||||
|
|
||||||
|
#include "blurhash.hpp" |
||||||
|
|
||||||
|
QImage |
||||||
|
BlurhashProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) |
||||||
|
{ |
||||||
|
QSize sz = requestedSize; |
||||||
|
if (sz.width() < 1 || sz.height() < 1) |
||||||
|
return QImage(); |
||||||
|
|
||||||
|
if (size) |
||||||
|
*size = sz; |
||||||
|
|
||||||
|
auto decoded = blurhash::decode( |
||||||
|
QUrl::fromPercentEncoding(id.toUtf8()).toStdString(), sz.width(), sz.height()); |
||||||
|
if (decoded.image.empty()) { |
||||||
|
*size = QSize(); |
||||||
|
return QImage(); |
||||||
|
} |
||||||
|
|
||||||
|
QImage image(sz, QImage::Format_RGB888); |
||||||
|
|
||||||
|
for (int y = 0; y < sz.height(); y++) { |
||||||
|
for (int x = 0; x < sz.width(); x++) { |
||||||
|
int base = (y * sz.width() + x) * 3; |
||||||
|
image.setPixel(x, |
||||||
|
y, |
||||||
|
qRgb(decoded.image[base], |
||||||
|
decoded.image[base + 1], |
||||||
|
decoded.image[base + 2])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// std::copy(decoded.image.begin(), decoded.image.end(), image.bits());
|
||||||
|
|
||||||
|
return image; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
#include <QQuickImageProvider> |
||||||
|
|
||||||
|
class BlurhashProvider : public QQuickImageProvider |
||||||
|
{ |
||||||
|
public: |
||||||
|
BlurhashProvider() |
||||||
|
: QQuickImageProvider(QQuickImageProvider::Image) |
||||||
|
{} |
||||||
|
|
||||||
|
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; |
||||||
|
}; |
@ -0,0 +1,23 @@ |
|||||||
|
Boost Software License - Version 1.0 - August 17th, 2003 |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person or organization |
||||||
|
obtaining a copy of the software and accompanying documentation covered by |
||||||
|
this license (the "Software") to use, reproduce, display, distribute, |
||||||
|
execute, and transmit the Software, and to prepare derivative works of the |
||||||
|
Software, and to permit third-parties to whom the Software is furnished to |
||||||
|
do so, all subject to the following: |
||||||
|
|
||||||
|
The copyright notices in the Software and this entire statement, including |
||||||
|
the above license grant, this restriction and the following disclaimer, |
||||||
|
must be included in all copies of the Software, in whole or in part, and |
||||||
|
all derivative works of the Software, unless such copies or derivative |
||||||
|
works are solely in the form of machine-executable object code generated by |
||||||
|
a source language processor. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT |
||||||
|
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE |
||||||
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, |
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||||
|
DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,472 @@ |
|||||||
|
#include "blurhash.hpp" |
||||||
|
|
||||||
|
#include <algorithm> |
||||||
|
#include <array> |
||||||
|
#include <cassert> |
||||||
|
#include <cmath> |
||||||
|
#include <stdexcept> |
||||||
|
|
||||||
|
#ifndef M_PI |
||||||
|
#define M_PI 3.14159265358979323846 |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN |
||||||
|
#include <doctest.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
using namespace std::literals; |
||||||
|
|
||||||
|
namespace { |
||||||
|
constexpr std::array<char, 84> int_to_b83{ |
||||||
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"}; |
||||||
|
|
||||||
|
std::string |
||||||
|
leftPad(std::string str, size_t len) |
||||||
|
{ |
||||||
|
if (str.size() >= len) |
||||||
|
return str; |
||||||
|
return str.insert(0, len - str.size(), '0'); |
||||||
|
} |
||||||
|
|
||||||
|
constexpr std::array<int, 255> b83_to_int = []() constexpr |
||||||
|
{ |
||||||
|
std::array<int, 255> a{}; |
||||||
|
|
||||||
|
for (auto &e : a) |
||||||
|
e = -1; |
||||||
|
|
||||||
|
for (int i = 0; i < 83; i++) { |
||||||
|
a[static_cast<unsigned char>(int_to_b83[i])] = i; |
||||||
|
} |
||||||
|
|
||||||
|
return a; |
||||||
|
} |
||||||
|
(); |
||||||
|
|
||||||
|
std::string |
||||||
|
encode83(int value) |
||||||
|
{ |
||||||
|
std::string buffer; |
||||||
|
|
||||||
|
do { |
||||||
|
buffer += int_to_b83[value % 83]; |
||||||
|
} while ((value = value / 83)); |
||||||
|
|
||||||
|
std::reverse(buffer.begin(), buffer.end()); |
||||||
|
return buffer; |
||||||
|
} |
||||||
|
|
||||||
|
struct Components |
||||||
|
{ |
||||||
|
int x, y; |
||||||
|
}; |
||||||
|
|
||||||
|
int |
||||||
|
packComponents(const Components &c) |
||||||
|
{ |
||||||
|
return (c.x - 1) + (c.y - 1) * 9; |
||||||
|
} |
||||||
|
|
||||||
|
Components |
||||||
|
unpackComponents(int c) |
||||||
|
{ |
||||||
|
return {c % 9 + 1, c / 9 + 1}; |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
decode83(std::string_view value) |
||||||
|
{ |
||||||
|
int temp = 0; |
||||||
|
|
||||||
|
for (char c : value) |
||||||
|
if (b83_to_int[static_cast<unsigned char>(c)] < 0) |
||||||
|
throw std::invalid_argument("invalid character in blurhash"); |
||||||
|
|
||||||
|
for (char c : value) |
||||||
|
temp = temp * 83 + b83_to_int[static_cast<unsigned char>(c)]; |
||||||
|
return temp; |
||||||
|
} |
||||||
|
|
||||||
|
float |
||||||
|
decodeMaxAC(int quantizedMaxAC) |
||||||
|
{ |
||||||
|
return (quantizedMaxAC + 1) / 166.; |
||||||
|
} |
||||||
|
|
||||||
|
float |
||||||
|
decodeMaxAC(std::string_view maxAC) |
||||||
|
{ |
||||||
|
assert(maxAC.size() == 1); |
||||||
|
return decodeMaxAC(decode83(maxAC)); |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
encodeMaxAC(float maxAC) |
||||||
|
{ |
||||||
|
return std::max(0, std::min(82, int(maxAC * 166 - 0.5))); |
||||||
|
} |
||||||
|
|
||||||
|
float |
||||||
|
srgbToLinear(int value) |
||||||
|
{ |
||||||
|
auto srgbToLinearF = [](float x) { |
||||||
|
if (x <= 0.0f) |
||||||
|
return 0.0f; |
||||||
|
else if (x >= 1.0f) |
||||||
|
return 1.0f; |
||||||
|
else if (x < 0.04045f) |
||||||
|
return x / 12.92f; |
||||||
|
else |
||||||
|
return std::pow((x + 0.055f) / 1.055f, 2.4f); |
||||||
|
}; |
||||||
|
|
||||||
|
return srgbToLinearF(value / 255.f); |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
linearToSrgb(float value) |
||||||
|
{ |
||||||
|
auto linearToSrgbF = [](float x) -> float { |
||||||
|
if (x <= 0.0f) |
||||||
|
return 0.0f; |
||||||
|
else if (x >= 1.0f) |
||||||
|
return 1.0f; |
||||||
|
else if (x < 0.0031308f) |
||||||
|
return x * 12.92f; |
||||||
|
else |
||||||
|
return std::pow(x, 1.0f / 2.4f) * 1.055f - 0.055f; |
||||||
|
}; |
||||||
|
|
||||||
|
return int(linearToSrgbF(value) * 255.f + 0.5); |
||||||
|
} |
||||||
|
|
||||||
|
struct Color |
||||||
|
{ |
||||||
|
float r, g, b; |
||||||
|
|
||||||
|
Color &operator*=(float scale) |
||||||
|
{ |
||||||
|
r *= scale; |
||||||
|
g *= scale; |
||||||
|
b *= scale; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
friend Color operator*(Color lhs, float rhs) { return (lhs *= rhs); } |
||||||
|
Color &operator/=(float scale) |
||||||
|
{ |
||||||
|
r /= scale; |
||||||
|
g /= scale; |
||||||
|
b /= scale; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
Color &operator+=(const Color &rhs) |
||||||
|
{ |
||||||
|
r += rhs.r; |
||||||
|
g += rhs.g; |
||||||
|
b += rhs.b; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Color |
||||||
|
decodeDC(int value) |
||||||
|
{ |
||||||
|
const int intR = value >> 16; |
||||||
|
const int intG = (value >> 8) & 255; |
||||||
|
const int intB = value & 255; |
||||||
|
return {srgbToLinear(intR), srgbToLinear(intG), srgbToLinear(intB)}; |
||||||
|
} |
||||||
|
|
||||||
|
Color |
||||||
|
decodeDC(std::string_view value) |
||||||
|
{ |
||||||
|
assert(value.size() == 4); |
||||||
|
return decodeDC(decode83(value)); |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
encodeDC(const Color &c) |
||||||
|
{ |
||||||
|
return (linearToSrgb(c.r) << 16) + (linearToSrgb(c.g) << 8) + linearToSrgb(c.b); |
||||||
|
} |
||||||
|
|
||||||
|
float |
||||||
|
signPow(float value, float exp) |
||||||
|
{ |
||||||
|
return std::copysign(std::pow(std::abs(value), exp), value); |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
encodeAC(const Color &c, float maximumValue) |
||||||
|
{ |
||||||
|
auto quantR = |
||||||
|
int(std::max(0., std::min(18., std::floor(signPow(c.r / maximumValue, 0.5) * 9 + 9.5)))); |
||||||
|
auto quantG = |
||||||
|
int(std::max(0., std::min(18., std::floor(signPow(c.g / maximumValue, 0.5) * 9 + 9.5)))); |
||||||
|
auto quantB = |
||||||
|
int(std::max(0., std::min(18., std::floor(signPow(c.b / maximumValue, 0.5) * 9 + 9.5)))); |
||||||
|
|
||||||
|
return quantR * 19 * 19 + quantG * 19 + quantB; |
||||||
|
} |
||||||
|
|
||||||
|
Color |
||||||
|
decodeAC(int value, float maximumValue) |
||||||
|
{ |
||||||
|
auto quantR = value / (19 * 19); |
||||||
|
auto quantG = (value / 19) % 19; |
||||||
|
auto quantB = value % 19; |
||||||
|
|
||||||
|
return {signPow((float(quantR) - 9) / 9, 2) * maximumValue, |
||||||
|
signPow((float(quantG) - 9) / 9, 2) * maximumValue, |
||||||
|
signPow((float(quantB) - 9) / 9, 2) * maximumValue}; |
||||||
|
} |
||||||
|
|
||||||
|
Color |
||||||
|
decodeAC(std::string_view value, float maximumValue) |
||||||
|
{ |
||||||
|
return decodeAC(decode83(value), maximumValue); |
||||||
|
} |
||||||
|
|
||||||
|
Color |
||||||
|
multiplyBasisFunction(Components components, int width, int height, unsigned char *pixels) |
||||||
|
{ |
||||||
|
Color c{}; |
||||||
|
float normalisation = (components.x == 0 && components.y == 0) ? 1 : 2; |
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) { |
||||||
|
for (int x = 0; x < width; x++) { |
||||||
|
float basis = std::cos(M_PI * components.x * x / float(width)) * |
||||||
|
std::cos(M_PI * components.y * y / float(height)); |
||||||
|
c.r += basis * srgbToLinear(pixels[3 * x + 0 + y * width * 3]); |
||||||
|
c.g += basis * srgbToLinear(pixels[3 * x + 1 + y * width * 3]); |
||||||
|
c.b += basis * srgbToLinear(pixels[3 * x + 2 + y * width * 3]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
float scale = normalisation / (width * height); |
||||||
|
c *= scale; |
||||||
|
return c; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
namespace blurhash { |
||||||
|
Image |
||||||
|
decode(std::string_view blurhash, size_t width, size_t height) |
||||||
|
{ |
||||||
|
Image i{}; |
||||||
|
|
||||||
|
if (blurhash.size() < 10) |
||||||
|
return i; |
||||||
|
|
||||||
|
Components components{}; |
||||||
|
std::vector<Color> values; |
||||||
|
try { |
||||||
|
components = unpackComponents(decode83(blurhash.substr(0, 1))); |
||||||
|
|
||||||
|
if (components.x < 1 || components.y < 1 || |
||||||
|
blurhash.size() != size_t(1 + 1 + 4 + (components.x * components.y - 1) * 2)) |
||||||
|
return {}; |
||||||
|
|
||||||
|
auto maxAC = decodeMaxAC(blurhash.substr(1, 1)); |
||||||
|
Color average = decodeDC(blurhash.substr(2, 4)); |
||||||
|
|
||||||
|
values.push_back(average); |
||||||
|
for (size_t c = 6; c < blurhash.size(); c += 2) |
||||||
|
values.push_back(decodeAC(blurhash.substr(c, 2), maxAC)); |
||||||
|
} catch (std::invalid_argument &) { |
||||||
|
return {}; |
||||||
|
} |
||||||
|
|
||||||
|
i.image.reserve(height * width * 3); |
||||||
|
|
||||||
|
for (size_t y = 0; y < height; y++) { |
||||||
|
for (size_t x = 0; x < width; x++) { |
||||||
|
Color c{}; |
||||||
|
|
||||||
|
for (size_t nx = 0; nx < size_t(components.x); nx++) { |
||||||
|
for (size_t ny = 0; ny < size_t(components.y); ny++) { |
||||||
|
float basis = |
||||||
|
std::cos(M_PI * float(x) * float(nx) / float(width)) * |
||||||
|
std::cos(M_PI * float(y) * float(ny) / float(height)); |
||||||
|
c += values[nx + ny * components.x] * basis; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
i.image.push_back(static_cast<unsigned char>(linearToSrgb(c.r))); |
||||||
|
i.image.push_back(static_cast<unsigned char>(linearToSrgb(c.g))); |
||||||
|
i.image.push_back(static_cast<unsigned char>(linearToSrgb(c.b))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
i.height = height; |
||||||
|
i.width = width; |
||||||
|
|
||||||
|
return i; |
||||||
|
} |
||||||
|
|
||||||
|
std::string |
||||||
|
encode(unsigned char *image, size_t width, size_t height, int components_x, int components_y) |
||||||
|
{ |
||||||
|
if (width < 1 || height < 1 || components_x < 1 || components_x > 9 || components_y < 1 || |
||||||
|
components_y > 9 || !image) |
||||||
|
return ""; |
||||||
|
|
||||||
|
std::vector<Color> factors; |
||||||
|
factors.reserve(components_x * components_y); |
||||||
|
for (int y = 0; y < components_y; y++) { |
||||||
|
for (int x = 0; x < components_x; x++) { |
||||||
|
factors.push_back(multiplyBasisFunction({x, y}, width, height, image)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assert(factors.size() > 0); |
||||||
|
|
||||||
|
auto dc = factors.front(); |
||||||
|
factors.erase(factors.begin()); |
||||||
|
|
||||||
|
std::string h; |
||||||
|
|
||||||
|
h += leftPad(encode83(packComponents({components_x, components_y})), 1); |
||||||
|
|
||||||
|
float maximumValue; |
||||||
|
if (!factors.empty()) { |
||||||
|
float actualMaximumValue = 0; |
||||||
|
for (auto ac : factors) { |
||||||
|
actualMaximumValue = std::max({ |
||||||
|
std::abs(ac.r), |
||||||
|
std::abs(ac.g), |
||||||
|
std::abs(ac.b), |
||||||
|
actualMaximumValue, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
int quantisedMaximumValue = encodeMaxAC(actualMaximumValue); |
||||||
|
maximumValue = ((float)quantisedMaximumValue + 1) / 166; |
||||||
|
h += leftPad(encode83(quantisedMaximumValue), 1); |
||||||
|
} else { |
||||||
|
maximumValue = 1; |
||||||
|
h += leftPad(encode83(0), 1); |
||||||
|
} |
||||||
|
|
||||||
|
h += leftPad(encode83(encodeDC(dc)), 4); |
||||||
|
|
||||||
|
for (auto ac : factors) |
||||||
|
h += leftPad(encode83(encodeAC(ac, maximumValue)), 2); |
||||||
|
|
||||||
|
return h; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN |
||||||
|
TEST_CASE("component packing") |
||||||
|
{ |
||||||
|
for (int i = 0; i < 9 * 9; i++) |
||||||
|
CHECK(packComponents(unpackComponents(i)) == i); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("encode83") |
||||||
|
{ |
||||||
|
CHECK(encode83(0) == "0"); |
||||||
|
|
||||||
|
CHECK(encode83(packComponents({4, 3})) == "L"); |
||||||
|
CHECK(encode83(packComponents({4, 4})) == "U"); |
||||||
|
CHECK(encode83(packComponents({8, 4})) == "Y"); |
||||||
|
CHECK(encode83(packComponents({2, 1})) == "1"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("decode83") |
||||||
|
{ |
||||||
|
CHECK(packComponents({4, 3}) == decode83("L")); |
||||||
|
CHECK(packComponents({4, 4}) == decode83("U")); |
||||||
|
CHECK(packComponents({8, 4}) == decode83("Y")); |
||||||
|
CHECK(packComponents({2, 1}) == decode83("1")); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("maxAC") |
||||||
|
{ |
||||||
|
for (int i = 0; i < 83; i++) |
||||||
|
CHECK(encodeMaxAC(decodeMaxAC(i)) == i); |
||||||
|
|
||||||
|
CHECK(std::abs(decodeMaxAC("l"sv) - 0.289157f) < 0.00001f); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("DC") |
||||||
|
{ |
||||||
|
CHECK(encode83(encodeDC(decodeDC("MF%n"))) == "MF%n"sv); |
||||||
|
CHECK(encode83(encodeDC(decodeDC("HV6n"))) == "HV6n"sv); |
||||||
|
CHECK(encode83(encodeDC(decodeDC("F5]+"))) == "F5]+"sv); |
||||||
|
CHECK(encode83(encodeDC(decodeDC("Pj0^"))) == "Pj0^"sv); |
||||||
|
CHECK(encode83(encodeDC(decodeDC("O2?U"))) == "O2?U"sv); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("AC") |
||||||
|
{ |
||||||
|
auto h = "00%#MwS|WCWEM{R*bbWBbH"sv; |
||||||
|
for (size_t i = 0; i < h.size(); i += 2) { |
||||||
|
auto s = h.substr(i, 2); |
||||||
|
const auto maxAC = 0.289157f; |
||||||
|
CHECK(leftPad(encode83(encodeAC(decodeAC(decode83(s), maxAC), maxAC)), 2) == s); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("decode") |
||||||
|
{ |
||||||
|
blurhash::Image i1 = blurhash::decode("LEHV6nWB2yk8pyoJadR*.7kCMdnj", 360, 200); |
||||||
|
CHECK(i1.width == 360); |
||||||
|
CHECK(i1.height == 200); |
||||||
|
CHECK(i1.image.size() == i1.height * i1.width * 3); |
||||||
|
CHECK(i1.image[0] == 135); |
||||||
|
CHECK(i1.image[1] == 164); |
||||||
|
CHECK(i1.image[2] == 177); |
||||||
|
CHECK(i1.image[10000] == 173); |
||||||
|
CHECK(i1.image[10001] == 176); |
||||||
|
CHECK(i1.image[10002] == 163); |
||||||
|
// stbi_write_bmp("test.bmp", i1.width, i1.height, 3, (void *)i1.image.data());
|
||||||
|
|
||||||
|
i1 = blurhash::decode("LGF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 360); |
||||||
|
CHECK(i1.height == 200); |
||||||
|
CHECK(i1.image.size() == i1.height * i1.width * 3); |
||||||
|
// stbi_write_bmp("test2.bmp", i1.width, i1.height, 3, (void *)i1.image.data());
|
||||||
|
|
||||||
|
// invalid inputs
|
||||||
|
i1 = blurhash::decode(" LGF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 0); |
||||||
|
CHECK(i1.height == 0); |
||||||
|
CHECK(i1.image.size() == 0); |
||||||
|
i1 = blurhash::decode(" LGF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 0); |
||||||
|
CHECK(i1.height == 0); |
||||||
|
CHECK(i1.image.size() == 0); |
||||||
|
|
||||||
|
i1 = blurhash::decode("LGF5]+Yk^6# M@-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 0); |
||||||
|
CHECK(i1.height == 0); |
||||||
|
CHECK(i1.image.size() == 0); |
||||||
|
i1 = blurhash::decode("LGF5]+Yk^6# M@-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 0); |
||||||
|
CHECK(i1.height == 0); |
||||||
|
CHECK(i1.image.size() == 0); |
||||||
|
|
||||||
|
i1 = blurhash::decode("LGF5]+Yk^6# @-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 0); |
||||||
|
CHECK(i1.height == 0); |
||||||
|
CHECK(i1.image.size() == 0); |
||||||
|
i1 = blurhash::decode(" GF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); |
||||||
|
CHECK(i1.width == 0); |
||||||
|
CHECK(i1.height == 0); |
||||||
|
CHECK(i1.image.size() == 0); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("encode") |
||||||
|
{ |
||||||
|
CHECK(blurhash::encode(nullptr, 360, 200, 4, 3) == ""); |
||||||
|
|
||||||
|
std::vector<unsigned char> black(360 * 200 * 3, 0); |
||||||
|
CHECK(blurhash::encode(black.data(), 0, 200, 4, 3) == ""); |
||||||
|
CHECK(blurhash::encode(black.data(), 360, 0, 4, 3) == ""); |
||||||
|
CHECK(blurhash::encode(black.data(), 360, 200, 0, 3) == ""); |
||||||
|
CHECK(blurhash::encode(black.data(), 360, 200, 4, 0) == ""); |
||||||
|
CHECK(blurhash::encode(black.data(), 360, 200, 4, 3) == "L00000fQfQfQfQfQfQfQfQfQfQfQ"); |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,22 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include <string_view> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace blurhash { |
||||||
|
struct Image |
||||||
|
{ |
||||||
|
size_t width, height; |
||||||
|
std::vector<unsigned char> image; // pixels rgb
|
||||||
|
}; |
||||||
|
|
||||||
|
// Decode a blurhash to an image with size width*height
|
||||||
|
Image |
||||||
|
decode(std::string_view blurhash, size_t width, size_t height); |
||||||
|
|
||||||
|
// Encode an image of rgb pixels (without padding) with size width*height into a blurhash with x*y
|
||||||
|
// components
|
||||||
|
std::string |
||||||
|
encode(unsigned char *image, size_t width, size_t height, int x, int y); |
||||||
|
} |
Loading…
Reference in new issue