Пример #1
0
        public void QuantizersDitherByDefault()
        {
            var werner  = new WernerPaletteQuantizer();
            var webSafe = new WebSafePaletteQuantizer();
            var octree  = new OctreeQuantizer();
            var wu      = new WuQuantizer();

            Assert.NotNull(werner.Options.Dither);
            Assert.NotNull(webSafe.Options.Dither);
            Assert.NotNull(octree.Options.Dither);
            Assert.NotNull(wu.Options.Dither);

            using (IFrameQuantizer <Rgba32> quantizer = werner.CreateFrameQuantizer <Rgba32>(this.Configuration))
            {
                Assert.NotNull(quantizer.Options.Dither);
            }

            using (IFrameQuantizer <Rgba32> quantizer = webSafe.CreateFrameQuantizer <Rgba32>(this.Configuration))
            {
                Assert.NotNull(quantizer.Options.Dither);
            }

            using (IFrameQuantizer <Rgba32> quantizer = octree.CreateFrameQuantizer <Rgba32>(this.Configuration))
            {
                Assert.NotNull(quantizer.Options.Dither);
            }

            using (IFrameQuantizer <Rgba32> quantizer = wu.CreateFrameQuantizer <Rgba32>(this.Configuration))
            {
                Assert.NotNull(quantizer.Options.Dither);
            }
        }
Пример #2
0
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration)
        {
            IFrameQuantizer <TPixel> executor = this.Quantizer.CreateFrameQuantizer <TPixel>();

            using (QuantizedFrame <TPixel> quantized = executor.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();
                    int yy = y * source.Width;

                    for (int x = 0; x < source.Width; x++)
                    {
                        int i = x + yy;
                        row[x] = quantized.Palette[Math.Min(paletteCount, quantizedPixelSpan[i])];
                    }
                }
            }
        }
Пример #3
0
        public void OctreeQuantizerCanCreateFrameQuantizer()
        {
            var quantizer = new OctreeQuantizer();
            IFrameQuantizer <Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(Configuration.Default);

            Assert.NotNull(frameQuantizer);
            Assert.NotNull(frameQuantizer.Options);
            Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither);
            frameQuantizer.Dispose();

            quantizer = new OctreeQuantizer(new QuantizerOptions {
                Dither = null
            });
            frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(Configuration.Default);

            Assert.NotNull(frameQuantizer);
            Assert.Null(frameQuantizer.Options.Dither);
            frameQuantizer.Dispose();

            quantizer = new OctreeQuantizer(new QuantizerOptions {
                Dither = KnownDitherings.Atkinson
            });
            frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(Configuration.Default);
            Assert.NotNull(frameQuantizer);
            Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither);
            frameQuantizer.Dispose();
        }
Пример #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="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 : unmanaged, IPixel <TPixel>
        {
            Guard.NotNull(image, nameof(image));
            Guard.NotNull(stream, nameof(stream));

            ImageMetadata metadata    = image.Metadata;
            GifMetadata   gifMetadata = metadata.GetGifMetadata();

            this.colorTableMode ??= gifMetadata.ColorTableMode;
            bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global;

            // Quantize the image returning a palette.
            IndexedImageFrame <TPixel> quantized;

            using (IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration))
            {
                quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds());
            }

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

            // 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(gifMetadata, stream);

            // Write application extension to allow additional frames.
            if (image.Frames.Count > 1)
            {
                this.WriteApplicationExtension(stream, 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);
        }
Пример #5
0
        private void EncodeGlobal <TPixel>(Image <TPixel> image, QuantizedFrame <TPixel> quantized, int transparencyIndex, Stream stream)
            where TPixel : struct, IPixel <TPixel>
        {
            var palleteQuantizer = new PaletteQuantizer <TPixel>(quantized.Palette, this.quantizer.Diffuser);

            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> palleteFrameQuantizer = palleteQuantizer.CreateFrameQuantizer(image.GetConfiguration()))
                        using (QuantizedFrame <TPixel> paletteQuantized = palleteFrameQuantizer.QuantizeFrame(frame))
                        {
                            this.WriteImageData(paletteQuantized, stream);
                        }
                }
            }
        }
        /// <summary>
        /// Creates the quantized frame.
        /// </summary>
        /// <typeparam name="TPixel">The type of the pixel.</typeparam>
        /// <param name="options">The options.</param>
        /// <param name="image">The image.</param>
        public static IndexedImageFrame <TPixel> CreateQuantizedFrame <TPixel>(
            PngEncoderOptions options,
            Image <TPixel> image)
            where TPixel : unmanaged, IPixel <TPixel>
        {
            if (options.ColorType != PngColorType.Palette)
            {
                return(null);
            }

            byte bits = (byte)options.BitDepth;

            if (Array.IndexOf(PngConstants.ColorTypes[options.ColorType.Value], bits) == -1)
            {
                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 (options.Quantizer is null)
            {
                var maxColors = ImageMaths.GetColorCountForBitDepth(bits);
                options.Quantizer = new WuQuantizer(new QuantizerOptions {
                    MaxColors = maxColors
                });
            }

            // Create quantized frame returning the palette and set the bit depth.
            using (IFrameQuantizer <TPixel> frameQuantizer = options.Quantizer.CreateFrameQuantizer <TPixel>(image.GetConfiguration()))
            {
                ImageFrame <TPixel> frame = image.Frames.RootFrame;
                return(frameQuantizer.QuantizeFrame(frame, frame.Bounds()));
            }
        }
        /// <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
        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;
            }
        }
Пример #9
0
        /// <inheritdoc />
        protected override void OnFrameApply(ImageFrame <TPixel> source)
        {
            var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle);

            Configuration configuration = this.Configuration;

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

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

            ParallelRowIterator.IterateRowIntervals(
                configuration,
                interest,
                in operation);
        }
Пример #10
0
        public void SinglePixelTransparent()
        {
            Configuration config    = Configuration.Default;
            var           quantizer = new WuQuantizer(new QuantizerOptions {
                Dither = null
            });

            using var image = new Image <Rgba32>(config, 1, 1, default(Rgba32));
            ImageFrame <Rgba32> frame = image.Frames.RootFrame;

            using IFrameQuantizer <Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(config);
            using QuantizedFrame <Rgba32> result          = frameQuantizer.QuantizeFrame(frame, frame.Bounds());

            Assert.Equal(1, result.Palette.Length);
            Assert.Equal(1, result.GetPixelSpan().Length);

            Assert.Equal(default, result.Palette.Span[0]);
Пример #11
0
        public void SinglePixelOpaque()
        {
            Configuration config    = Configuration.Default;
            var           quantizer = new WuQuantizer(new QuantizerOptions {
                Dither = null
            });

            using var image = new Image <Rgba32>(config, 1, 1, Color.Black);
            ImageFrame <Rgba32> frame = image.Frames.RootFrame;

            using IFrameQuantizer <Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(config);
            using IndexedImageFrame <Rgba32> result       = frameQuantizer.QuantizeFrame(frame, frame.Bounds());

            Assert.Equal(1, result.Palette.Length);
            Assert.Equal(1, result.GetPixelBufferSpan().Length);

            Assert.Equal(Color.Black, (Color)result.Palette.Span[0]);
            Assert.Equal(0, result.GetPixelBufferSpan()[0]);
        }
Пример #12
0
        public void OctreeQuantizerCanCreateFrameQuantizer()
        {
            var quantizer = new OctreeQuantizer();
            IFrameQuantizer <Rgba32> frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(Configuration.Default);

            Assert.NotNull(frameQuantizer);
            Assert.True(frameQuantizer.Dither);
            Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser);

            quantizer      = new OctreeQuantizer(false);
            frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(Configuration.Default);

            Assert.NotNull(frameQuantizer);
            Assert.False(frameQuantizer.Dither);
            Assert.Null(frameQuantizer.Diffuser);

            quantizer      = new OctreeQuantizer(KnownDiffusers.Atkinson);
            frameQuantizer = quantizer.CreateFrameQuantizer <Rgba32>(Configuration.Default);
            Assert.NotNull(frameQuantizer);
            Assert.True(frameQuantizer.Dither);
            Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser);
        }
Пример #13
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 IFrameQuantizer <TPixel> quantizer = this.quantizer.CreateFrameQuantizer <TPixel>(this.configuration);
            using QuantizedFrame <TPixel> quantized  = quantizer.QuantizeFrame(image, image.Bounds());

            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);
                }
            }
        }
Пример #14
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 == 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 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();
        }