예제 #1
0
        /// <summary>
        /// Writes the palette chunk to the stream.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        /// <param name="header">The <see cref="PngHeader"/>.</param>
        /// <param name="image">The image to encode.</param>
        /// <returns>The <see cref="QuantizedImage{TPixel}"/></returns>
        private QuantizedImage <TPixel> WritePaletteChunk <TPixel>(Stream stream, PngHeader header, ImageBase <TPixel> image)
            where TPixel : struct, IPixel <TPixel>
        {
            if (this.quality > 256)
            {
                return(null);
            }

            if (this.quantizer == null)
            {
                this.quantizer = new WuQuantizer <TPixel>();
            }

            // Quantize the image returning a palette. This boxing is icky.
            QuantizedImage <TPixel> quantized = ((IQuantizer <TPixel>) this.quantizer).Quantize(image, this.quality);

            // Grab the palette and write it to the stream.
            TPixel[] palette    = quantized.Palette;
            byte     pixelCount = palette.Length.ToByte();

            // Get max colors for bit depth.
            int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;

            byte[] colorTable = ArrayPool <byte> .Shared.Rent(colorTableLength);

            byte[] alphaTable = ArrayPool <byte> .Shared.Rent(pixelCount);

            byte[] bytes = ArrayPool <byte> .Shared.Rent(4);

            bool anyAlpha = false;

            try
            {
                for (byte i = 0; i < pixelCount; i++)
                {
                    if (quantized.Pixels.Contains(i))
                    {
                        int offset = i * 3;
                        palette[i].ToXyzwBytes(bytes, 0);

                        byte alpha = bytes[3];

                        colorTable[offset]     = bytes[0];
                        colorTable[offset + 1] = bytes[1];
                        colorTable[offset + 2] = bytes[2];

                        if (alpha > this.options.Threshold)
                        {
                            alpha = 255;
                        }

                        anyAlpha      = anyAlpha || alpha < 255;
                        alphaTable[i] = alpha;
                    }
                }

                this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);

                // Write the transparency data
                if (anyAlpha)
                {
                    this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount);
                }
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(colorTable);

                ArrayPool <byte> .Shared.Return(alphaTable);

                ArrayPool <byte> .Shared.Return(bytes);
            }

            return(quantized);
        }
예제 #2
0
        /// <summary>
        /// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="image">The <see cref="ImageBase{TPixel}"/> to encode from.</param>
        /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
        public void Encode <TPixel>(Image <TPixel> image, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            this.width  = image.Width;
            this.height = image.Height;

            // Write the png header.
            this.chunkDataBuffer[0] = 0x89; // Set the high bit.
            this.chunkDataBuffer[1] = 0x50; // P
            this.chunkDataBuffer[2] = 0x4E; // N
            this.chunkDataBuffer[3] = 0x47; // G
            this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF
            this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF
            this.chunkDataBuffer[6] = 0x1A; // EOF
            this.chunkDataBuffer[7] = 0x0A; // LF

            stream.Write(this.chunkDataBuffer, 0, 8);

            // Ensure that quality can be set but has a fallback.
            this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality;
            this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue;

            this.pngColorType = this.options.PngColorType;
            this.quantizer    = this.options.Quantizer;

            // Set correct color type if the color count is 256 or less.
            if (this.quality <= 256)
            {
                this.pngColorType = PngColorType.Palette;
            }

            if (this.pngColorType == PngColorType.Palette && this.quality > 256)
            {
                this.quality = 256;
            }

            // Set correct bit depth.
            this.bitDepth = this.quality <= 256
                               ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).Clamp(1, 8)
                               : (byte)8;

            // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
            if (this.bitDepth == 3)
            {
                this.bitDepth = 4;
            }
            else if (this.bitDepth >= 5 || this.bitDepth <= 7)
            {
                this.bitDepth = 8;
            }

            this.bytesPerPixel = this.CalculateBytesPerPixel();

            PngHeader header = new PngHeader
            {
                Width             = image.Width,
                Height            = image.Height,
                ColorType         = this.pngColorType,
                BitDepth          = this.bitDepth,
                FilterMethod      = 0, // None
                CompressionMethod = 0,
                InterlaceMethod   = 0
            };

            this.WriteHeaderChunk(stream, header);

            // Collect the indexed pixel data
            if (this.pngColorType == PngColorType.Palette)
            {
                this.CollectIndexedBytes(image, stream, header);
            }

            this.WritePhysicalChunk(stream, image);
            this.WriteGammaChunk(stream);
            using (PixelAccessor <TPixel> pixels = image.Lock())
            {
                this.WriteDataChunks(pixels, stream);
            }

            this.WriteEndChunk(stream);
            stream.Flush();
        }
예제 #3
0
        /// <summary>
        /// Collects the indexed pixel data.
        /// </summary>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <param name="image">The image to encode.</param>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        /// <param name="header">The <see cref="PngHeader"/>.</param>
        private void CollectIndexedBytes <TPixel>(ImageBase <TPixel> image, Stream stream, PngHeader header)
            where TPixel : struct, IPixel <TPixel>
        {
            // Quantize the image and get the pixels.
            QuantizedImage <TPixel> quantized = this.WritePaletteChunk(stream, header, image);

            this.palettePixelData = quantized.Pixels;
        }
예제 #4
0
        /// <summary>
        /// Writes the palette chunk to the stream.
        /// </summary>
        /// <typeparam name="TColor">The pixel format.</typeparam>
        /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        /// <param name="header">The <see cref="PngHeader"/>.</param>
        /// <param name="image">The image to encode.</param>
        /// <returns>The <see cref="QuantizedImage{TColor, TPacked}"/></returns>
        private QuantizedImage <TColor, TPacked> WritePaletteChunk <TColor, TPacked>(Stream stream, PngHeader header, ImageBase <TColor, TPacked> image)
            where TColor : struct, IPackedPixel <TPacked>
            where TPacked : struct
        {
            if (this.Quality > 256)
            {
                return(null);
            }

            if (this.Quantizer == null)
            {
                this.Quantizer = new WuQuantizer <TColor, TPacked>();
            }

            // Quantize the image returning a palette. This boxing is icky.
            QuantizedImage <TColor, TPacked> quantized = ((IQuantizer <TColor, TPacked>) this.Quantizer).Quantize(image, this.Quality);

            // Grab the palette and write it to the stream.
            TColor[]    palette           = quantized.Palette;
            int         pixelCount        = palette.Length;
            List <byte> transparentPixels = new List <byte>();

            // Get max colors for bit depth.
            int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;

            byte[] colorTable = ArrayPool <byte> .Shared.Rent(colorTableLength);

            byte[] bytes = ArrayPool <byte> .Shared.Rent(4);

            try
            {
                for (int i = 0; i < pixelCount; i++)
                {
                    int offset = i * 3;
                    palette[i].ToBytes(bytes, 0, ComponentOrder.XYZW);

                    int alpha = bytes[3];

                    colorTable[offset]     = bytes[0];
                    colorTable[offset + 1] = bytes[1];
                    colorTable[offset + 2] = bytes[2];

                    if (alpha <= this.Threshold)
                    {
                        transparentPixels.Add((byte)offset);
                    }
                }

                this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(colorTable);

                ArrayPool <byte> .Shared.Return(bytes);
            }

            // Write the transparency data
            if (transparentPixels.Any())
            {
                this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
            }

            return(quantized);
        }
예제 #5
0
        /// <summary>
        /// Writes the palette chunk to the stream.
        /// </summary>
        /// <typeparam name="TColor">The pixel format.</typeparam>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        /// <param name="header">The <see cref="PngHeader"/>.</param>
        /// <param name="image">The image to encode.</param>
        /// <returns>The <see cref="QuantizedImage{TColor}"/></returns>
        private QuantizedImage <TColor> WritePaletteChunk <TColor>(Stream stream, PngHeader header, ImageBase <TColor> image)
            where TColor : struct, IPixel <TColor>
        {
            if (this.quality > 256)
            {
                return(null);
            }

            if (this.quantizer == null)
            {
                this.quantizer = new OctreeQuantizer <TColor>();
            }

            // Quantize the image returning a palette. This boxing is icky.
            QuantizedImage <TColor> quantized = ((IQuantizer <TColor>) this.quantizer).Quantize(image, this.quality);

            // Grab the palette and write it to the stream.
            TColor[]    palette           = quantized.Palette;
            int         pixelCount        = palette.Length;
            List <byte> transparentPixels = new List <byte>();

            // Get max colors for bit depth.
            int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;

            byte[] colorTable = ArrayPool <byte> .Shared.Rent(colorTableLength);

            byte[] bytes = ArrayPool <byte> .Shared.Rent(4);

            try
            {
                for (int i = 0; i < pixelCount; i++)
                {
                    int offset = i * 3;
                    palette[i].ToXyzwBytes(bytes, 0);

                    int alpha = bytes[3];

                    colorTable[offset]     = bytes[0];
                    colorTable[offset + 1] = bytes[1];
                    colorTable[offset + 2] = bytes[2];

                    if (alpha < 255 && alpha <= this.options.Threshold)
                    {
                        // Ensure the index is actually being used in our array.
                        // I'd like to find a faster way of doing this.
                        if (quantized.Pixels.Contains((byte)i))
                        {
                            transparentPixels.Add((byte)i);
                        }
                    }
                }

                this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength);
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(colorTable);

                ArrayPool <byte> .Shared.Return(bytes);
            }

            // Write the transparency data
            if (transparentPixels.Any())
            {
                this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
            }

            return(quantized);
        }
예제 #6
0
        /// <summary>
        /// Writes the palette chunk to the stream.
        /// </summary>
        /// <typeparam name="TColor">The pixel format.</typeparam>
        /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        /// <param name="header">The <see cref="PngHeader"/>.</param>
        /// <param name="image">The image to encode.</param>
        /// <returns>The <see cref="QuantizedImage{TColor, TPacked}"/></returns>
        private QuantizedImage <TColor, TPacked> WritePaletteChunk <TColor, TPacked>(Stream stream, PngHeader header, ImageBase <TColor, TPacked> image)
            where TColor : struct, IPackedPixel <TPacked>
            where TPacked : struct
        {
            if (this.Quality > 256)
            {
                return(null);
            }

            if (this.Quantizer == null)
            {
                this.Quantizer = new WuQuantizer <TColor, TPacked>();
            }

            // Quantize the image returning a palette. This boxing is icky.
            QuantizedImage <TColor, TPacked> quantized = ((IQuantizer <TColor, TPacked>) this.Quantizer).Quantize(image, this.Quality);

            // Grab the palette and write it to the stream.
            TColor[]    palette           = quantized.Palette;
            int         pixelCount        = palette.Length;
            List <byte> transparentPixels = new List <byte>();

            // Get max colors for bit depth.
            int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;

            byte[] colorTable = new byte[colorTableLength];

            // TODO: Optimize this.
            Parallel.For(
                0,
                pixelCount,
                Bootstrapper.Instance.ParallelOptions,
                i =>
            {
                int offset  = i * 3;
                Color color = new Color(palette[i].ToVector4());
                int alpha   = color.A;

                // Premultiply the color. This helps prevent banding.
                if (alpha < 255 && alpha > this.Threshold)
                {
                    color = Color.Multiply(color, new Color(alpha, alpha, alpha, 255));
                }

                colorTable[offset]     = color.R;
                colorTable[offset + 1] = color.G;
                colorTable[offset + 2] = color.B;

                if (alpha <= this.Threshold)
                {
                    transparentPixels.Add((byte)offset);
                }
            });

            this.WriteChunk(stream, PngChunkTypes.Palette, colorTable);

            // Write the transparency data
            if (transparentPixels.Any())
            {
                this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray());
            }

            return(quantized);
        }
예제 #7
0
        /// <summary>
        /// Collects the indexed pixel data.
        /// </summary>
        /// <typeparam name="TColor">The pixel format.</typeparam>
        /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
        /// <param name="image">The image to encode.</param>
        /// <param name="stream">The <see cref="Stream"/> containing image data.</param>
        /// <param name="header">The <see cref="PngHeader"/>.</param>
        private void CollectIndexedBytes <TColor, TPacked>(ImageBase <TColor, TPacked> image, Stream stream, PngHeader header)
            where TColor : struct, IPackedPixel <TPacked>
            where TPacked : struct
        {
            // Quatize the image and get the pixels
            QuantizedImage <TColor, TPacked> quantized = this.WritePaletteChunk(stream, header, image);

            this.pixelData = quantized.Pixels;
        }
예제 #8
0
        /// <summary>
        /// Encodes the image to the specified stream from the <see cref="ImageBase{TColor, TPacked}"/>.
        /// </summary>
        /// <typeparam name="TColor">The pixel format.</typeparam>
        /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
        /// <param name="image">The <see cref="ImageBase{TColor, TPacked}"/> to encode from.</param>
        /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
        public void Encode <TColor, TPacked>(ImageBase <TColor, TPacked> image, Stream stream)
            where TColor : struct, IPackedPixel <TPacked>
            where TPacked : struct
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            this.width  = image.Width;
            this.height = image.Height;

            // Write the png header.
            stream.Write(
                new byte[]
            {
                0x89,     // Set the high bit.
                0x50,     // P
                0x4E,     // N
                0x47,     // G
                0x0D,     // Line ending CRLF
                0x0A,     // Line ending CRLF
                0x1A,     // EOF
                0x0A      // LF
            },
                0,
                8);

            // Ensure that quality can be set but has a fallback.
            int quality = this.Quality > 0 ? this.Quality : image.Quality;

            this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue;

            // Set correct color type if the color count is 256 or less.
            if (this.Quality <= 256)
            {
                this.PngColorType = PngColorType.Palette;
            }

            // Set correct bit depth.
            this.bitDepth = this.Quality <= 256
                               ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)
                               : (byte)8;

            // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
            if (this.bitDepth == 3)
            {
                this.bitDepth = 4;
            }
            else if (this.bitDepth >= 5 || this.bitDepth <= 7)
            {
                this.bitDepth = 8;
            }

            this.bytesPerPixel = this.CalculateBytesPerPixel();

            PngHeader header = new PngHeader
            {
                Width             = image.Width,
                Height            = image.Height,
                ColorType         = (byte)this.PngColorType,
                BitDepth          = this.bitDepth,
                FilterMethod      = 0, // None
                CompressionMethod = 0,
                InterlaceMethod   = 0
            };

            this.WriteHeaderChunk(stream, header);

            // Collect the pixel data
            if (this.PngColorType == PngColorType.Palette)
            {
                this.CollectIndexedBytes(image, stream, header);
            }
            else if (this.PngColorType == PngColorType.Grayscale || this.PngColorType == PngColorType.GrayscaleWithAlpha)
            {
                this.CollectGrayscaleBytes(image);
            }
            else
            {
                this.CollectColorBytes(image);
            }

            this.WritePhysicalChunk(stream, image);
            this.WriteGammaChunk(stream);
            this.WriteDataChunks(stream);
            this.WriteEndChunk(stream);
            stream.Flush();
        }