/// <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); } // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { byte bits = (byte)options.BitDepth; 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 (IQuantizer <TPixel> frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer <TPixel>(image.GetConfiguration())) { ImageFrame <TPixel> frame = image.Frames.RootFrame; return(frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())); } }
public TiffPaletteWriter( ImageFrame <TPixel> image, IQuantizer quantizer, MemoryAllocator memoryAllocator, Configuration configuration, TiffEncoderEntriesCollector entriesCollector, int bitsPerPixel) : base(image, memoryAllocator, configuration, entriesCollector) { DebugGuard.NotNull(quantizer, nameof(quantizer)); DebugGuard.NotNull(configuration, nameof(configuration)); DebugGuard.NotNull(entriesCollector, nameof(entriesCollector)); DebugGuard.MustBeBetweenOrEqualTo(bitsPerPixel, 4, 8, nameof(bitsPerPixel)); this.BitsPerPixel = bitsPerPixel; this.maxColors = this.BitsPerPixel == 4 ? 16 : 256; this.colorPaletteSize = this.maxColors * 3; this.colorPaletteBytes = this.colorPaletteSize * 2; using IQuantizer <TPixel> frameQuantizer = quantizer.CreatePixelSpecificQuantizer <TPixel>(this.Configuration, new QuantizerOptions() { MaxColors = this.maxColors }); this.quantizedImage = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); this.AddColorMapTag(); }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); Configuration configuration = this.Configuration; using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); ReadOnlySpan <TPixel> paletteSpan = quantized.Palette.Span; int offsetY = interest.Top; int offsetX = interest.Left; Buffer2D <TPixel> sourceBuffer = source.PixelBuffer; for (int y = interest.Y; y < interest.Height; y++) { Span <TPixel> row = sourceBuffer.DangerousGetRowSpan(y); ReadOnlySpan <byte> quantizedRow = quantized.DangerousGetRowSpan(y - offsetY); for (int x = interest.Left; x < interest.Right; x++) { row[x] = paletteSpan[quantizedRow[x - offsetX]]; } } }
/// <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 : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); ReadOnlySpan <TPixel> quantizedColors = quantized.Palette.Span; PixelOperations <TPixel> .Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast <byte, Bgra32>(colorPalette)); Span <uint> colorPaletteAsUInt = MemoryMarshal.Cast <byte, uint>(colorPalette); for (int i = 0; i < colorPaletteAsUInt.Length; i++) { colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0. } stream.Write(colorPalette); for (int y = image.Height - 1; y >= 0; y--) { ReadOnlySpan <byte> pixelSpan = quantized.GetPixelRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) { stream.WriteByte(0); } } }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); Configuration configuration = this.Configuration; using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ParallelRowIterator.IterateRowIntervals( configuration, interest, in operation); }
private void EncodeLocal <TPixel>(Image <TPixel> image, IndexedImageFrame <TPixel> quantized, Stream stream) where TPixel : unmanaged, IPixel <TPixel> { ImageFrame <TPixel> previousFrame = null; GifFrameMetadata previousMeta = null; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame <TPixel> frame = image.Frames[i]; 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 IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration, options); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } else { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); } } this.bitDepth = ColorNumerics.GetBitsNeededForColorDepth(quantized.Palette.Length); 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; } }
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 IQuantizer <Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer <Rgba32>(config); using IndexedImageFrame <Rgba32> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(1, result.Height); Assert.Equal(default, result.Palette.Span[0]);
/// <summary> /// Creates the quantized image and sets calculates and sets the bit depth. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="image">The image to quantize.</param> /// <returns>The quantized image.</returns> private IndexedImageFrame <TPixel> CreateQuantizedImage <TPixel>(Image <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { byte bits = (byte)options.PictBpp; var maxColors = GetColorCountForBitDepth(bits); options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); } // Create quantized frame returning the palette and set the bit depth. using IQuantizer <TPixel> frameQuantizer = options.Quantizer.CreatePixelSpecificQuantizer <TPixel>(image.GetConfiguration()); ImageFrame <TPixel> frame = image.Frames.RootFrame; return(frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())); }
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 IQuantizer <Rgba32> frameQuantizer = quantizer.CreatePixelSpecificQuantizer <Rgba32>(config); using IndexedImageFrame <Rgba32> result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); Assert.Equal(1, result.Palette.Length); Assert.Equal(1, result.Width); Assert.Equal(1, result.Height); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); Assert.Equal(0, result.GetPixelRowSpan(0)[0]); }
/// <summary> /// Writes a 1 bit image with a color palette. The color palette has 2 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 Write1BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration, new QuantizerOptions() { MaxColors = 2 }); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); using IMemoryOwner <byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean); Span <byte> colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan <TPixel> quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); ReadOnlySpan <byte> quantizedPixelRow = quantized.GetPixelRowSpan(0); int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { quantizedPixelRow = quantized.GetPixelRowSpan(y); int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; for (int i = 0; i < endIdx; i += 8) { Write1BitPalette(stream, i, i + 8, quantizedPixelRow); } if (quantizedPixelRow.Length % 8 != 0) { int startIdx = quantizedPixelRow.Length - 7; endIdx = quantizedPixelRow.Length; Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); } for (int i = 0; i < rowPadding; 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 : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); ReadOnlySpan <TPixel> quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); for (int y = image.Height - 1; y >= 0; y--) { ReadOnlySpan <byte> pixelSpan = quantized.GetPixelRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) { stream.WriteByte(0); } } }
/// <summary> /// Writes an 4 bit color image with a color palette. The color palette has 16 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 Write4BitColor <TPixel>(Stream stream, ImageFrame <TPixel> image) where TPixel : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration, new QuantizerOptions() { MaxColors = 16 }); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); using IMemoryOwner <byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean); Span <byte> colorPalette = colorPaletteBuffer.GetSpan(); ReadOnlySpan <TPixel> quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); ReadOnlySpan <byte> pixelRowSpan = quantized.GetPixelRowSpan(0); int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { pixelRowSpan = quantized.GetPixelRowSpan(y); int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; for (int i = 0; i < endIdx; i += 2) { stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); } if (pixelRowSpan.Length % 2 != 0) { stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0)); } for (int i = 0; i < rowPadding; 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 : unmanaged, IPixel <TPixel> { using IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration); using IndexedImageFrame <TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(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.GetPixelRowSpan(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="Image{TPixel}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="cancellationToken">The token to request cancellation.</param> public void Encode <TPixel>(Image <TPixel> image, Stream stream, CancellationToken cancellationToken) 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 (IQuantizer <TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer <TPixel>(this.configuration)) { if (useGlobalTable) { frameQuantizer.BuildPalette(this.pixelSamplingStrategy, image); quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } else { quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds()); } } // Get the number of bits. this.bitDepth = ColorNumerics.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); }