/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { IFrameQuantizer <TPixel> executor = this.Quantizer.CreateFrameQuantizer <TPixel>(); 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); int yy = y * source.Width; for (int x = 0; x < source.Width; x++) { int i = x + yy; TPixel color = quantized.Palette[Math.Min(paletteCount, quantized.Pixels[i])]; row[x] = color; } } }
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; } }
/// <inheritdoc/> public virtual QuantizedFrame <TPixel> QuantizeFrame(ImageFrame <TPixel> image) { Guard.NotNull(image, nameof(image)); // Get the size of the source image int height = image.Height; int width = image.Width; // Call the FirstPass function if not a single pass algorithm. // For something like an Octree quantizer, this will run through // all image pixels, build a data structure, and create a palette. if (!this.singlePass) { this.FirstPass(image, width, height); } // Collect the palette. Required before the second pass runs. TPixel[] palette = this.GetPalette(); this.paletteVector = new Vector4[palette.Length]; PixelOperations <TPixel> .Instance.ToScaledVector4(palette, this.paletteVector, palette.Length); var quantizedFrame = new QuantizedFrame <TPixel>(image.MemoryAllocator, width, height, palette); if (this.Dither) { // We clone the image as we don't want to alter the original via dithering. using (ImageFrame <TPixel> clone = image.Clone()) { this.SecondPass(clone, quantizedFrame.GetPixelSpan(), palette, width, height); } } else { this.SecondPass(image, quantizedFrame.GetPixelSpan(), palette, width, height); } return(quantizedFrame); }
/// <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.width = image.Width; this.height = image.Height; stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); QuantizedFrame <TPixel> quantized = null; 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); this.palettePixelData = quantized.Pixels; 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 = 8; } this.bytesPerPixel = this.CalculateBytesPerPixel(); var header = new PngHeader( width: image.Width, height: image.Height, colorType: this.pngColorType, bitDepth: this.bitDepth, filterMethod: 0, // None compressionMethod: 0, interlaceMethod: 0); 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, stream); this.WriteEndChunk(stream); stream.Flush(); }
/// <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 = 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.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(); }
/// <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; // Write the png header. this.chunkDataBuffer[0] = 0x89; // Set the high bit. this.chunkDataBuffer[1] = 0x50; // P this.chunkDataBuffer[2] = 0x4E; // N this.chunkDataBuffer[3] = 0x47; // G this.chunkDataBuffer[4] = 0x0D; // Line ending CRLF this.chunkDataBuffer[5] = 0x0A; // Line ending CRLF this.chunkDataBuffer[6] = 0x1A; // EOF this.chunkDataBuffer[7] = 0x0A; // LF stream.Write(this.chunkDataBuffer, 0, 8); QuantizedFrame <TPixel> quantized = null; 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); this.palettePixelData = quantized.Pixels; 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 = 8; } this.bytesPerPixel = this.CalculateBytesPerPixel(); var header = new PngHeader { Width = image.Width, Height = image.Height, ColorType = this.pngColorType, BitDepth = this.bitDepth, FilterMethod = 0, // None CompressionMethod = 0, InterlaceMethod = 0 }; 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, stream); this.WriteEndChunk(stream); stream.Flush(); }
/// <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(); }
/// <summary> /// Writes the palette chunk to the stream. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="header">The <see cref="PngHeader"/>.</param> /// <param name="image">The image to encode.</param> /// <returns>The <see cref="QuantizedFrame{TPixel}"/></returns> private QuantizedFrame <TPixel> WritePaletteChunk <TPixel>(Stream stream, PngHeader header, ImageFrame <TPixel> image) where TPixel : struct, IPixel <TPixel> { if (this.paletteSize > 256) { return(null); } if (this.quantizer == null) { this.quantizer = new WuQuantizer <TPixel>(); } // Quantize the image returning a palette. This boxing is icky. QuantizedFrame <TPixel> quantized = ((IQuantizer <TPixel>) this.quantizer).Quantize(image, this.paletteSize); // Grab the palette and write it to the stream. TPixel[] palette = quantized.Palette; byte pixelCount = palette.Length.ToByte(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; var rgba = default(Rgba32); bool anyAlpha = false; using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) using (IManagedByteBuffer alphaTable = this.memoryManager.AllocateManagedByteBuffer(pixelCount)) { Span <byte> colorTableSpan = colorTable.Span; Span <byte> alphaTableSpan = alphaTable.Span; for (byte i = 0; i < pixelCount; i++) { if (quantized.Pixels.Contains(i)) { int offset = i * 3; palette[i].ToRgba32(ref rgba); byte alpha = rgba.A; colorTableSpan[offset] = rgba.R; colorTableSpan[offset + 1] = rgba.G; colorTableSpan[offset + 2] = rgba.B; if (alpha > this.threshold) { alpha = 255; } anyAlpha = anyAlpha || alpha < 255; alphaTableSpan[i] = alpha; } } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable.Array, 0, colorTableLength); // Write the transparency data if (anyAlpha) { this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable.Array, 0, pixelCount); } } return(quantized); }
/// <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.quantizer = this.quantizer ?? new OctreeQuantizer <TPixel>(); // Do not use IDisposable pattern here as we want to preserve the stream. var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that pallete size can be set but has a fallback. int size = this.paletteSize; size = size > 0 ? size.Clamp(1, 256) : 256; // Get the number of bits. this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(size); this.hasFrames = image.Frames.Count > 1; var pixelQuantizer = (IQuantizer <TPixel>) this.quantizer; // Quantize the image returning a palette. QuantizedFrame <TPixel> quantized = pixelQuantizer.Quantize(image.Frames.RootFrame, size); int index = this.GetTransparentIndex(quantized); // Write the header. this.WriteHeader(writer); // Write the LSD. We'll use local color tables for now. this.WriteLogicalScreenDescriptor(image, writer, index); // Write the first frame. this.WriteComments(image, writer); // Write additional frames. if (this.hasFrames) { this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count); } foreach (ImageFrame <TPixel> frame in image.Frames) { if (quantized == null) { quantized = pixelQuantizer.Quantize(frame, size); } this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantized)); this.WriteImageDescriptor(frame, writer); this.WriteColorTable(quantized, writer); this.WriteImageData(quantized, writer); quantized = null; // so next frame can regenerate it } // TODO: Write extension etc writer.Write(GifConstants.EndIntroducer); }