91 lines
3.0 KiB
C#
91 lines
3.0 KiB
C#
namespace PrivaPub.Domain.Media
|
|||
|
|
{
|
||
|
|
public static class Blurhash
|
||
|
|
{
|
||
|
|
const string Characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
||
|
|
|
||
|
|
public static string Encode(byte[] rgb, int width, int height, int componentsX = 4, int componentsY = 3)
|
||
|
|
{
|
||
|
|
var factors = new double[componentsY, componentsX, 3];
|
||
|
|
for (var j = 0; j < componentsY; j++)
|
||
|
|
for (var i = 0; i < componentsX; i++)
|
||
|
|
{
|
||
|
|
var normalisation = i == 0 && j == 0 ? 1 : 2;
|
||
|
|
double r = 0, g = 0, b = 0;
|
||
|
|
for (var y = 0; y < height; y++)
|
||
|
|
for (var x = 0; x < width; x++)
|
||
|
|
{
|
||
|
|
var basis = normalisation * Math.Cos(Math.PI * i * x / width) * Math.Cos(Math.PI * j * y / height);
|
||
|
|
var offset = (y * width + x) * 3;
|
||
|
|
r += basis * ToLinear(rgb[offset]);
|
||
|
|
g += basis * ToLinear(rgb[offset + 1]);
|
||
|
|
b += basis * ToLinear(rgb[offset + 2]);
|
||
|
|
}
|
||
|
|
var scale = 1.0 / (width * height);
|
||
|
|
factors[j, i, 0] = r * scale;
|
||
|
|
factors[j, i, 1] = g * scale;
|
||
|
|
factors[j, i, 2] = b * scale;
|
||
|
|
}
|
||
|
|
|
||
|
|
var hash = new System.Text.StringBuilder();
|
||
|
|
hash.Append(Base83((componentsX - 1) + (componentsY - 1) * 9, 1));
|
||
|
|
|
||
|
|
double maximum = 0;
|
||
|
|
for (var j = 0; j < componentsY; j++)
|
||
|
|
for (var i = 0; i < componentsX; i++)
|
||
|
|
if (i != 0 || j != 0)
|
||
|
|
for (var c = 0; c < 3; c++)
|
||
|
|
maximum = Math.Max(maximum, Math.Abs(factors[j, i, c]));
|
||
|
|
double maximumValue;
|
||
|
|
if (componentsX * componentsY > 1)
|
||
|
|
{
|
||
|
|
var quantised = (int)Math.Max(0, Math.Min(82, Math.Floor(maximum * 166 - 0.5)));
|
||
|
|
maximumValue = (quantised + 1) / 166.0;
|
||
|
|
hash.Append(Base83(quantised, 1));
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
maximumValue = 1;
|
||
|
|
hash.Append(Base83(0, 1));
|
||
|
|
}
|
||
|
|
|
||
|
|
var dc = (ToSrgb(factors[0, 0, 0]) << 16) + (ToSrgb(factors[0, 0, 1]) << 8) + ToSrgb(factors[0, 0, 2]);
|
||
|
|
hash.Append(Base83(dc, 4));
|
||
|
|
for (var j = 0; j < componentsY; j++)
|
||
|
|
for (var i = 0; i < componentsX; i++)
|
||
|
|
{
|
||
|
|
if (i == 0 && j == 0)
|
||
|
|
continue;
|
||
|
|
int Quantise(double value) => (int)Math.Max(0, Math.Min(18, Math.Floor(SignPow(value / maximumValue, 0.5) * 9 + 9.5)));
|
||
|
|
var ac = Quantise(factors[j, i, 0]) * 19 * 19 + Quantise(factors[j, i, 1]) * 19 + Quantise(factors[j, i, 2]);
|
||
|
|
hash.Append(Base83(ac, 2));
|
||
|
|
}
|
||
|
|
return hash.ToString();
|
||
|
|
}
|
||
|
|
|
||
|
|
static double ToLinear(byte value)
|
||
|
|
{
|
||
|
|
var v = value / 255.0;
|
||
|
|
return v <= 0.04045 ? v / 12.92 : Math.Pow((v + 0.055) / 1.055, 2.4);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int ToSrgb(double value)
|
||
|
|
{
|
||
|
|
var v = Math.Max(0, Math.Min(1, value));
|
||
|
|
return v <= 0.0031308 ? (int)(v * 12.92 * 255 + 0.5) : (int)((1.055 * Math.Pow(v, 1 / 2.4) - 0.055) * 255 + 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
static double SignPow(double value, double exponent) => Math.CopySign(Math.Pow(Math.Abs(value), exponent), value);
|
||
|
|
|
||
|
|
static string Base83(int value, int length)
|
||
|
|
{
|
||
|
|
var result = new char[length];
|
||
|
|
for (var i = 1; i <= length; i++)
|
||
|
|
{
|
||
|
|
var digit = value / (int)Math.Pow(83, length - i) % 83;
|
||
|
|
result[i - 1] = Characters[digit];
|
||
|
|
}
|
||
|
|
return new string(result);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|