Example #1
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="ImageFrame{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.configuration = image.GetConfiguration();
            this.width         = image.Width;
            this.height        = image.Height;

            // Always take the encoder options over the metadata values.
            ImageMetaData metaData    = image.MetaData;
            PngMetaData   pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);

            this.gamma        = this.gamma ?? pngMetaData.Gamma;
            this.writeGamma   = this.gamma > 0;
            this.pngColorType = this.pngColorType ?? pngMetaData.ColorType;
            this.pngBitDepth  = this.pngBitDepth ?? pngMetaData.BitDepth;
            this.use16Bit     = this.pngBitDepth.Equals(PngBitDepth.Bit16);

            // Ensure we are not allowing impossible combinations.
            if (!ColorTypes.ContainsKey(this.pngColorType.Value))
            {
                throw new NotSupportedException("Color type is not supported or not valid.");
            }

            stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);

            QuantizedFrame <TPixel> quantized = null;

            if (this.pngColorType == PngColorType.Palette)
            {
                byte bits = (byte)this.pngBitDepth;
                if (!ColorTypes[this.pngColorType.Value].Contains(bits))
                {
                    throw new NotSupportedException("Bit depth is not supported or not valid.");
                }

                // Use the metadata to determine what quantization depth to use if no quantizer has been set.
                if (this.quantizer == null)
                {
                    this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits));
                }

                // Create quantized frame returning the palette and set the bit depth.
                quantized = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration())
                            .QuantizeFrame(image.Frames.RootFrame);
                byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
                bits = Math.Max(bits, quantizedBits);

                // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
                // We check again for the bit depth as the bit depth of the color palette from a given quantizer might not
                // be within the acceptable range.
                if (bits == 3)
                {
                    bits = 4;
                }
                else if (bits >= 5 && bits <= 7)
                {
                    bits = 8;
                }

                this.bitDepth = bits;
            }
            else
            {
                this.bitDepth = (byte)this.pngBitDepth;
                if (!ColorTypes[this.pngColorType.Value].Contains(this.bitDepth))
                {
                    throw new NotSupportedException("Bit depth is not supported or not valid.");
                }
            }

            this.bytesPerPixel = this.CalculateBytesPerPixel();

            var header = new PngHeader(
                width: image.Width,
                height: image.Height,
                bitDepth: this.bitDepth,
                colorType: this.pngColorType.Value,
                compressionMethod: 0, // None
                filterMethod: 0,
                interlaceMethod: 0);  // TODO: Can't write interlaced yet.

            this.WriteHeaderChunk(stream, header);

            // Collect the indexed pixel data
            if (quantized != null)
            {
                this.WritePaletteChunk(stream, quantized);
            }

            this.WritePhysicalChunk(stream, metaData);
            this.WriteGammaChunk(stream);
            this.WriteExifChunk(stream, metaData);
            this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
            this.WriteEndChunk(stream);
            stream.Flush();

            quantized?.Dispose();
        }
Example #2
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, ImageFrame <TPixel> image)
            where TPixel : struct, IPixel <TPixel>
        {
            if (this.paletteSize > 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.paletteSize);

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

            var  rgba     = default(Rgba32);
            bool anyAlpha = false;

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

                        byte alpha = rgba.A;

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

                        if (alpha > this.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);
            }

            return(quantized);
        }
Example #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>(ImageFrame <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;
        }
Example #4
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="ImageFrame{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);

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

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

            // Set correct bit depth.
            this.bitDepth = this.paletteSize <= 256
                               ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.paletteSize).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();

            var 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.Frames.RootFrame, stream, header);
            }

            this.WritePhysicalChunk(stream, image);
            this.WriteGammaChunk(stream);
            this.WriteDataChunks(image.Frames.RootFrame, stream);
            this.WriteEndChunk(stream);
            stream.Flush();
        }
Example #5
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="ImageFrame{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;

            stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);

            QuantizedFrame <TPixel> quantized = null;

            if (this.pngColorType == PngColorType.Palette)
            {
                // Create quantized frame returning the palette and set the bit depth.
                quantized             = this.quantizer.CreateFrameQuantizer <TPixel>().QuantizeFrame(image.Frames.RootFrame);
                this.palettePixelData = quantized.Pixels;
                byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);

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

                this.bitDepth = bits;
            }
            else
            {
                this.bitDepth = 8;
            }

            this.bytesPerPixel = this.CalculateBytesPerPixel();

            var 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 (quantized != null)
            {
                this.WritePaletteChunk(stream, header, quantized);
            }

            this.WritePhysicalChunk(stream, image);
            this.WriteGammaChunk(stream);
            this.WriteDataChunks(image.Frames.RootFrame, stream);
            this.WriteEndChunk(stream);
            stream.Flush();
        }
Example #6
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="ImageFrame{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;

            // Always take the encoder options over the metadata values.
            ImageMetaData metaData    = image.MetaData;
            PngMetaData   pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);

            this.gamma        = this.gamma ?? pngMetaData.Gamma;
            this.writeGamma   = this.gamma > 0;
            this.pngColorType = this.pngColorType ?? pngMetaData.ColorType;
            this.pngBitDepth  = this.pngBitDepth ?? pngMetaData.BitDepth;
            this.use16Bit     = this.pngBitDepth.Equals(PngBitDepth.Bit16);

            stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);

            QuantizedFrame <TPixel> quantized           = null;
            ReadOnlySpan <byte>     quantizedPixelsSpan = default;

            if (this.pngColorType == PngColorType.Palette)
            {
                byte bits;

                // Use the metadata to determine what quantization depth to use if no quantizer has been set.
                if (this.quantizer == null)
                {
                    bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
                    int colorSize = ImageMaths.GetColorCountForBitDepth(bits);
                    this.quantizer = new WuQuantizer(colorSize);
                }

                // Create quantized frame returning the palette and set the bit depth.
                quantized           = this.quantizer.CreateFrameQuantizer <TPixel>().QuantizeFrame(image.Frames.RootFrame);
                quantizedPixelsSpan = quantized.GetPixelSpan();
                bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);

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

                this.bitDepth = bits;
            }
            else
            {
                this.bitDepth = (byte)(this.use16Bit ? 16 : 8);
            }

            this.bytesPerPixel = this.CalculateBytesPerPixel();

            var header = new PngHeader(
                width: image.Width,
                height: image.Height,
                bitDepth: this.bitDepth,
                colorType: this.pngColorType.Value,
                compressionMethod: 0, // None
                filterMethod: 0,
                interlaceMethod: 0);  // TODO: Can't write interlaced yet.

            this.WriteHeaderChunk(stream, header);

            // Collect the indexed pixel data
            if (quantized != null)
            {
                this.WritePaletteChunk(stream, quantized);
            }

            this.WritePhysicalChunk(stream, metaData);
            this.WriteGammaChunk(stream);
            this.WriteExifChunk(stream, metaData);
            this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
            this.WriteEndChunk(stream);
            stream.Flush();

            quantized?.Dispose();
        }