Browse Source

Supports indexed png when quality is set to <= 256

Former-commit-id: 83d6dd94b5db6de3a05d02e5e2884ba2740556ee
Former-commit-id: c41912b1e3b1a957ea9482e7b21de18bae85785c
Former-commit-id: 2504d8cd469282a5e24e0ecc730e391685486519
af/merge-core
James South 10 years ago
parent
commit
68ff5669c0
  1. 448
      src/ImageProcessorCore/Formats/Png/PngEncoder.cs
  2. 480
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

448
src/ImageProcessorCore/Formats/Png/PngEncoder.cs

@ -7,7 +7,6 @@ namespace ImageProcessorCore.Formats
{
using System;
using System.IO;
using System.Threading.Tasks;
using ImageProcessorCore.Quantizers;
@ -16,18 +15,6 @@ namespace ImageProcessorCore.Formats
/// </summary>
public class PngEncoder : IImageEncoder
{
/// <summary>
/// The maximum block size, defaults at 64k for uncompressed blocks.
/// </summary>
private const int MaxBlockSize = 65535;
/// <summary>
/// The number of bits required to encode the colors in the png.
/// </summary>
private byte bitDepth;
private QuantizedImage quantized;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
@ -45,19 +32,13 @@ namespace ImageProcessorCore.Formats
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public double Gamma { get; set; } = 2.2F;
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// The quantizer for reducing the color count.
@ -69,6 +50,12 @@ namespace ImageProcessorCore.Formats
/// </summary>
public byte Threshold { get; set; } = 128;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <inheritdoc/>
public bool IsSupportedFileExtension(string extension)
{
@ -82,420 +69,17 @@ namespace ImageProcessorCore.Formats
/// <inheritdoc/>
public void Encode(ImageBase image, Stream stream)
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
// 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);
this.Quality = image.Quality.Clamp(1, int.MaxValue);
this.bitDepth = this.Quality <= 256
? (byte)(this.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;
}
PngHeader header = new PngHeader
{
Width = image.Width,
Height = image.Height,
ColorType = (byte)(this.Quality <= 256 ? 3 : 6), // 3 = indexed, 6= Each pixel is an R,G,B triple, followed by an alpha sample.
BitDepth = this.bitDepth,
FilterMethod = 0, // None
CompressionMethod = 0,
InterlaceMethod = 0
PngEncoderCore encoder = new PngEncoderCore
{
CompressionLevel = this.CompressionLevel,
Gamma = this.Gamma,
Quality = this.Quality,
Quantizer = this.Quantizer,
WriteGamma = this.WriteGamma,
Threshold = this.Threshold
};
this.WriteHeaderChunk(stream, header);
this.WritePaletteChunk(stream, header, image);
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
this.WriteDataChunks(stream, image);
this.WriteEndChunk(stream);
stream.Flush();
}
/// <summary>
/// Writes an integer to the byte array.
/// </summary>
/// <param name="data">The <see cref="T:byte[]"/> containing image data.</param>
/// <param name="offset">The amount to offset by.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(byte[] data, int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
Array.Copy(buffer, 0, data, offset, 4);
}
/// <summary>
/// Writes an integer to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes an unsigned integer to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes the header chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
private void WriteHeaderChunk(Stream stream, PngHeader header)
{
byte[] chunkData = new byte[13];
WriteInteger(chunkData, 0, header.Width);
WriteInteger(chunkData, 4, header.Height);
chunkData[8] = header.BitDepth;
chunkData[9] = header.ColorType;
chunkData[10] = header.CompressionMethod;
chunkData[11] = header.FilterMethod;
chunkData[12] = header.InterlaceMethod;
this.WriteChunk(stream, PngChunkTypes.Header, chunkData);
}
/// <summary>
/// Writes the palette chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
private void WritePaletteChunk(Stream stream, PngHeader header, ImageBase image)
{
if (this.Quality > 256)
{
return;
}
if (this.Quantizer == null)
{
this.Quantizer = new WuQuantizer { Threshold = this.Threshold };
}
// Quantize the image returning a palette.
this.quantized = this.Quantizer.Quantize(image, this.Quality);
// Grab the palette and write it to the stream.
Bgra32[] palette = this.quantized.Palette;
int pixelCount = palette.Length;
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = new byte[colorTableLength];
Parallel.For(0, pixelCount,
i =>
{
int offset = i * 3;
Bgra32 color = palette[i];
colorTable[offset] = color.R;
colorTable[offset + 1] = color.G;
colorTable[offset + 2] = color.B;
});
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable);
// Write the transparency data
if (this.quantized.TransparentIndex > -1)
{
byte[] buffer = BitConverter.GetBytes(this.quantized.TransparentIndex);
Array.Reverse(buffer);
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, buffer);
}
}
/// <summary>
/// Writes the physical dimension information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="imageBase">The image base.</param>
private void WritePhysicalChunk(Stream stream, ImageBase imageBase)
{
Image image = imageBase as Image;
if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0)
{
// 39.3700787 = inches in a meter.
int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787d);
int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787d);
byte[] chunkData = new byte[9];
WriteInteger(chunkData, 0, dpmX);
WriteInteger(chunkData, 4, dpmY);
chunkData[8] = 1;
this.WriteChunk(stream, PngChunkTypes.Physical, chunkData);
}
}
/// <summary>
/// Writes the gamma information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream)
{
if (this.WriteGamma)
{
int gammaValue = (int)(this.Gamma * 100000f);
byte[] fourByteData = new byte[4];
byte[] size = BitConverter.GetBytes(gammaValue);
fourByteData[0] = size[3];
fourByteData[1] = size[2];
fourByteData[2] = size[1];
fourByteData[3] = size[0];
this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData);
}
}
/// <summary>
/// Writes the pixel information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image base.</param>
private void WriteDataChunks(Stream stream, ImageBase image)
{
byte[] data;
int imageWidth = image.Width;
int imageHeight = image.Height;
// Indexed image.
if (this.Quality <= 256)
{
// TODO: I think I need to split then pad the beginning of each row.
// Split the array etc. Code below doesn't do this right.
// Time to read the spec... Again.
//data = new byte[(imageWidth * imageHeight) + image.Height];
//int rowLength = imageWidth;
//Parallel.For(0, imageHeight, y =>
//{
// byte compression = 0;
// if (y > 0)
// {
// compression = 2;
// }
// data[y * rowLength] = compression;
// for (int x = 0; x < imageWidth; x++)
// {
// // Calculate the offset for the new array.
// int dataOffset = (y * rowLength) + x + 1;
// data[dataOffset + 1] = this.quantized.Pixels[(y * rowLength) + x];
// if (y > 0)
// {
// data[dataOffset] -= this.quantized.Pixels[((y - 1) * rowLength) + x];
// }
// }
//});
// This outputs image but doesn't pad.
data = this.quantized.Pixels;
}
else
{
// TrueColor image.
data = new byte[(imageWidth * imageHeight * 4) + image.Height];
int rowLength = (imageWidth * 4) + 1;
Parallel.For(0, imageHeight, y =>
{
byte compression = 0;
if (y > 0)
{
compression = 2;
}
data[y * rowLength] = compression;
for (int x = 0; x < imageWidth; x++)
{
Bgra32 color = Color.ToNonPremultiplied(image[x, y]);
// Calculate the offset for the new array.
int dataOffset = (y * rowLength) + (x * 4) + 1;
data[dataOffset] = color.R;
data[dataOffset + 1] = color.G;
data[dataOffset + 2] = color.B;
data[dataOffset + 3] = color.A;
if (y > 0)
{
color = Color.ToNonPremultiplied(image[x, y - 1]);
data[dataOffset] -= color.R;
data[dataOffset + 1] -= color.G;
data[dataOffset + 2] -= color.B;
data[dataOffset + 3] -= color.A;
}
}
});
}
byte[] buffer;
int bufferLength;
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream();
using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel))
{
deflateStream.Write(data, 0, data.Length);
}
bufferLength = (int)memoryStream.Length;
buffer = memoryStream.ToArray();
}
finally
{
memoryStream?.Dispose();
}
int numChunks = bufferLength / MaxBlockSize;
if (bufferLength % MaxBlockSize != 0)
{
numChunks++;
}
for (int i = 0; i < numChunks; i++)
{
int length = bufferLength - (i * MaxBlockSize);
if (length > MaxBlockSize)
{
length = MaxBlockSize;
}
this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length);
}
}
/// <summary>
/// Writes the chunk end to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream)
{
this.WriteChunk(stream, PngChunkTypes.End, null);
}
/// <summary>
/// Writes a chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, string type, byte[] data)
{
this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
}
/// <summary>
/// Writes a chunk of a specified length to the stream at the given offset.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length)
{
WriteInteger(stream, length);
byte[] typeArray = new byte[4];
typeArray[0] = (byte)type[0];
typeArray[1] = (byte)type[1];
typeArray[2] = (byte)type[2];
typeArray[3] = (byte)type[3];
stream.Write(typeArray, 0, 4);
if (data != null)
{
stream.Write(data, offset, length);
}
Crc32 crc32 = new Crc32();
crc32.Update(typeArray);
if (data != null)
{
crc32.Update(data, offset, length);
}
WriteInteger(stream, (uint)crc32.Value);
}
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colors">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int GetBitsNeededForColorDepth(int colors)
{
return (int)Math.Ceiling(Math.Log(colors, 2));
encoder.Encode(image, stream);
}
}
}

480
src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

@ -0,0 +1,480 @@
// <copyright file="PngEncoderCore.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
using System;
using System.IO;
using System.Threading.Tasks;
using ImageProcessorCore.Quantizers;
/// <summary>
/// Performs the png encoding operation.
/// TODO: Perf. There's lots of array parsing going on here. This should be unmanaged.
/// </summary>
internal class PngEncoderCore
{
/// <summary>
/// The maximum block size, defaults at 64k for uncompressed blocks.
/// </summary>
private const int MaxBlockSize = 65535;
/// <summary>
/// The number of bits required to encode the colors in the png.
/// </summary>
private byte bitDepth;
/// <summary>
/// The quantized image result.
/// </summary>
private QuantizedImage quantized;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
public int Quality { get; set; } = int.MaxValue;
/// <summary>
/// The compression level 1-9.
/// <remarks>Defaults to 6.</remarks>
/// </summary>
public int CompressionLevel { get; set; } = 6;
/// <summary>
/// Gets or sets a value indicating whether this instance should write
/// gamma information to the stream. The default value is false.
/// </summary>
public bool WriteGamma { get; set; }
/// <summary>
/// Gets or sets the gamma value, that will be written
/// the the stream, when the <see cref="WriteGamma"/> property
/// is set to true. The default value is 2.2F.
/// </summary>
/// <value>The gamma value of the image.</value>
public float Gamma { get; set; } = 2.2F;
/// <summary>
/// The quantizer for reducing the color count.
/// </summary>
public IQuantizer Quantizer { get; set; }
/// <summary>
/// Gets or sets the transparency threshold.
/// </summary>
public byte Threshold { get; set; } = 128;
/// <inheritdoc/>
public void Encode(ImageBase image, Stream stream)
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
// 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);
this.Quality = image.Quality.Clamp(1, int.MaxValue);
this.bitDepth = this.Quality <= 256
? (byte)(this.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;
}
// TODO: Add more color options here.
PngHeader header = new PngHeader
{
Width = image.Width,
Height = image.Height,
ColorType = (byte)(this.Quality <= 256 ? 3 : 6), // 3 = indexed, 6= Each pixel is an R,G,B triple, followed by an alpha sample.
BitDepth = this.bitDepth,
FilterMethod = 0, // None
CompressionMethod = 0,
InterlaceMethod = 0
};
this.WriteHeaderChunk(stream, header);
this.WritePaletteChunk(stream, header, image);
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
this.WriteDataChunks(stream, image);
this.WriteEndChunk(stream);
stream.Flush();
}
/// <summary>
/// Writes an integer to the byte array.
/// </summary>
/// <param name="data">The <see cref="T:byte[]"/> containing image data.</param>
/// <param name="offset">The amount to offset by.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(byte[] data, int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
Array.Copy(buffer, 0, data, offset, 4);
}
/// <summary>
/// Writes an integer to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes an unsigned integer to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="value">The value to write.</param>
private static void WriteInteger(Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
/// <summary>
/// Writes the header chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="header">The <see cref="PngHeader"/>.</param>
private void WriteHeaderChunk(Stream stream, PngHeader header)
{
byte[] chunkData = new byte[13];
WriteInteger(chunkData, 0, header.Width);
WriteInteger(chunkData, 4, header.Height);
chunkData[8] = header.BitDepth;
chunkData[9] = header.ColorType;
chunkData[10] = header.CompressionMethod;
chunkData[11] = header.FilterMethod;
chunkData[12] = header.InterlaceMethod;
this.WriteChunk(stream, PngChunkTypes.Header, chunkData);
}
/// <summary>
/// Writes the palette chunk to the stream.
/// </summary>
/// <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>
private void WritePaletteChunk(Stream stream, PngHeader header, ImageBase image)
{
if (this.Quality > 256)
{
return;
}
if (this.Quantizer == null)
{
this.Quantizer = new WuQuantizer { Threshold = this.Threshold };
}
// Quantize the image returning a palette.
this.quantized = this.Quantizer.Quantize(image, this.Quality);
// Grab the palette and write it to the stream.
Bgra32[] palette = this.quantized.Palette;
int pixelCount = palette.Length;
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
byte[] colorTable = new byte[colorTableLength];
Parallel.For(0, pixelCount,
i =>
{
int offset = i * 3;
Bgra32 color = palette[i];
colorTable[offset] = color.R;
colorTable[offset + 1] = color.G;
colorTable[offset + 2] = color.B;
});
this.WriteChunk(stream, PngChunkTypes.Palette, colorTable);
// Write the transparency data
if (this.quantized.TransparentIndex > -1)
{
byte[] buffer = BitConverter.GetBytes(this.quantized.TransparentIndex);
Array.Reverse(buffer);
this.WriteChunk(stream, PngChunkTypes.PaletteAlpha, buffer);
}
}
/// <summary>
/// Writes the physical dimension information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="imageBase">The image base.</param>
private void WritePhysicalChunk(Stream stream, ImageBase imageBase)
{
Image image = imageBase as Image;
if (image != null && image.HorizontalResolution > 0 && image.VerticalResolution > 0)
{
// 39.3700787 = inches in a meter.
int dpmX = (int)Math.Round(image.HorizontalResolution * 39.3700787d);
int dpmY = (int)Math.Round(image.VerticalResolution * 39.3700787d);
byte[] chunkData = new byte[9];
WriteInteger(chunkData, 0, dpmX);
WriteInteger(chunkData, 4, dpmY);
chunkData[8] = 1;
this.WriteChunk(stream, PngChunkTypes.Physical, chunkData);
}
}
/// <summary>
/// Writes the gamma information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteGammaChunk(Stream stream)
{
if (this.WriteGamma)
{
int gammaValue = (int)(this.Gamma * 100000f);
byte[] fourByteData = new byte[4];
byte[] size = BitConverter.GetBytes(gammaValue);
fourByteData[0] = size[3];
fourByteData[1] = size[2];
fourByteData[2] = size[1];
fourByteData[3] = size[0];
this.WriteChunk(stream, PngChunkTypes.Gamma, fourByteData);
}
}
/// <summary>
/// Writes the pixel information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image base.</param>
private void WriteDataChunks(Stream stream, ImageBase image)
{
byte[] data;
int imageWidth = image.Width;
int imageHeight = image.Height;
// Indexed image.
if (this.Quality <= 256)
{
int rowLength = imageWidth + 1;
data = new byte[rowLength * imageHeight];
Parallel.For(0, imageHeight, y =>
{
int dataOffset = (y * rowLength);
byte compression = 0;
if (y > 0)
{
compression = 2;
}
data[dataOffset++] = compression;
for (int x = 0; x < imageWidth; x++)
{
data[dataOffset++] = this.quantized.Pixels[(y * imageWidth) + x];
if (y > 0)
{
data[dataOffset - 1] -= this.quantized.Pixels[((y - 1) * imageWidth) + x];
}
}
});
}
else
{
// TrueColor image.
data = new byte[(imageWidth * imageHeight * 4) + image.Height];
int rowLength = (imageWidth * 4) + 1;
Parallel.For(0, imageHeight, y =>
{
byte compression = 0;
if (y > 0)
{
compression = 2;
}
data[y * rowLength] = compression;
for (int x = 0; x < imageWidth; x++)
{
Bgra32 color = Color.ToNonPremultiplied(image[x, y]);
// Calculate the offset for the new array.
int dataOffset = (y * rowLength) + (x * 4) + 1;
data[dataOffset] = color.R;
data[dataOffset + 1] = color.G;
data[dataOffset + 2] = color.B;
data[dataOffset + 3] = color.A;
if (y > 0)
{
color = Color.ToNonPremultiplied(image[x, y - 1]);
data[dataOffset] -= color.R;
data[dataOffset + 1] -= color.G;
data[dataOffset + 2] -= color.B;
data[dataOffset + 3] -= color.A;
}
}
});
}
byte[] buffer;
int bufferLength;
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream();
using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.CompressionLevel))
{
deflateStream.Write(data, 0, data.Length);
}
bufferLength = (int)memoryStream.Length;
buffer = memoryStream.ToArray();
}
finally
{
memoryStream?.Dispose();
}
int numChunks = bufferLength / MaxBlockSize;
if (bufferLength % MaxBlockSize != 0)
{
numChunks++;
}
for (int i = 0; i < numChunks; i++)
{
int length = bufferLength - (i * MaxBlockSize);
if (length > MaxBlockSize)
{
length = MaxBlockSize;
}
this.WriteChunk(stream, PngChunkTypes.Data, buffer, i * MaxBlockSize, length);
}
}
/// <summary>
/// Writes the chunk end to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
private void WriteEndChunk(Stream stream)
{
this.WriteChunk(stream, PngChunkTypes.End, null);
}
/// <summary>
/// Writes a chunk to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, string type, byte[] data)
{
this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
}
/// <summary>
/// Writes a chunk of a specified length to the stream at the given offset.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, string type, byte[] data, int offset, int length)
{
WriteInteger(stream, length);
byte[] typeArray = new byte[4];
typeArray[0] = (byte)type[0];
typeArray[1] = (byte)type[1];
typeArray[2] = (byte)type[2];
typeArray[3] = (byte)type[3];
stream.Write(typeArray, 0, 4);
if (data != null)
{
stream.Write(data, offset, length);
}
Crc32 crc32 = new Crc32();
crc32.Update(typeArray);
if (data != null)
{
crc32.Update(data, offset, length);
}
WriteInteger(stream, (uint)crc32.Value);
}
/// <summary>
/// Returns how many bits are required to store the specified number of colors.
/// Performs a Log2() on the value.
/// </summary>
/// <param name="colors">The number of colors.</param>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int GetBitsNeededForColorDepth(int colors)
{
return (int)Math.Ceiling(Math.Log(colors, 2));
}
}
}
Loading…
Cancel
Save