// SPDX-FileCopyrightText: 2018 Wolt Enterprises // SPDX-License-Identifier: MIT #include "blurhash.h" #include #include #include #include #include namespace { const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"; struct Color { float r = 0; float g = 0; float b = 0; }; inline int linearTosRGB(float value) { float v = fmaxf(0, fminf(1, value)); if (v <= 0.0031308) return v * 12.92 * 255 + 0.5; else return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5; } inline float sRGBToLinear(int value) { float v = (float)value / 255; if (v <= 0.04045) return v / 12.92; else return powf((v + 0.055) / 1.055, 2.4); } inline float signPow(float value, float exp) { return copysignf(powf(fabsf(value), exp), value); } inline uint8_t clampToUByte(int *src) { if (*src >= 0 && *src <= 255) { return *src; } return (*src < 0) ? 0 : 255; } inline uint8_t *createByteArray(int size) { return (uint8_t *)malloc(size * sizeof(uint8_t)); } int decodeToInt(const char *string, int start, int end) { int value = 0; for (int iter1 = start; iter1 < end; iter1++) { int index = -1; for (int iter2 = 0; iter2 < 83; iter2++) { if (chars[iter2] == string[iter1]) { index = iter2; break; } } if (index == -1) { return -1; } value = value * 83 + index; } return value; } void decodeDC(int value, Color *color) { color->r = sRGBToLinear(value >> 16); color->g = sRGBToLinear((value >> 8) & 255); color->b = sRGBToLinear(value & 255); } void decodeAC(int value, float maximumValue, Color *color) { int quantR = (int)floorf(value / (19 * 19)); int quantG = (int)floorf(value / 19) % 19; int quantB = (int)value % 19; color->r = signPow(((float)quantR - 9) / 9, 2.0) * maximumValue; color->g = signPow(((float)quantG - 9) / 9, 2.0) * maximumValue; color->b = signPow(((float)quantB - 9) / 9, 2.0) * maximumValue; } int decodeToArray(const char *blurhash, int width, int height, int punch, int nChannels, uint8_t *pixelArray) { if (!isValidBlurhash(blurhash)) { return -1; } if (punch < 1) { punch = 1; } int sizeFlag = decodeToInt(blurhash, 0, 1); int numY = (int)floorf(sizeFlag / 9) + 1; int numX = (sizeFlag % 9) + 1; int iter = 0; Color color; int quantizedMaxValue = decodeToInt(blurhash, 1, 2); if (quantizedMaxValue == -1) { return -1; } const float maxValue = ((float)(quantizedMaxValue + 1)) / 166; const int colors_size = numX * numY; std::vector colors(colors_size, {0, 0, 0}); for (iter = 0; iter < colors_size; iter++) { if (iter == 0) { int value = decodeToInt(blurhash, 2, 6); if (value == -1) { return -1; } decodeDC(value, &color); colors[iter] = color; } else { int value = decodeToInt(blurhash, 4 + iter * 2, 6 + iter * 2); if (value == -1) { return -1; } decodeAC(value, maxValue * punch, &color); colors[iter] = color; } } int bytesPerRow = width * nChannels; int x = 0, y = 0, i = 0, j = 0; int intR = 0, intG = 0, intB = 0; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { float r = 0, g = 0, b = 0; for (j = 0; j < numY; j++) { for (i = 0; i < numX; i++) { float basics = cos((M_PI * x * i) / width) * cos((M_PI * y * j) / height); int idx = i + j * numX; r += colors[idx].r * basics; g += colors[idx].g * basics; b += colors[idx].b * basics; } } intR = linearTosRGB(r); intG = linearTosRGB(g); intB = linearTosRGB(b); pixelArray[nChannels * x + 0 + y * bytesPerRow] = clampToUByte(&intR); pixelArray[nChannels * x + 1 + y * bytesPerRow] = clampToUByte(&intG); pixelArray[nChannels * x + 2 + y * bytesPerRow] = clampToUByte(&intB); if (nChannels == 4) { pixelArray[nChannels * x + 3 + y * bytesPerRow] = 255; } } } return 0; } } uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels) { int bytesPerRow = width * nChannels; uint8_t *pixelArray = createByteArray(bytesPerRow * height); if (decodeToArray(blurhash, width, height, punch, nChannels, pixelArray) == -1) { return nullptr; } return pixelArray; } bool isValidBlurhash(const char *blurhash) { const int hashLength = strlen(blurhash); if (!blurhash || strlen(blurhash) < 6) { return false; } int sizeFlag = decodeToInt(blurhash, 0, 1); int numY = (int)floorf(sizeFlag / 9) + 1; int numX = (sizeFlag % 9) + 1; return hashLength == 4 + 2 * numX * numY; }