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); } } }