diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs
index 166105b98..170144ca7 100644
--- a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs
+++ b/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
///
public class PngEncoder : IImageEncoder
{
- ///
- /// The maximum block size, defaults at 64k for uncompressed blocks.
- ///
- private const int MaxBlockSize = 65535;
-
- ///
- /// The number of bits required to encode the colors in the png.
- ///
- private byte bitDepth;
-
- private QuantizedImage quantized;
-
///
/// Gets or sets the quality of output for images.
///
@@ -45,19 +32,13 @@ namespace ImageProcessorCore.Formats
///
public int CompressionLevel { get; set; } = 6;
- ///
- /// Gets or sets a value indicating whether this instance should write
- /// gamma information to the stream. The default value is false.
- ///
- public bool WriteGamma { get; set; }
-
///
/// Gets or sets the gamma value, that will be written
/// the the stream, when the property
/// is set to true. The default value is 2.2F.
///
/// The gamma value of the image.
- public double Gamma { get; set; } = 2.2F;
+ public float Gamma { get; set; } = 2.2F;
///
/// The quantizer for reducing the color count.
@@ -69,6 +50,12 @@ namespace ImageProcessorCore.Formats
///
public byte Threshold { get; set; } = 128;
+ ///
+ /// Gets or sets a value indicating whether this instance should write
+ /// gamma information to the stream. The default value is false.
+ ///
+ public bool WriteGamma { get; set; }
+
///
public bool IsSupportedFileExtension(string extension)
{
@@ -82,420 +69,17 @@ namespace ImageProcessorCore.Formats
///
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();
- }
-
- ///
- /// Writes an integer to the byte array.
- ///
- /// The containing image data.
- /// The amount to offset by.
- /// The value to write.
- 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);
- }
-
- ///
- /// Writes an integer to the stream.
- ///
- /// The containing image data.
- /// The value to write.
- private static void WriteInteger(Stream stream, int value)
- {
- byte[] buffer = BitConverter.GetBytes(value);
-
- Array.Reverse(buffer);
-
- stream.Write(buffer, 0, 4);
- }
-
- ///
- /// Writes an unsigned integer to the stream.
- ///
- /// The containing image data.
- /// The value to write.
- private static void WriteInteger(Stream stream, uint value)
- {
- byte[] buffer = BitConverter.GetBytes(value);
-
- Array.Reverse(buffer);
-
- stream.Write(buffer, 0, 4);
- }
-
- ///
- /// Writes the header chunk to the stream.
- ///
- /// The containing image data.
- /// The .
- 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);
- }
-
- ///
- /// Writes the palette chunk to the stream.
- ///
- /// The containing image data.
- /// The .
- 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);
- }
- }
-
- ///
- /// Writes the physical dimension information to the stream.
- ///
- /// The containing image data.
- /// The image base.
- 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);
- }
- }
-
- ///
- /// Writes the gamma information to the stream.
- ///
- /// The containing image data.
- 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);
- }
- }
-
- ///
- /// Writes the pixel information to the stream.
- ///
- /// The containing image data.
- /// The image base.
- 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);
- }
- }
-
- ///
- /// Writes the chunk end to the stream.
- ///
- /// The containing image data.
- private void WriteEndChunk(Stream stream)
- {
- this.WriteChunk(stream, PngChunkTypes.End, null);
- }
-
- ///
- /// Writes a chunk to the stream.
- ///
- /// The to write to.
- /// The type of chunk to write.
- /// The containing data.
- private void WriteChunk(Stream stream, string type, byte[] data)
- {
- this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
- }
-
- ///
- /// Writes a chunk of a specified length to the stream at the given offset.
- ///
- /// The to write to.
- /// The type of chunk to write.
- /// The containing data.
- /// The position to offset the data at.
- /// The of the data to write.
- 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);
- }
-
- ///
- /// Returns how many bits are required to store the specified number of colors.
- /// Performs a Log2() on the value.
- ///
- /// The number of colors.
- ///
- /// The
- ///
- private int GetBitsNeededForColorDepth(int colors)
- {
- return (int)Math.Ceiling(Math.Log(colors, 2));
+ encoder.Encode(image, stream);
}
}
}
diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
new file mode 100644
index 000000000..ada5ec153
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
@@ -0,0 +1,480 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Formats
+{
+ using System;
+ using System.IO;
+ using System.Threading.Tasks;
+
+ using ImageProcessorCore.Quantizers;
+
+ ///
+ /// Performs the png encoding operation.
+ /// TODO: Perf. There's lots of array parsing going on here. This should be unmanaged.
+ ///
+ internal class PngEncoderCore
+ {
+ ///
+ /// The maximum block size, defaults at 64k for uncompressed blocks.
+ ///
+ private const int MaxBlockSize = 65535;
+
+ ///
+ /// The number of bits required to encode the colors in the png.
+ ///
+ private byte bitDepth;
+
+ ///
+ /// The quantized image result.
+ ///
+ private QuantizedImage quantized;
+
+ ///
+ /// Gets or sets the quality of output for images.
+ ///
+ public int Quality { get; set; } = int.MaxValue;
+
+ ///
+ /// The compression level 1-9.
+ /// Defaults to 6.
+ ///
+ public int CompressionLevel { get; set; } = 6;
+
+ ///
+ /// Gets or sets a value indicating whether this instance should write
+ /// gamma information to the stream. The default value is false.
+ ///
+ public bool WriteGamma { get; set; }
+
+ ///
+ /// Gets or sets the gamma value, that will be written
+ /// the the stream, when the property
+ /// is set to true. The default value is 2.2F.
+ ///
+ /// The gamma value of the image.
+ public float Gamma { get; set; } = 2.2F;
+
+ ///
+ /// The quantizer for reducing the color count.
+ ///
+ public IQuantizer Quantizer { get; set; }
+
+ ///
+ /// Gets or sets the transparency threshold.
+ ///
+ public byte Threshold { get; set; } = 128;
+
+ ///
+ 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();
+ }
+
+ ///
+ /// Writes an integer to the byte array.
+ ///
+ /// The containing image data.
+ /// The amount to offset by.
+ /// The value to write.
+ 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);
+ }
+
+ ///
+ /// Writes an integer to the stream.
+ ///
+ /// The containing image data.
+ /// The value to write.
+ private static void WriteInteger(Stream stream, int value)
+ {
+ byte[] buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+
+ stream.Write(buffer, 0, 4);
+ }
+
+ ///
+ /// Writes an unsigned integer to the stream.
+ ///
+ /// The containing image data.
+ /// The value to write.
+ private static void WriteInteger(Stream stream, uint value)
+ {
+ byte[] buffer = BitConverter.GetBytes(value);
+
+ Array.Reverse(buffer);
+
+ stream.Write(buffer, 0, 4);
+ }
+
+ ///
+ /// Writes the header chunk to the stream.
+ ///
+ /// The containing image data.
+ /// The .
+ 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);
+ }
+
+ ///
+ /// Writes the palette chunk to the stream.
+ ///
+ /// The containing image data.
+ /// The .
+ /// The image to encode.
+ 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);
+ }
+ }
+
+ ///
+ /// Writes the physical dimension information to the stream.
+ ///
+ /// The containing image data.
+ /// The image base.
+ 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);
+ }
+ }
+
+ ///
+ /// Writes the gamma information to the stream.
+ ///
+ /// The containing image data.
+ 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);
+ }
+ }
+
+ ///
+ /// Writes the pixel information to the stream.
+ ///
+ /// The containing image data.
+ /// The image base.
+ 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);
+ }
+ }
+
+ ///
+ /// Writes the chunk end to the stream.
+ ///
+ /// The containing image data.
+ private void WriteEndChunk(Stream stream)
+ {
+ this.WriteChunk(stream, PngChunkTypes.End, null);
+ }
+
+ ///
+ /// Writes a chunk to the stream.
+ ///
+ /// The to write to.
+ /// The type of chunk to write.
+ /// The containing data.
+ private void WriteChunk(Stream stream, string type, byte[] data)
+ {
+ this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
+ }
+
+ ///
+ /// Writes a chunk of a specified length to the stream at the given offset.
+ ///
+ /// The to write to.
+ /// The type of chunk to write.
+ /// The containing data.
+ /// The position to offset the data at.
+ /// The of the data to write.
+ 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);
+ }
+
+ ///
+ /// Returns how many bits are required to store the specified number of colors.
+ /// Performs a Log2() on the value.
+ ///
+ /// The number of colors.
+ ///
+ /// The
+ ///
+ private int GetBitsNeededForColorDepth(int colors)
+ {
+ return (int)Math.Ceiling(Math.Log(colors, 2));
+ }
+ }
+}