/// <summary> /// Encodes a 2-dimensional array of pixels into a Blurhash string /// </summary> /// <param name="pixels">The 2-dimensional array of pixels to encode</param> /// <param name="componentsX">The number of components used on the X-Axis for the DCT</param> /// <param name="componentsY">The number of components used on the Y-Axis for the DCT</param> /// <returns>The resulting Blurhash string</returns> protected string CoreEncode(Pixel[,] pixels, int componentsX, int componentsY) { if (componentsX < 1) { throw new ArgumentException("componentsX needs to be at least 1"); } if (componentsX > 9) { throw new ArgumentException("componentsX needs to be at most 9"); } if (componentsY < 1) { throw new ArgumentException("componentsY needs to be at least 1"); } if (componentsY > 9) { throw new ArgumentException("componentsY needs to be at most 9"); } var factors = new Pixel[componentsX, componentsY]; var components = Enumerable .Range(0, componentsX) .SelectMany(i => Enumerable.Range(0, componentsY).Select(j => new Coordinate(i, j))) .ToArray(); // Create tuples (i,j) for all components var factorCount = componentsX * componentsY; var factorIndex = 0; var locker = new object(); // Parallel Parallel.ForEach(components, (coordinate) => { factors[coordinate.X, coordinate.Y] = MultiplyBasisFunction(coordinate.X, coordinate.Y, pixels); lock (locker) { ProgressCallback?.Invoke((double)factorIndex / factorCount); factorIndex++; } }); var dc = factors[0, 0]; var acCount = componentsX * componentsY - 1; var resultBuilder = new StringBuilder(); var sizeFlag = (componentsX - 1) + (componentsY - 1) * 9; resultBuilder.Append(sizeFlag.EncodeBase83(1)); float maximumValue; if (acCount > 0) { // Get maximum absolute value of all AC components var actualMaximumValue = 0.0; for (var y = 0; y < componentsY; y++) { for (var x = 0; x < componentsX; x++) { // Ignore DC component if (x == 0 && y == 0) { continue; } actualMaximumValue = Math.Max(Math.Abs(factors[x, y].Red), actualMaximumValue); actualMaximumValue = Math.Max(Math.Abs(factors[x, y].Green), actualMaximumValue); actualMaximumValue = Math.Max(Math.Abs(factors[x, y].Blue), actualMaximumValue); } } var quantizedMaximumValue = (int)Math.Max(0.0, Math.Min(82.0, Math.Floor(actualMaximumValue * 166 - 0.5))); maximumValue = ((float)quantizedMaximumValue + 1) / 166; resultBuilder.Append(quantizedMaximumValue.EncodeBase83(1)); } else { maximumValue = 1; resultBuilder.Append(0.EncodeBase83(1)); } resultBuilder.Append(EncodeDc(dc.Red, dc.Green, dc.Blue).EncodeBase83(4)); for (var y = 0; y < componentsY; y++) { for (var x = 0; x < componentsX; x++) { // Ignore DC component if (x == 0 && y == 0) { continue; } resultBuilder.Append(EncodeAc(factors[x, y].Red, factors[x, y].Green, factors[x, y].Blue, maximumValue).EncodeBase83(2)); } } return(resultBuilder.ToString()); }
/// <summary> /// Decodes a Blurhash string into a 2-dimensional array of pixels /// </summary> /// <param name="blurhash">The blurhash string to decode</param> /// <param name="outputWidth">The desired width of the output in pixels</param> /// <param name="outputHeight">The desired height of the output in pixels</param> /// <param name="punch">A value that affects the contrast of the decoded image. 1 means normal, smaller values will make the effect more subtle, and larger values will make it stronger.</param> /// <returns>A 2-dimensional array of <see cref="Pixel"/>s </returns> protected Pixel[,] CoreDecode(string blurhash, int outputWidth, int outputHeight, double punch = 1.0) { if (blurhash.Length < 6) { throw new ArgumentException("Blurhash value needs to be at least 6 characters", nameof(blurhash)); } var sizeFlag = (int)new[] { blurhash[0] }.DecodeBase83Integer(); var componentsY = sizeFlag / 9 + 1; var componentsX = sizeFlag % 9 + 1; if (blurhash.Length != 4 + 2 * componentsX * componentsY) { throw new ArgumentException("Blurhash value is missing data", nameof(blurhash)); } var quantizedMaximumValue = (double)new[] { blurhash[1] }.DecodeBase83Integer(); var maximumValue = (quantizedMaximumValue + 1.0) / 166.0; var coefficients = new Pixel[componentsX, componentsY]; { var i = 0; for (var y = 0; y < componentsY; y++) { for (var x = 0; x < componentsX; x++) { if (x == 0 && y == 0) { var substring = blurhash.Substring(2, 4); var value = substring.DecodeBase83Integer(); coefficients[x, y] = DecodeDc(value); } else { var substring = blurhash.Substring(4 + i * 2, 2); var value = substring.DecodeBase83Integer(); coefficients[x, y] = DecodeAc(value, maximumValue * punch); } i++; } } } var pixels = new Pixel[outputWidth, outputHeight]; var pixelCount = outputHeight * outputWidth; var currentPixel = 0; var coordinates = Enumerable.Range(0, outputWidth) .SelectMany(x => Enumerable.Range(0, outputHeight).Select(y => new Coordinate(x, y))) .ToArray(); var locker = new object(); Parallel.ForEach(coordinates, (coordinate) => { pixels[coordinate.X, coordinate.Y] = DecodePixel(componentsY, componentsX, coordinate.X, coordinate.Y, outputWidth, outputHeight, coefficients); lock (locker) { ProgressCallback?.Invoke((double)currentPixel / pixelCount); currentPixel++; } }); return(pixels); }