Ejemplo n.º 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.width  = image.Width;
            this.height = image.Height;

            ImageMetadata metadata    = image.Metadata;
            PngMetadata   pngMetadata = metadata.GetPngMetadata();

            PngEncoderOptionsHelpers.AdjustOptions <TPixel>(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel);
            QuantizedFrame <TPixel> quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image);

            this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized);

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

            this.WriteHeaderChunk(stream);
            this.WritePaletteChunk(stream, quantized);
            this.WriteTransparencyChunk(stream, pngMetadata);
            this.WritePhysicalChunk(stream, metadata);
            this.WriteGammaChunk(stream);
            this.WriteExifChunk(stream, metadata);
            this.WriteTextChunks(stream, pngMetadata);
            this.WriteDataChunks(image.Frames.RootFrame, quantized, stream);
            this.WriteEndChunk(stream);
            stream.Flush();

            quantized?.Dispose();
        }
Ejemplo n.º 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="Image{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();

            ImageMetadata metadata = image.Metadata;

            this.gifMetadata    = metadata.GetFormatMetadata(GifFormat.Instance);
            this.colorTableMode = this.colorTableMode ?? this.gifMetadata.ColorTableMode;
            bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;

            // Quantize the image returning a palette.
            QuantizedFrame <TPixel> quantized =
                this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration()).QuantizeFrame(image.Frames.RootFrame);

            // Get the number of bits.
            this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);

            // Write the header.
            this.WriteHeader(stream);

            // Write the LSD.
            int index = this.GetTransparentIndex(quantized);

            this.WriteLogicalScreenDescriptor(metadata, image.Width, image.Height, index, useGlobalTable, stream);

            if (useGlobalTable)
            {
                this.WriteColorTable(quantized, stream);
            }

            // Write the comments.
            this.WriteComments(metadata, stream);

            // Write application extension to allow additional frames.
            if (image.Frames.Count > 1)
            {
                this.WriteApplicationExtension(stream, this.gifMetadata.RepeatCount);
            }

            if (useGlobalTable)
            {
                this.EncodeGlobal(image, quantized, index, stream);
            }
            else
            {
                this.EncodeLocal(image, quantized, stream);
            }

            // Clean up.
            quantized?.Dispose();
            quantized = null;

            // TODO: Write extension etc
            stream.WriteByte(GifConstants.EndIntroducer);
        }
Ejemplo n.º 3
0
        private void EncodeLocal <TPixel>(Image <TPixel> image, QuantizedFrame <TPixel> quantized, Stream stream)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            ImageFrame <TPixel> previousFrame = null;
            GifFrameMetadata    previousMeta  = null;

            foreach (ImageFrame <TPixel> frame in image.Frames)
            {
                ImageFrameMetadata metadata      = frame.Metadata;
                GifFrameMetadata   frameMetadata = metadata.GetGifMetadata();
                if (quantized is null)
                {
                    // Allow each frame to be encoded at whatever color depth the frame designates if set.
                    if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength &&
                        frameMetadata.ColorTableLength > 0)
                    {
                        var options = new QuantizerOptions
                        {
                            Dither      = this.quantizer.Options.Dither,
                            DitherScale = this.quantizer.Options.DitherScale,
                            MaxColors   = frameMetadata.ColorTableLength
                        };

                        using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration, options))
                        {
                            quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
                        }
                    }
                    else
                    {
                        using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration))
                        {
                            quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds());
                        }
                    }
                }

                this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
                this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream);
                this.WriteImageDescriptor(frame, true, stream);
                this.WriteColorTable(quantized, stream);
                this.WriteImageData(quantized, stream);

                quantized?.Dispose();
                quantized     = null; // So next frame can regenerate it
                previousFrame = frame;
                previousMeta  = frameMetadata;
            }
        }
Ejemplo n.º 4
0
        private void EncodeLocal <TPixel>(Image <TPixel> image, QuantizedFrame <TPixel> quantized, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            foreach (ImageFrame <TPixel> frame in image.Frames)
            {
                if (quantized == null)
                {
                    quantized = this.quantizer.CreateFrameQuantizer <TPixel>().QuantizeFrame(frame);
                }

                this.WriteGraphicalControlExtension(frame.MetaData, this.GetTransparentIndex(quantized), stream);
                this.WriteImageDescriptor(frame, true, stream);
                this.WriteColorTable(quantized, stream);
                this.WriteImageData(quantized, stream);

                quantized?.Dispose();
                quantized = null; // So next frame can regenerate it
            }
        }
Ejemplo n.º 5
0
        private void EncodeLocal <TPixel>(Image <TPixel> image, QuantizedFrame <TPixel> quantized, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            ImageFrame <TPixel> previousFrame = null;
            GifFrameMetadata    previousMeta  = null;

            foreach (ImageFrame <TPixel> frame in image.Frames)
            {
                ImageFrameMetadata metadata      = frame.Metadata;
                GifFrameMetadata   frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance);
                if (quantized is null)
                {
                    // Allow each frame to be encoded at whatever color depth the frame designates if set.
                    if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength &&
                        frameMetadata.ColorTableLength > 0)
                    {
                        quantized = this.quantizer.CreateFrameQuantizer <TPixel>(
                            image.GetConfiguration(),
                            frameMetadata.ColorTableLength).QuantizeFrame(frame);
                    }
                    else
                    {
                        quantized = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration())
                                    .QuantizeFrame(frame);
                    }
                }

                this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
                this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream);
                this.WriteImageDescriptor(frame, true, stream);
                this.WriteColorTable(quantized, stream);
                this.WriteImageData(quantized, stream);

                quantized?.Dispose();
                quantized     = null; // So next frame can regenerate it
                previousFrame = frame;
                previousMeta  = frameMetadata;
            }
        }
Ejemplo n.º 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;

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

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

            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);
                quantizedPixelsSpan = quantized.GetPixelSpan();
                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 = (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,
                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, header, quantized);
            }

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

            quantized?.Dispose();
        }
Ejemplo n.º 7
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, header, 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();
        }
Ejemplo n.º 8
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();
        }