예제 #1
0
        private void EncodeGlobal <TPixel>(Image <TPixel> image, IQuantizedFrame <TPixel> quantized, int transparencyIndex, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            for (int i = 0; i < image.Frames.Count; i++)
            {
                ImageFrame <TPixel> frame         = image.Frames[i];
                ImageFrameMetadata  metadata      = frame.Metadata;
                GifFrameMetadata    frameMetadata = metadata.GetFormatMetadata(GifFormat.Instance);
                this.WriteGraphicalControlExtension(frameMetadata, transparencyIndex, stream);
                this.WriteImageDescriptor(frame, false, stream);

                if (i == 0)
                {
                    this.WriteImageData(quantized, stream);
                }
                else
                {
                    using (IFrameQuantizer <TPixel> paletteFrameQuantizer =
                               new PaletteFrameQuantizer <TPixel>(this.quantizer.Diffuser, quantized.Palette))
                    {
                        using (IQuantizedFrame <TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame))
                        {
                            this.WriteImageData(paletteQuantized, stream);
                        }
                    }
                }
            }
        }
예제 #2
0
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source)
        {
            Configuration configuration = this.Configuration;

            using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(configuration))
                using (IQuantizedFrame <TPixel> quantized = frameQuantizer.QuantizeFrame(source))
                {
                    int paletteCount = quantized.Palette.Length - 1;

                    // Not parallel to remove "quantized" closure allocation.
                    // We can operate directly on the source here as we've already read it to get the
                    // quantized result
                    for (int y = 0; y < source.Height; y++)
                    {
                        Span <TPixel>       row = source.GetPixelRowSpan(y);
                        ReadOnlySpan <byte> quantizedPixelSpan = quantized.GetPixelSpan();

                        ReadOnlySpan <TPixel> paletteSpan = quantized.Palette.Span;

                        int yy = y * source.Width;

                        for (int x = 0; x < source.Width; x++)
                        {
                            int i = x + yy;
                            row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])];
                        }
                    }
                }
        }
예제 #3
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);
            IQuantizedFrame <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();
        }
 public RowIntervalOperation(
     Rectangle bounds,
     ImageFrame <TPixel> source,
     IQuantizedFrame <TPixel> quantized)
 {
     this.bounds          = bounds;
     this.source          = source;
     this.quantized       = quantized;
     this.maxPaletteIndex = quantized.Palette.Length - 1;
 }
예제 #5
0
        public void SinglePixelTransparent()
        {
            Configuration config    = Configuration.Default;
            var           quantizer = new WuQuantizer(false);

            using (var image = new Image <Rgba32>(config, 1, 1, default(Rgba32)))
                using (IQuantizedFrame <Rgba32> result = quantizer.CreateFrameQuantizer <Rgba32>(config).QuantizeFrame(image.Frames[0]))
                {
                    Assert.Equal(1, result.Palette.Length);
                    Assert.Equal(1, result.GetPixelSpan().Length);

                    Assert.Equal(default, result.Palette.Span[0]);
예제 #6
0
        private int GetTransparentIndex <TPixel>(IQuantizedFrame <TPixel> quantized)
            where TPixel : struct, IPixel <TPixel>
        {
            // Transparent pixels are much more likely to be found at the end of a palette
            int    index = -1;
            Rgba32 trans = default;
            ReadOnlySpan <TPixel> paletteSpan = quantized.Palette.Span;

            for (int i = paletteSpan.Length - 1; i >= 0; i--)
            {
                paletteSpan[i].ToRgba32(ref trans);

                if (trans.Equals(default))
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source)
        {
            Configuration configuration = this.Configuration;

            using IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(configuration);
            using IQuantizedFrame <TPixel> quantized      = frameQuantizer.QuantizeFrame(source);

            var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized);

            ParallelRowIterator.IterateRows(
                configuration,
                this.SourceRectangle,
                in operation);
        }
예제 #8
0
        public void SinglePixelOpaque()
        {
            Configuration config    = Configuration.Default;
            var           quantizer = new WuQuantizer(false);

            using (var image = new Image <Rgba32>(config, 1, 1, Color.Black))
                using (IQuantizedFrame <Rgba32> result = quantizer.CreateFrameQuantizer <Rgba32>(config).QuantizeFrame(image.Frames[0]))
                {
                    Assert.Equal(1, result.Palette.Length);
                    Assert.Equal(1, result.GetPixelSpan().Length);

                    Assert.Equal(Color.Black, (Color)result.Palette.Span[0]);
                    Assert.Equal(0, result.GetPixelSpan()[0]);
                }
        }
예제 #9
0
        /// <summary>
        /// Returns the index of the most transparent color in the palette.
        /// </summary>
        /// <param name="quantized">
        /// The quantized.
        /// </param>
        /// <typeparam name="TPixel">The pixel format.</typeparam>
        /// <returns>
        /// The <see cref="int"/>.
        /// </returns>
        private int GetTransparentIndex <TPixel>(IQuantizedFrame <TPixel> quantized)
            where TPixel : struct, IPixel <TPixel>
        {
            // Transparent pixels are much more likely to be found at the end of a palette
            int index  = -1;
            int length = quantized.Palette.Length;

            using (IMemoryOwner <Rgba32> rgbaBuffer = this.memoryAllocator.Allocate <Rgba32>(length))
            {
                Span <Rgba32> rgbaSpan   = rgbaBuffer.GetSpan();
                ref Rgba32    paletteRef = ref MemoryMarshal.GetReference(rgbaSpan);
                PixelOperations <TPixel> .Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan);

                for (int i = quantized.Palette.Length - 1; i >= 0; i--)
                {
                    if (Unsafe.Add(ref paletteRef, i).Equals(default))
예제 #10
0
        public void WuQuantizerYieldsCorrectTransparentPixel <TPixel>(TestImageProvider <TPixel> provider, bool dither)
            where TPixel : struct, IPixel <TPixel>
        {
            using (Image <TPixel> image = provider.GetImage())
            {
                Assert.True(image[0, 0].Equals(default(TPixel)));

                var quantizer = new WuQuantizer(dither);

                foreach (ImageFrame <TPixel> frame in image.Frames)
                {
                    IQuantizedFrame <TPixel> quantized =
                        quantizer.CreateFrameQuantizer <TPixel>(this.Configuration).QuantizeFrame(frame);

                    int index = this.GetTransparentIndex(quantized);
                    Assert.Equal(index, quantized.GetPixelSpan()[0]);
                }
            }
        }
예제 #11
0
        private void EncodeLocal <TPixel>(Image <TPixel> image, IQuantizedFrame <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)
                    {
                        using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration(), frameMetadata.ColorTableLength))
                        {
                            quantized = frameQuantizer.QuantizeFrame(frame);
                        }
                    }
                    else
                    {
                        using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration()))
                        {
                            quantized = frameQuantizer.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;
            }
        }
        /// <summary>
        /// Calculates the bit depth value.
        /// </summary>
        /// <typeparam name="TPixel">The type of the pixel.</typeparam>
        /// <param name="options">The options.</param>
        /// <param name="image">The image.</param>
        /// <param name="quantizedFrame">The quantized frame.</param>
        public static byte CalculateBitDepth <TPixel>(
            PngEncoderOptions options,
            Image <TPixel> image,
            IQuantizedFrame <TPixel> quantizedFrame)
            where TPixel : struct, IPixel <TPixel>
        {
            byte bitDepth;

            if (options.ColorType == PngColorType.Palette)
            {
                byte quantizedBits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantizedFrame.Palette.Length).Clamp(1, 8);
                byte bits          = Math.Max((byte)options.BitDepth, 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;
                }

                bitDepth = bits;
            }
            else
            {
                bitDepth = (byte)options.BitDepth;
            }

            if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bitDepth) == -1)
            {
                throw new NotSupportedException("Bit depth is not supported or not valid.");
            }

            return(bitDepth);
        }
예제 #13
0
        /// <summary>
        /// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
        /// </summary>
        /// <typeparam name="TPixel">The type of the pixel.</typeparam>
        /// <param name="stream">The <see cref="Stream"/> to write to.</param>
        /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
        private void Write8Bit <TPixel>(Stream stream, ImageFrame <TPixel> image)
            where TPixel : struct, IPixel <TPixel>
        {
            using (IMemoryOwner <byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean))
                using (IQuantizedFrame <TPixel> quantized = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration, 256).QuantizeFrame(image))
                {
                    Span <byte>           colorPalette = colorPaletteBuffer.GetSpan();
                    int                   idx          = 0;
                    var                   color        = default(Rgba32);
                    ReadOnlySpan <TPixel> paletteSpan  = quantized.Palette.Span;

                    // TODO: Use bulk conversion here for better perf
                    foreach (TPixel quantizedColor in paletteSpan)
                    {
                        quantizedColor.ToRgba32(ref color);
                        colorPalette[idx]     = color.B;
                        colorPalette[idx + 1] = color.G;
                        colorPalette[idx + 2] = color.R;

                        // Padding byte, always 0
                        colorPalette[idx + 3] = 0;
                        idx += 4;
                    }

                    stream.Write(colorPalette);

                    for (int y = image.Height - 1; y >= 0; y--)
                    {
                        ReadOnlySpan <byte> pixelSpan = quantized.GetRowSpan(y);
                        stream.Write(pixelSpan);

                        for (int i = 0; i < this.padding; i++)
                        {
                            stream.WriteByte(0);
                        }
                    }
                }
        }
예제 #14
0
        /// <summary>
        /// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
        /// </summary>
        /// <typeparam name="TPixel">The type of the pixel.</typeparam>
        /// <param name="stream">The <see cref="Stream"/> to write to.</param>
        /// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
        /// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
        private void Write8BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image, Span <byte> colorPalette)
            where TPixel : struct, IPixel <TPixel>
        {
            using (IQuantizedFrame <TPixel> quantized = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration, 256).QuantizeFrame(image))
            {
                ReadOnlySpan <TPixel> quantizedColors = quantized.Palette.Span;
                var color = default(Rgba32);

                // TODO: Use bulk conversion here for better perf
                int idx = 0;
                foreach (TPixel quantizedColor in quantizedColors)
                {
                    quantizedColor.ToRgba32(ref color);
                    colorPalette[idx]     = color.B;
                    colorPalette[idx + 1] = color.G;
                    colorPalette[idx + 2] = color.R;

                    // Padding byte, always 0.
                    colorPalette[idx + 3] = 0;
                    idx += 4;
                }

                stream.Write(colorPalette);

                for (int y = image.Height - 1; y >= 0; y--)
                {
                    ReadOnlySpan <byte> pixelSpan = quantized.GetRowSpan(y);
                    stream.Write(pixelSpan);

                    for (int i = 0; i < this.padding; i++)
                    {
                        stream.WriteByte(0);
                    }
                }
            }
        }
 public static ReadOnlySpan <byte> GetRowSpan <TPixel>(this IQuantizedFrame <TPixel> frame, int rowIndex)
     where TPixel : struct, IPixel <TPixel>
 => frame.GetPixelSpan().Slice(rowIndex * frame.Width, frame.Width);
예제 #16
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.
            IQuantizedFrame <TPixel> quantized = null;

            using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration()))
            {
                quantized = frameQuantizer.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();

            // TODO: Write extension etc
            stream.WriteByte(GifConstants.EndIntroducer);
        }
        /// <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 == 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);

            IQuantizedFrame <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 is null)
                {
                    this.quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits));
                }

                // Create quantized frame returning the palette and set the bit depth.
                using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration()))
                {
                    quantized = frameQuantizer.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);
            }

            if (pngMetadata.HasTrans)
            {
                this.WriteTransparencyChunk(stream, pngMetadata);
            }

            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();
        }