/// <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, ImageBase <TPixel> image) where TPixel : struct, IPixel <TPixel> { if (this.quality > 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.quality); // 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); byte[] bytes = ArrayPool <byte> .Shared.Rent(4); bool anyAlpha = false; try { for (byte i = 0; i < pixelCount; i++) { if (quantized.Pixels.Contains(i)) { int offset = i * 3; palette[i].ToXyzwBytes(bytes, 0); byte alpha = bytes[3]; colorTable[offset] = bytes[0]; colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; if (alpha > this.options.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); ArrayPool <byte> .Shared.Return(bytes); } 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="ImageBase{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); // Ensure that quality can be set but has a fallback. this.quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; this.quality = this.quality > 0 ? this.quality.Clamp(1, int.MaxValue) : int.MaxValue; this.pngColorType = this.options.PngColorType; this.quantizer = this.options.Quantizer; // Set correct color type if the color count is 256 or less. if (this.quality <= 256) { this.pngColorType = PngColorType.Palette; } if (this.pngColorType == PngColorType.Palette && this.quality > 256) { this.quality = 256; } // Set correct bit depth. this.bitDepth = this.quality <= 256 ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.quality).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(); PngHeader 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, stream, header); } this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); using (PixelAccessor <TPixel> pixels = image.Lock()) { this.WriteDataChunks(pixels, stream); } this.WriteEndChunk(stream); stream.Flush(); }
/// <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>(ImageBase <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> /// Writes the palette chunk to the stream. /// </summary> /// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></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{TColor, TPacked}"/></returns> private QuantizedImage <TColor, TPacked> WritePaletteChunk <TColor, TPacked>(Stream stream, PngHeader header, ImageBase <TColor, TPacked> image) where TColor : struct, IPackedPixel <TPacked> where TPacked : struct { if (this.Quality > 256) { return(null); } if (this.Quantizer == null) { this.Quantizer = new WuQuantizer <TColor, TPacked>(); } // Quantize the image returning a palette. This boxing is icky. QuantizedImage <TColor, TPacked> quantized = ((IQuantizer <TColor, TPacked>) this.Quantizer).Quantize(image, this.Quality); // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; int pixelCount = palette.Length; List <byte> transparentPixels = new List <byte>(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; byte[] colorTable = ArrayPool <byte> .Shared.Rent(colorTableLength); byte[] bytes = ArrayPool <byte> .Shared.Rent(4); try { for (int i = 0; i < pixelCount; i++) { int offset = i * 3; palette[i].ToBytes(bytes, 0, ComponentOrder.XYZW); int alpha = bytes[3]; colorTable[offset] = bytes[0]; colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; if (alpha <= this.Threshold) { transparentPixels.Add((byte)offset); } } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); } finally { ArrayPool <byte> .Shared.Return(colorTable); ArrayPool <byte> .Shared.Return(bytes); } // Write the transparency data if (transparentPixels.Any()) { this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray()); } return(quantized); }
/// <summary> /// Writes the palette chunk to the stream. /// </summary> /// <typeparam name="TColor">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{TColor}"/></returns> private QuantizedImage <TColor> WritePaletteChunk <TColor>(Stream stream, PngHeader header, ImageBase <TColor> image) where TColor : struct, IPixel <TColor> { if (this.quality > 256) { return(null); } if (this.quantizer == null) { this.quantizer = new OctreeQuantizer <TColor>(); } // Quantize the image returning a palette. This boxing is icky. QuantizedImage <TColor> quantized = ((IQuantizer <TColor>) this.quantizer).Quantize(image, this.quality); // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; int pixelCount = palette.Length; List <byte> transparentPixels = new List <byte>(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; byte[] colorTable = ArrayPool <byte> .Shared.Rent(colorTableLength); byte[] bytes = ArrayPool <byte> .Shared.Rent(4); try { for (int i = 0; i < pixelCount; i++) { int offset = i * 3; palette[i].ToXyzwBytes(bytes, 0); int alpha = bytes[3]; colorTable[offset] = bytes[0]; colorTable[offset + 1] = bytes[1]; colorTable[offset + 2] = bytes[2]; if (alpha < 255 && alpha <= this.options.Threshold) { // Ensure the index is actually being used in our array. // I'd like to find a faster way of doing this. if (quantized.Pixels.Contains((byte)i)) { transparentPixels.Add((byte)i); } } } this.WriteChunk(stream, PngChunkTypes.Palette, colorTable, 0, colorTableLength); } finally { ArrayPool <byte> .Shared.Return(colorTable); ArrayPool <byte> .Shared.Return(bytes); } // Write the transparency data if (transparentPixels.Any()) { this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray()); } return(quantized); }
/// <summary> /// Writes the palette chunk to the stream. /// </summary> /// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></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{TColor, TPacked}"/></returns> private QuantizedImage <TColor, TPacked> WritePaletteChunk <TColor, TPacked>(Stream stream, PngHeader header, ImageBase <TColor, TPacked> image) where TColor : struct, IPackedPixel <TPacked> where TPacked : struct { if (this.Quality > 256) { return(null); } if (this.Quantizer == null) { this.Quantizer = new WuQuantizer <TColor, TPacked>(); } // Quantize the image returning a palette. This boxing is icky. QuantizedImage <TColor, TPacked> quantized = ((IQuantizer <TColor, TPacked>) this.Quantizer).Quantize(image, this.Quality); // Grab the palette and write it to the stream. TColor[] palette = quantized.Palette; int pixelCount = palette.Length; List <byte> transparentPixels = new List <byte>(); // Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3; byte[] colorTable = new byte[colorTableLength]; // TODO: Optimize this. Parallel.For( 0, pixelCount, Bootstrapper.Instance.ParallelOptions, i => { int offset = i * 3; Color color = new Color(palette[i].ToVector4()); int alpha = color.A; // Premultiply the color. This helps prevent banding. if (alpha < 255 && alpha > this.Threshold) { color = Color.Multiply(color, new Color(alpha, alpha, alpha, 255)); } colorTable[offset] = color.R; colorTable[offset + 1] = color.G; colorTable[offset + 2] = color.B; if (alpha <= this.Threshold) { transparentPixels.Add((byte)offset); } }); this.WriteChunk(stream, PngChunkTypes.Palette, colorTable); // Write the transparency data if (transparentPixels.Any()) { this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, transparentPixels.ToArray()); } return(quantized); }
/// <summary> /// Collects the indexed pixel data. /// </summary> /// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></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 <TColor, TPacked>(ImageBase <TColor, TPacked> image, Stream stream, PngHeader header) where TColor : struct, IPackedPixel <TPacked> where TPacked : struct { // Quatize the image and get the pixels QuantizedImage <TColor, TPacked> quantized = this.WritePaletteChunk(stream, header, image); this.pixelData = quantized.Pixels; }
/// <summary> /// Encodes the image to the specified stream from the <see cref="ImageBase{TColor, TPacked}"/>. /// </summary> /// <typeparam name="TColor">The pixel format.</typeparam> /// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam> /// <param name="image">The <see cref="ImageBase{TColor, TPacked}"/> to encode from.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> public void Encode <TColor, TPacked>(ImageBase <TColor, TPacked> image, Stream stream) where TColor : struct, IPackedPixel <TPacked> where TPacked : struct { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); this.width = image.Width; this.height = image.Height; // Write the png header. stream.Write( new byte[] { 0x89, // Set the high bit. 0x50, // P 0x4E, // N 0x47, // G 0x0D, // Line ending CRLF 0x0A, // Line ending CRLF 0x1A, // EOF 0x0A // LF }, 0, 8); // Ensure that quality can be set but has a fallback. int quality = this.Quality > 0 ? this.Quality : image.Quality; this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; // Set correct color type if the color count is 256 or less. if (this.Quality <= 256) { this.PngColorType = PngColorType.Palette; } // Set correct bit depth. this.bitDepth = this.Quality <= 256 ? (byte)ImageMaths.GetBitsNeededForColorDepth(this.Quality).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(); PngHeader header = new PngHeader { Width = image.Width, Height = image.Height, ColorType = (byte)this.PngColorType, BitDepth = this.bitDepth, FilterMethod = 0, // None CompressionMethod = 0, InterlaceMethod = 0 }; this.WriteHeaderChunk(stream, header); // Collect the pixel data if (this.PngColorType == PngColorType.Palette) { this.CollectIndexedBytes(image, stream, header); } else if (this.PngColorType == PngColorType.Grayscale || this.PngColorType == PngColorType.GrayscaleWithAlpha) { this.CollectGrayscaleBytes(image); } else { this.CollectColorBytes(image); } this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); this.WriteDataChunks(stream); this.WriteEndChunk(stream); stream.Flush(); }