/// <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="QuantizedImage{TPixel}"/></returns> private QuantizedImage <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. QuantizedImage <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; byte[] colorTable = ArrayPool <byte> .Shared.Rent(colorTableLength); byte[] alphaTable = ArrayPool <byte> .Shared.Rent(pixelCount); var rgba = default(Rgba32); bool anyAlpha = false; try { 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; colorTable[offset] = rgba.R; colorTable[offset + 1] = rgba.G; colorTable[offset + 2] = rgba.B; if (alpha > this.threshold) { alpha = 255; } anyAlpha = anyAlpha || alpha < 255; alphaTable[i] = alpha; } } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); // Write the transparency data if (anyAlpha) { this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, alphaTable, 0, pixelCount); } } finally { ArrayPool <byte> .Shared.Return(colorTable); ArrayPool <byte> .Shared.Return(alphaTable); } return(quantized); }
/// <summary> /// Collects the indexed pixel data. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The image to encode.</param> /// <param name="stream">The <see cref="Stream"/> containing image data.</param> /// <param name="header">The <see cref="PngHeader"/>.</param> private void CollectIndexedBytes <TPixel>(ImageFrame <TPixel> image, Stream stream, PngHeader header) where TPixel : struct, IPixel <TPixel> { // Quantize the image and get the pixels. QuantizedImage <TPixel> quantized = this.WritePaletteChunk(stream, header, image); this.palettePixelData = quantized.Pixels; }
/// <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); // Set correct color type if the color count is 256 or less. if (this.paletteSize <= 256) { this.pngColorType = PngColorType.Palette; } if (this.pngColorType == PngColorType.Palette && this.paletteSize > 256) { this.paletteSize = 256; } // Set correct bit depth. this.bitDepth = this.paletteSize <= 256 ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.paletteSize).Clamp(1, 8) : (byte)8; // Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk if (this.bitDepth == 3) { this.bitDepth = 4; } else if (this.bitDepth >= 5 || this.bitDepth <= 7) { 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 (this.pngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image.Frames.RootFrame, stream, header); } 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.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="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, 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(); }