File indexing completed on 2024-05-12 05:04:17
0001 // SPDX-FileCopyrightText: 2022 DeepBlueV7.X <https://github.com/deepbluev7> 0002 // SPDX-License-Identifier: BSL-1.0 0003 0004 #include "blurhash.hpp" 0005 0006 #include <algorithm> 0007 #include <array> 0008 #include <cassert> 0009 #include <cmath> 0010 #include <stdexcept> 0011 0012 #ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 0013 #if __has_include(<doctest.h>) 0014 #include <doctest.h> 0015 #else 0016 #include <doctest/doctest.h> 0017 #endif 0018 #endif 0019 0020 using namespace std::literals; 0021 0022 namespace { 0023 constexpr std::array<char, 84> int_to_b83{ 0024 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"}; 0025 0026 std::string 0027 leftPad(std::string str, size_t len) 0028 { 0029 if (str.size() >= len) 0030 return str; 0031 return str.insert(0, len - str.size(), '0'); 0032 } 0033 0034 constexpr std::array<int, 255> b83_to_int = []() constexpr 0035 { 0036 std::array<int, 255> a{}; 0037 0038 for (auto &e : a) 0039 e = -1; 0040 0041 for (int i = 0; i < 83; i++) { 0042 a[static_cast<unsigned char>(int_to_b83[i])] = i; 0043 } 0044 0045 return a; 0046 } 0047 (); 0048 0049 std::string 0050 encode83(int value) 0051 { 0052 std::string buffer; 0053 0054 do { 0055 buffer += int_to_b83[value % 83]; 0056 } while ((value = value / 83)); 0057 0058 std::reverse(buffer.begin(), buffer.end()); 0059 return buffer; 0060 } 0061 0062 struct Components 0063 { 0064 int x, y; 0065 }; 0066 0067 int 0068 packComponents(const Components &c) noexcept 0069 { 0070 return (c.x - 1) + (c.y - 1) * 9; 0071 } 0072 0073 Components 0074 unpackComponents(int c) noexcept 0075 { 0076 return {c % 9 + 1, c / 9 + 1}; 0077 } 0078 0079 int 0080 decode83(std::string_view value) 0081 { 0082 int temp = 0; 0083 0084 for (char c : value) 0085 if (b83_to_int[static_cast<unsigned char>(c)] < 0) 0086 throw std::invalid_argument("invalid character in blurhash"); 0087 0088 for (char c : value) 0089 temp = temp * 83 + b83_to_int[static_cast<unsigned char>(c)]; 0090 return temp; 0091 } 0092 0093 float 0094 decodeMaxAC(int quantizedMaxAC) noexcept 0095 { 0096 return static_cast<float>(quantizedMaxAC + 1) / 166.f; 0097 } 0098 0099 float 0100 decodeMaxAC(std::string_view maxAC) 0101 { 0102 assert(maxAC.size() == 1); 0103 return decodeMaxAC(decode83(maxAC)); 0104 } 0105 0106 int 0107 encodeMaxAC(float maxAC) noexcept 0108 { 0109 return std::max(0, std::min(82, int(maxAC * 166 - 0.5f))); 0110 } 0111 0112 float 0113 srgbToLinear(int value) noexcept 0114 { 0115 auto srgbToLinearF = [](float x) { 0116 if (x <= 0.0f) 0117 return 0.0f; 0118 else if (x >= 1.0f) 0119 return 1.0f; 0120 else if (x < 0.04045f) 0121 return x / 12.92f; 0122 else 0123 return std::pow((x + 0.055f) / 1.055f, 2.4f); 0124 }; 0125 0126 return srgbToLinearF(static_cast<float>(value) / 255.f); 0127 } 0128 0129 int 0130 linearToSrgb(float value) noexcept 0131 { 0132 auto linearToSrgbF = [](float x) -> float { 0133 if (x <= 0.0f) 0134 return 0.0f; 0135 else if (x >= 1.0f) 0136 return 1.0f; 0137 else if (x < 0.0031308f) 0138 return x * 12.92f; 0139 else 0140 return std::pow(x, 1.0f / 2.4f) * 1.055f - 0.055f; 0141 }; 0142 0143 return int(linearToSrgbF(value) * 255.f + 0.5f); 0144 } 0145 0146 struct Color 0147 { 0148 float r, g, b; 0149 0150 Color &operator*=(float scale) 0151 { 0152 r *= scale; 0153 g *= scale; 0154 b *= scale; 0155 return *this; 0156 } 0157 friend Color operator*(Color lhs, float rhs) { return (lhs *= rhs); } 0158 Color &operator/=(float scale) 0159 { 0160 r /= scale; 0161 g /= scale; 0162 b /= scale; 0163 return *this; 0164 } 0165 Color &operator+=(const Color &rhs) 0166 { 0167 r += rhs.r; 0168 g += rhs.g; 0169 b += rhs.b; 0170 return *this; 0171 } 0172 }; 0173 0174 Color 0175 decodeDC(int value) 0176 { 0177 const int intR = value >> 16; 0178 const int intG = (value >> 8) & 255; 0179 const int intB = value & 255; 0180 return {srgbToLinear(intR), srgbToLinear(intG), srgbToLinear(intB)}; 0181 } 0182 0183 Color 0184 decodeDC(std::string_view value) 0185 { 0186 assert(value.size() == 4); 0187 return decodeDC(decode83(value)); 0188 } 0189 0190 int 0191 encodeDC(const Color &c) 0192 { 0193 return (linearToSrgb(c.r) << 16) + (linearToSrgb(c.g) << 8) + linearToSrgb(c.b); 0194 } 0195 0196 float 0197 signPow(float value, float exp) 0198 { 0199 return std::copysign(std::pow(std::abs(value), exp), value); 0200 } 0201 0202 int 0203 encodeAC(const Color &c, float maximumValue) 0204 { 0205 auto quantR = 0206 int(std::max(0., std::min(18., std::floor(signPow(c.r / maximumValue, 0.5) * 9 + 9.5)))); 0207 auto quantG = 0208 int(std::max(0., std::min(18., std::floor(signPow(c.g / maximumValue, 0.5) * 9 + 9.5)))); 0209 auto quantB = 0210 int(std::max(0., std::min(18., std::floor(signPow(c.b / maximumValue, 0.5) * 9 + 9.5)))); 0211 0212 return quantR * 19 * 19 + quantG * 19 + quantB; 0213 } 0214 0215 Color 0216 decodeAC(int value, float maximumValue) 0217 { 0218 auto quantR = value / (19 * 19); 0219 auto quantG = (value / 19) % 19; 0220 auto quantB = value % 19; 0221 0222 return {signPow((float(quantR) - 9) / 9, 2) * maximumValue, 0223 signPow((float(quantG) - 9) / 9, 2) * maximumValue, 0224 signPow((float(quantB) - 9) / 9, 2) * maximumValue}; 0225 } 0226 0227 Color 0228 decodeAC(std::string_view value, float maximumValue) 0229 { 0230 return decodeAC(decode83(value), maximumValue); 0231 } 0232 0233 std::vector<float> 0234 bases_for(size_t dimension, size_t components) 0235 { 0236 std::vector<float> bases(dimension * components, 0.f); 0237 auto scale = M_PI / float(dimension); 0238 for (size_t x = 0; x < dimension; x++) { 0239 for (size_t nx = 0; nx < size_t(components); nx++) { 0240 bases[x * components + nx] = std::cos(scale * float(nx * x)); 0241 } 0242 } 0243 return bases; 0244 } 0245 } 0246 0247 namespace blurhash { 0248 Image 0249 decode(std::string_view blurhash, size_t width, size_t height, size_t bytesPerPixel) noexcept 0250 { 0251 Image i{}; 0252 0253 if (blurhash.size() < 10) 0254 return i; 0255 0256 Components components{}; 0257 std::vector<Color> values; 0258 values.reserve(blurhash.size() / 2); 0259 try { 0260 components = unpackComponents(decode83(blurhash.substr(0, 1))); 0261 0262 if (components.x < 1 || components.y < 1 || 0263 blurhash.size() != size_t(1 + 1 + 4 + (components.x * components.y - 1) * 2)) 0264 return {}; 0265 0266 auto maxAC = decodeMaxAC(blurhash.substr(1, 1)); 0267 Color average = decodeDC(blurhash.substr(2, 4)); 0268 0269 values.push_back(average); 0270 for (size_t c = 6; c < blurhash.size(); c += 2) 0271 values.push_back(decodeAC(blurhash.substr(c, 2), maxAC)); 0272 } catch (std::invalid_argument &) { 0273 return {}; 0274 } 0275 0276 i.image = decltype(i.image)(height * width * bytesPerPixel, 255); 0277 0278 std::vector<float> basis_x = bases_for(width, components.x); 0279 std::vector<float> basis_y = bases_for(height, components.y); 0280 0281 for (size_t y = 0; y < height; y++) { 0282 for (size_t x = 0; x < width; x++) { 0283 Color c{}; 0284 0285 for (size_t nx = 0; nx < size_t(components.x); nx++) { 0286 for (size_t ny = 0; ny < size_t(components.y); ny++) { 0287 float basis = basis_x[x * components.x + nx] * 0288 basis_y[y * components.y + ny]; 0289 c += values[nx + ny * components.x] * basis; 0290 } 0291 } 0292 0293 i.image[(y * width + x) * bytesPerPixel + 0] = 0294 static_cast<unsigned char>(linearToSrgb(c.r)); 0295 i.image[(y * width + x) * bytesPerPixel + 1] = 0296 static_cast<unsigned char>(linearToSrgb(c.g)); 0297 i.image[(y * width + x) * bytesPerPixel + 2] = 0298 static_cast<unsigned char>(linearToSrgb(c.b)); 0299 } 0300 } 0301 0302 i.height = height; 0303 i.width = width; 0304 0305 return i; 0306 } 0307 0308 std::string 0309 encode(unsigned char *image, size_t width, size_t height, int components_x, int components_y) 0310 { 0311 if (width < 1 || height < 1 || components_x < 1 || components_x > 9 || components_y < 1 || 0312 components_y > 9 || !image) 0313 return ""; 0314 0315 std::vector<float> basis_x = bases_for(width, components_x); 0316 std::vector<float> basis_y = bases_for(height, components_y); 0317 0318 std::vector<Color> factors(components_x * components_y, Color{}); 0319 for (size_t y = 0; y < height; y++) { 0320 for (size_t x = 0; x < width; x++) { 0321 Color linear{srgbToLinear(image[3 * x + 0 + y * width * 3]), 0322 srgbToLinear(image[3 * x + 1 + y * width * 3]), 0323 srgbToLinear(image[3 * x + 2 + y * width * 3])}; 0324 0325 // other half of normalization. 0326 linear *= 1.f / static_cast<float>(width); 0327 0328 for (size_t ny = 0; ny < size_t(components_y); ny++) { 0329 for (size_t nx = 0; nx < size_t(components_x); nx++) { 0330 float basis = basis_x[x * size_t(components_x) + nx] * 0331 basis_y[y * size_t(components_y) + ny]; 0332 factors[ny * components_x + nx] += linear * basis; 0333 } 0334 } 0335 } 0336 } 0337 0338 // scale by normalization. Half the scaling is done in the previous loop to prevent going 0339 // too far outside the float range. 0340 for (size_t i = 0; i < factors.size(); i++) { 0341 float normalisation = (i == 0) ? 1 : 2; 0342 float scale = normalisation / static_cast<float>(height); 0343 factors[i] *= scale; 0344 } 0345 0346 assert(factors.size() > 0); 0347 0348 auto dc = factors.front(); 0349 factors.erase(factors.begin()); 0350 0351 std::string h; 0352 0353 h += leftPad(encode83(packComponents({components_x, components_y})), 1); 0354 0355 float maximumValue; 0356 if (!factors.empty()) { 0357 float actualMaximumValue = 0; 0358 for (auto ac : factors) { 0359 actualMaximumValue = std::max({ 0360 std::abs(ac.r), 0361 std::abs(ac.g), 0362 std::abs(ac.b), 0363 actualMaximumValue, 0364 }); 0365 } 0366 0367 int quantisedMaximumValue = encodeMaxAC(actualMaximumValue); 0368 maximumValue = ((float)quantisedMaximumValue + 1) / 166; 0369 h += leftPad(encode83(quantisedMaximumValue), 1); 0370 } else { 0371 maximumValue = 1; 0372 h += leftPad(encode83(0), 1); 0373 } 0374 0375 h += leftPad(encode83(encodeDC(dc)), 4); 0376 0377 for (auto ac : factors) 0378 h += leftPad(encode83(encodeAC(ac, maximumValue)), 2); 0379 0380 return h; 0381 } 0382 } 0383 0384 #ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 0385 TEST_CASE("component packing") 0386 { 0387 for (int i = 0; i < 9 * 9; i++) 0388 CHECK(packComponents(unpackComponents(i)) == i); 0389 } 0390 0391 TEST_CASE("encode83") 0392 { 0393 CHECK(encode83(0) == "0"); 0394 0395 CHECK(encode83(packComponents({4, 3})) == "L"); 0396 CHECK(encode83(packComponents({4, 4})) == "U"); 0397 CHECK(encode83(packComponents({8, 4})) == "Y"); 0398 CHECK(encode83(packComponents({2, 1})) == "1"); 0399 } 0400 0401 TEST_CASE("decode83") 0402 { 0403 CHECK(packComponents({4, 3}) == decode83("L")); 0404 CHECK(packComponents({4, 4}) == decode83("U")); 0405 CHECK(packComponents({8, 4}) == decode83("Y")); 0406 CHECK(packComponents({2, 1}) == decode83("1")); 0407 } 0408 0409 TEST_CASE("maxAC") 0410 { 0411 for (int i = 0; i < 83; i++) 0412 CHECK(encodeMaxAC(decodeMaxAC(i)) == i); 0413 0414 CHECK(std::abs(decodeMaxAC("l"sv) - 0.289157f) < 0.00001f); 0415 } 0416 0417 TEST_CASE("DC") 0418 { 0419 CHECK(encode83(encodeDC(decodeDC("MF%n"))) == "MF%n"sv); 0420 CHECK(encode83(encodeDC(decodeDC("HV6n"))) == "HV6n"sv); 0421 CHECK(encode83(encodeDC(decodeDC("F5]+"))) == "F5]+"sv); 0422 CHECK(encode83(encodeDC(decodeDC("Pj0^"))) == "Pj0^"sv); 0423 CHECK(encode83(encodeDC(decodeDC("O2?U"))) == "O2?U"sv); 0424 } 0425 0426 TEST_CASE("AC") 0427 { 0428 auto h = "00%#MwS|WCWEM{R*bbWBbH"sv; 0429 for (size_t i = 0; i < h.size(); i += 2) { 0430 auto s = h.substr(i, 2); 0431 const auto maxAC = 0.289157f; 0432 CHECK(leftPad(encode83(encodeAC(decodeAC(decode83(s), maxAC), maxAC)), 2) == s); 0433 } 0434 } 0435 0436 TEST_CASE("decode") 0437 { 0438 blurhash::Image i1 = blurhash::decode("LEHV6nWB2yk8pyoJadR*.7kCMdnj", 360, 200); 0439 CHECK(i1.width == 360); 0440 CHECK(i1.height == 200); 0441 CHECK(i1.image.size() == i1.height * i1.width * 3); 0442 CHECK(i1.image[0] == 135); 0443 CHECK(i1.image[1] == 164); 0444 CHECK(i1.image[2] == 177); 0445 CHECK(i1.image[10000] == 173); 0446 CHECK(i1.image[10001] == 176); 0447 CHECK(i1.image[10002] == 163); 0448 // stbi_write_bmp("test.bmp", i1.width, i1.height, 3, (void *)i1.image.data()); 0449 0450 i1 = blurhash::decode("LGF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); 0451 CHECK(i1.width == 360); 0452 CHECK(i1.height == 200); 0453 CHECK(i1.image.size() == i1.height * i1.width * 3); 0454 // stbi_write_bmp("test2.bmp", i1.width, i1.height, 3, (void *)i1.image.data()); 0455 0456 // invalid inputs 0457 i1 = blurhash::decode(" LGF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); 0458 CHECK(i1.width == 0); 0459 CHECK(i1.height == 0); 0460 CHECK(i1.image.size() == 0); 0461 i1 = blurhash::decode(" LGF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); 0462 CHECK(i1.width == 0); 0463 CHECK(i1.height == 0); 0464 CHECK(i1.image.size() == 0); 0465 0466 i1 = blurhash::decode("LGF5]+Yk^6# M@-5c,1J5@[or[Q6.", 360, 200); 0467 CHECK(i1.width == 0); 0468 CHECK(i1.height == 0); 0469 CHECK(i1.image.size() == 0); 0470 i1 = blurhash::decode("LGF5]+Yk^6# M@-5c,1J5@[or[Q6.", 360, 200); 0471 CHECK(i1.width == 0); 0472 CHECK(i1.height == 0); 0473 CHECK(i1.image.size() == 0); 0474 0475 i1 = blurhash::decode("LGF5]+Yk^6# @-5c,1J5@[or[Q6.", 360, 200); 0476 CHECK(i1.width == 0); 0477 CHECK(i1.height == 0); 0478 CHECK(i1.image.size() == 0); 0479 i1 = blurhash::decode(" GF5]+Yk^6#M@-5c,1J5@[or[Q6.", 360, 200); 0480 CHECK(i1.width == 0); 0481 CHECK(i1.height == 0); 0482 CHECK(i1.image.size() == 0); 0483 } 0484 0485 TEST_CASE("encode") 0486 { 0487 CHECK(blurhash::encode(nullptr, 360, 200, 4, 3) == ""); 0488 0489 std::vector<unsigned char> black(360 * 200 * 3, 0); 0490 CHECK(blurhash::encode(black.data(), 0, 200, 4, 3) == ""); 0491 CHECK(blurhash::encode(black.data(), 360, 0, 4, 3) == ""); 0492 CHECK(blurhash::encode(black.data(), 360, 200, 0, 3) == ""); 0493 CHECK(blurhash::encode(black.data(), 360, 200, 4, 0) == ""); 0494 CHECK(blurhash::encode(black.data(), 360, 200, 4, 3) == "L00000fQfQfQfQfQfQfQfQfQfQfQ"); 0495 } 0496 #endif