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