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