File indexing completed on 2024-10-06 12:53:58

0001 // SPDX-FileCopyrightText: 2018 Wolt Enterprises
0002 // SPDX-License-Identifier: MIT
0003 
0004 #include "blurhash.h"
0005 
0006 #include <math.h>
0007 #include <stdbool.h>
0008 #include <stdlib.h>
0009 #include <string.h>
0010 #include <vector>
0011 
0012 namespace
0013 {
0014 const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
0015 
0016 struct Color {
0017     float r = 0;
0018     float g = 0;
0019     float b = 0;
0020 };
0021 
0022 inline int linearTosRGB(float value)
0023 {
0024     float v = fmaxf(0, fminf(1, value));
0025     if (v <= 0.0031308)
0026         return v * 12.92 * 255 + 0.5;
0027     else
0028         return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5;
0029 }
0030 
0031 inline float sRGBToLinear(int value)
0032 {
0033     float v = (float)value / 255;
0034     if (v <= 0.04045)
0035         return v / 12.92;
0036     else
0037         return powf((v + 0.055) / 1.055, 2.4);
0038 }
0039 
0040 inline float signPow(float value, float exp)
0041 {
0042     return copysignf(powf(fabsf(value), exp), value);
0043 }
0044 
0045 inline uint8_t clampToUByte(int *src)
0046 {
0047     if (*src >= 0 && *src <= 255) {
0048         return *src;
0049     }
0050     return (*src < 0) ? 0 : 255;
0051 }
0052 
0053 inline uint8_t *createByteArray(int size)
0054 {
0055     return (uint8_t *)malloc(size * sizeof(uint8_t));
0056 }
0057 
0058 int decodeToInt(const char *string, int start, int end)
0059 {
0060     int value = 0;
0061     for (int iter1 = start; iter1 < end; iter1++) {
0062         int index = -1;
0063         for (int iter2 = 0; iter2 < 83; iter2++) {
0064             if (chars[iter2] == string[iter1]) {
0065                 index = iter2;
0066                 break;
0067             }
0068         }
0069         if (index == -1) {
0070             return -1;
0071         }
0072         value = value * 83 + index;
0073     }
0074     return value;
0075 }
0076 
0077 void decodeDC(int value, Color *color)
0078 {
0079     color->r = sRGBToLinear(value >> 16);
0080     color->g = sRGBToLinear((value >> 8) & 255);
0081     color->b = sRGBToLinear(value & 255);
0082 }
0083 
0084 void decodeAC(int value, float maximumValue, Color *color)
0085 {
0086     int quantR = (int)floorf(value / (19 * 19));
0087     int quantG = (int)floorf(value / 19) % 19;
0088     int quantB = (int)value % 19;
0089 
0090     color->r = signPow(((float)quantR - 9) / 9, 2.0) * maximumValue;
0091     color->g = signPow(((float)quantG - 9) / 9, 2.0) * maximumValue;
0092     color->b = signPow(((float)quantB - 9) / 9, 2.0) * maximumValue;
0093 }
0094 
0095 int decodeToArray(const char *blurhash, int width, int height, int punch, int nChannels, uint8_t *pixelArray)
0096 {
0097     if (!isValidBlurhash(blurhash)) {
0098         return -1;
0099     }
0100     if (punch < 1) {
0101         punch = 1;
0102     }
0103 
0104     int sizeFlag = decodeToInt(blurhash, 0, 1);
0105     int numY = (int)floorf(sizeFlag / 9) + 1;
0106     int numX = (sizeFlag % 9) + 1;
0107     int iter = 0;
0108 
0109     Color color;
0110     int quantizedMaxValue = decodeToInt(blurhash, 1, 2);
0111     if (quantizedMaxValue == -1) {
0112         return -1;
0113     }
0114 
0115     const float maxValue = ((float)(quantizedMaxValue + 1)) / 166;
0116 
0117     const int colors_size = numX * numY;
0118 
0119     std::vector<Color> colors(colors_size, {0, 0, 0});
0120 
0121     for (iter = 0; iter < colors_size; iter++) {
0122         if (iter == 0) {
0123             int value = decodeToInt(blurhash, 2, 6);
0124             if (value == -1) {
0125                 return -1;
0126             }
0127             decodeDC(value, &color);
0128             colors[iter] = color;
0129         } else {
0130             int value = decodeToInt(blurhash, 4 + iter * 2, 6 + iter * 2);
0131             if (value == -1) {
0132                 return -1;
0133             }
0134             decodeAC(value, maxValue * punch, &color);
0135             colors[iter] = color;
0136         }
0137     }
0138 
0139     int bytesPerRow = width * nChannels;
0140     int x = 0, y = 0, i = 0, j = 0;
0141     int intR = 0, intG = 0, intB = 0;
0142 
0143     for (y = 0; y < height; y++) {
0144         for (x = 0; x < width; x++) {
0145             float r = 0, g = 0, b = 0;
0146 
0147             for (j = 0; j < numY; j++) {
0148                 for (i = 0; i < numX; i++) {
0149                     float basics = cos((M_PI * x * i) / width) * cos((M_PI * y * j) / height);
0150                     int idx = i + j * numX;
0151                     r += colors[idx].r * basics;
0152                     g += colors[idx].g * basics;
0153                     b += colors[idx].b * basics;
0154                 }
0155             }
0156 
0157             intR = linearTosRGB(r);
0158             intG = linearTosRGB(g);
0159             intB = linearTosRGB(b);
0160 
0161             pixelArray[nChannels * x + 0 + y * bytesPerRow] = clampToUByte(&intR);
0162             pixelArray[nChannels * x + 1 + y * bytesPerRow] = clampToUByte(&intG);
0163             pixelArray[nChannels * x + 2 + y * bytesPerRow] = clampToUByte(&intB);
0164 
0165             if (nChannels == 4) {
0166                 pixelArray[nChannels * x + 3 + y * bytesPerRow] = 255;
0167             }
0168         }
0169     }
0170     return 0;
0171 }
0172 }
0173 
0174 uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels)
0175 {
0176     int bytesPerRow = width * nChannels;
0177     uint8_t *pixelArray = createByteArray(bytesPerRow * height);
0178 
0179     if (decodeToArray(blurhash, width, height, punch, nChannels, pixelArray) == -1) {
0180         return NULL;
0181     }
0182     return pixelArray;
0183 }
0184 
0185 bool isValidBlurhash(const char *blurhash)
0186 {
0187     const int hashLength = strlen(blurhash);
0188 
0189     if (!blurhash || strlen(blurhash) < 6) {
0190         return false;
0191     }
0192 
0193     int sizeFlag = decodeToInt(blurhash, 0, 1);
0194     int numY = (int)floorf(sizeFlag / 9) + 1;
0195     int numX = (sizeFlag % 9) + 1;
0196 
0197     return hashLength == 4 + 2 * numX * numY;
0198 }