Example #1
0
        /// <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());
        }
Example #2
0
        /// <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);
        }