2023-08-12 09:14:14 +00:00
|
|
|
#include "decode.h"
|
|
|
|
#include "common.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
static char chars[83] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
static inline uint8_t clampToUByte(int* src)
|
|
|
|
{
|
|
|
|
if (*src >= 0 && *src <= 255)
|
2023-08-12 09:14:14 +00:00
|
|
|
return *src;
|
|
|
|
return (*src < 0) ? 0 : 255;
|
|
|
|
}
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
int decodeToInt(uint8_t* string, int start, int end)
|
|
|
|
{
|
2023-08-12 09:14:14 +00:00
|
|
|
int value = 0, iter1 = 0, iter2 = 0;
|
2023-08-15 02:24:20 +00:00
|
|
|
for (iter1 = start; iter1 < end; iter1++) {
|
2023-08-12 09:14:14 +00:00
|
|
|
int index = -1;
|
2023-08-15 02:24:20 +00:00
|
|
|
for (iter2 = 0; iter2 < 83; iter2++) {
|
2023-08-12 09:14:14 +00:00
|
|
|
if (chars[iter2] == string[iter1]) {
|
|
|
|
index = iter2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-08-15 02:24:20 +00:00
|
|
|
if (index == -1)
|
|
|
|
return -1;
|
2023-08-12 09:14:14 +00:00
|
|
|
value = value * 83 + index;
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
bool isValidBlurhash(uint8_t* blurhash)
|
|
|
|
{
|
|
|
|
const int hashLength = strlen((const char*)blurhash);
|
2023-08-12 09:14:14 +00:00
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
if (!blurhash || strlen((const char*)blurhash) < 6)
|
|
|
|
return false;
|
2023-08-12 09:14:14 +00:00
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
// Get size from first character
|
|
|
|
int sizeFlag = decodeToInt(blurhash, 0, 1);
|
2023-08-12 09:14:14 +00:00
|
|
|
|
|
|
|
int numY = (int)floorf(sizeFlag / 9) + 1;
|
|
|
|
int numX = (sizeFlag % 9) + 1;
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
if (hashLength != 4 + 2 * numX * numY)
|
|
|
|
return false;
|
2023-08-12 09:14:14 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
void decodeDC(int value, float* r, float* g, float* b)
|
|
|
|
{
|
|
|
|
*r = sRGBToLinear(value >> 16); // R-component
|
2023-08-12 09:14:14 +00:00
|
|
|
*g = sRGBToLinear((value >> 8) & 255); // G-Component
|
2023-08-15 02:24:20 +00:00
|
|
|
*b = sRGBToLinear(value & 255); // B-Component
|
2023-08-12 09:14:14 +00:00
|
|
|
}
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
void decodeAC(int value, float maximumValue, float* r, float* g, float* b)
|
|
|
|
{
|
2023-08-12 09:14:14 +00:00
|
|
|
int quantR = (int)floorf(value / (19 * 19));
|
|
|
|
int quantG = (int)floorf(value / 19) % 19;
|
|
|
|
int quantB = (int)value % 19;
|
|
|
|
|
|
|
|
*r = signPow(((float)quantR - 9) / 9, 2.0) * maximumValue;
|
|
|
|
*g = signPow(((float)quantG - 9) / 9, 2.0) * maximumValue;
|
|
|
|
*b = signPow(((float)quantB - 9) / 9, 2.0) * maximumValue;
|
|
|
|
}
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
int decodeToArray(uint8_t* blurhash, int width, int height, int punch, int nChannels, uint8_t* pixelArray)
|
|
|
|
{
|
|
|
|
if (!isValidBlurhash(blurhash))
|
|
|
|
return -1;
|
|
|
|
if (punch < 1)
|
|
|
|
punch = 1;
|
2023-08-12 09:14:14 +00:00
|
|
|
|
|
|
|
int sizeFlag = decodeToInt(blurhash, 0, 1);
|
|
|
|
int numY = (int)floorf(sizeFlag / 9) + 1;
|
|
|
|
int numX = (sizeFlag % 9) + 1;
|
|
|
|
int iter = 0;
|
|
|
|
|
|
|
|
float r = 0, g = 0, b = 0;
|
|
|
|
int quantizedMaxValue = decodeToInt(blurhash, 1, 2);
|
2023-08-15 02:24:20 +00:00
|
|
|
if (quantizedMaxValue == -1)
|
|
|
|
return -1;
|
2023-08-12 09:14:14 +00:00
|
|
|
|
|
|
|
float maxValue = ((float)(quantizedMaxValue + 1)) / 166;
|
|
|
|
|
|
|
|
int colors_size = numX * numY;
|
|
|
|
float colors[colors_size][3];
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
for (iter = 0; iter < colors_size; iter++) {
|
2023-08-12 09:14:14 +00:00
|
|
|
if (iter == 0) {
|
|
|
|
int value = decodeToInt(blurhash, 2, 6);
|
2023-08-15 02:24:20 +00:00
|
|
|
if (value == -1)
|
|
|
|
return -1;
|
2023-08-12 09:14:14 +00:00
|
|
|
decodeDC(value, &r, &g, &b);
|
|
|
|
colors[iter][0] = r;
|
|
|
|
colors[iter][1] = g;
|
|
|
|
colors[iter][2] = b;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
int value = decodeToInt(blurhash, 4 + iter * 2, 6 + iter * 2);
|
2023-08-15 02:24:20 +00:00
|
|
|
if (value == -1)
|
|
|
|
return -1;
|
2023-08-12 09:14:14 +00:00
|
|
|
decodeAC(value, maxValue * punch, &r, &g, &b);
|
|
|
|
colors[iter][0] = r;
|
|
|
|
colors[iter][1] = g;
|
|
|
|
colors[iter][2] = b;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytesPerRow = width * nChannels;
|
|
|
|
int x = 0, y = 0, i = 0, j = 0;
|
|
|
|
int intR = 0, intG = 0, intB = 0;
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
for (y = 0; y < height; y++) {
|
|
|
|
for (x = 0; x < width; x++) {
|
2023-08-12 09:14:14 +00:00
|
|
|
|
|
|
|
float r = 0, g = 0, b = 0;
|
|
|
|
|
2023-08-15 02:24:20 +00:00
|
|
|
for (j = 0; j < numY; j++) {
|
|
|
|
for (i = 0; i < numX; i++) {
|
2023-08-12 09:14:14 +00:00
|
|
|
float basics = cos((M_PI * x * i) / width) * cos((M_PI * y * j) / height);
|
|
|
|
int idx = i + j * numX;
|
|
|
|
r += colors[idx][0] * basics;
|
|
|
|
g += colors[idx][1] * basics;
|
|
|
|
b += colors[idx][2] * 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)
|
2023-08-15 02:24:20 +00:00
|
|
|
// If nChannels=4, treat each pixel as RGBA instead of RGB
|
|
|
|
pixelArray[nChannels * x + 3 + y * bytesPerRow] = 255;
|
2023-08-12 09:14:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|