diff --git a/src/ImageProcessorCore/Formats/Png/Filters/AverageFilter.cs b/src/ImageProcessorCore/Formats/Png/Filters/AverageFilter.cs
new file mode 100644
index 0000000000..c7f3f537aa
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/Filters/AverageFilter.cs
@@ -0,0 +1,44 @@
+namespace ImageProcessorCore.Formats
+{
+ using System;
+
+ internal static class AverageFilter
+ {
+ public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ {
+ byte[] result = new byte[scanline.Length];
+
+ for (int x = 1; x < scanline.Length; x++)
+ {
+ byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel];
+ byte above = previousScanline[x];
+
+ result[x] = (byte)((scanline[x] + Average(left, above)) % 256);
+ }
+
+ return result;
+ }
+
+ public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ {
+ var encodedScanline = new byte[scanline.Length + 1];
+
+ encodedScanline[0] = (byte)FilterType.Average;
+
+ for (int x = 0; x < scanline.Length; x++)
+ {
+ byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
+ byte above = previousScanline[x];
+
+ encodedScanline[x + 1] = (byte)((scanline[x] - Average(left, above)) % 256);
+ }
+
+ return encodedScanline;
+ }
+
+ private static int Average(byte left, byte above)
+ {
+ return Convert.ToInt32(Math.Floor((left + above) / 2.0));
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Formats/Png/Filters/FilterType.cs b/src/ImageProcessorCore/Formats/Png/Filters/FilterType.cs
new file mode 100644
index 0000000000..0a8a519a3b
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/Filters/FilterType.cs
@@ -0,0 +1,44 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Formats
+{
+ ///
+ /// Provides enumeration of the various png filter types.
+ ///
+ internal enum FilterType
+ {
+ ///
+ /// With the None filter, the scanline is transmitted unmodified; it is only necessary to
+ /// insert a filter type byte before the data.
+ ///
+ None = 0,
+
+ ///
+ /// The Sub filter transmits the difference between each byte and the value of the corresponding
+ /// byte of the prior pixel.
+ ///
+ Sub = 1,
+
+ ///
+ /// The Up filter is just like the Sub filter except that the pixel immediately above the current
+ /// pixel, rather than just to its left, is used as the predictor.
+ ///
+ Up = 2,
+
+ ///
+ /// The Average filter uses the average of the two neighboring pixels (left and above) to
+ /// predict the value of a pixel.
+ ///
+ Average = 3,
+
+ ///
+ /// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left),
+ /// then chooses as predictor the neighboring pixel closest to the computed value.
+ /// This technique is due to Alan W. Paeth
+ ///
+ Paeth = 4
+ }
+}
diff --git a/src/ImageProcessorCore/Formats/Png/Filters/NoneFilter.cs b/src/ImageProcessorCore/Formats/Png/Filters/NoneFilter.cs
new file mode 100644
index 0000000000..a92a53a90e
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/Filters/NoneFilter.cs
@@ -0,0 +1,40 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Formats
+{
+ ///
+ /// The None filter, the scanline is transmitted unmodified; it is only necessary to
+ /// insert a filter type byte before the data.
+ ///
+ internal static class NoneFilter
+ {
+ ///
+ /// Decodes the scanline
+ ///
+ /// The scanline to decode
+ /// The
+ public static byte[] Decode(byte[] scanline)
+ {
+ // No change required.
+ return scanline;
+ }
+
+ ///
+ /// Encodes the scanline
+ ///
+ /// The scanline to encode
+ /// The
+ public static byte[] Encode(byte[] scanline)
+ {
+ // Insert a byte before the data.
+ byte[] encodedScanline = new byte[scanline.Length + 1];
+ encodedScanline[0] = (byte)FilterType.None;
+ scanline.CopyTo(encodedScanline, 1);
+
+ return encodedScanline;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Formats/Png/Filters/PaethFilter.cs b/src/ImageProcessorCore/Formats/Png/Filters/PaethFilter.cs
new file mode 100644
index 0000000000..bb90ce06fc
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/Filters/PaethFilter.cs
@@ -0,0 +1,66 @@
+namespace ImageProcessorCore.Formats
+{
+ using System;
+
+ internal static class PaethFilter
+ {
+ public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ {
+ byte[] result = new byte[scanline.Length];
+
+ for (int x = 1; x < scanline.Length; x++)
+ {
+ byte left = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel];
+ byte above = previousScanline[x];
+ byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : previousScanline[x - bytesPerPixel];
+
+ result[x] = (byte)((scanline[x] + PaethPredictor(left, above, upperLeft)) % 256);
+ }
+
+ return result;
+ }
+
+ public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
+ {
+ var encodedScanline = new byte[scanline.Length + 1];
+
+ encodedScanline[0] = (byte)FilterType.Paeth;
+
+ for (int x = 0; x < scanline.Length; x++)
+ {
+ byte left = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
+ byte above = previousScanline[x];
+ byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : previousScanline[x - bytesPerPixel];
+
+ encodedScanline[x + 1] = (byte)((scanline[x] - PaethPredictor(left, above, upperLeft)) % 256);
+ }
+
+ return encodedScanline;
+ }
+
+ private static int PaethPredictor(int a, int b, int c)
+ {
+ int p = a + b - c;
+ int pa = Math.Abs(p - a);
+ int pb = Math.Abs(p - b);
+ int pc = Math.Abs(p - c);
+
+ if ((pa <= pb) && (pa <= pc))
+ {
+ return a;
+ }
+ else
+ {
+ if (pb <= pc)
+ {
+ return b;
+ }
+ else
+ {
+ return c;
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/ImageProcessorCore/Formats/Png/Filters/SubFilter.cs b/src/ImageProcessorCore/Formats/Png/Filters/SubFilter.cs
new file mode 100644
index 0000000000..9734af6547
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/Filters/SubFilter.cs
@@ -0,0 +1,35 @@
+namespace ImageProcessorCore.Formats
+{
+ internal static class SubFilter
+ {
+ public static byte[] Decode(byte[] scanline, int bytesPerPixel)
+ {
+ byte[] result = new byte[scanline.Length];
+
+ for (int x = 1; x < scanline.Length; x++)
+ {
+ byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : result[x - bytesPerPixel];
+
+ result[x] = (byte)((scanline[x] + priorRawByte) % 256);
+ }
+
+ return result;
+ }
+
+ public static byte[] Encode(byte[] scanline, int bytesPerPixel)
+ {
+ var encodedScanline = new byte[scanline.Length + 1];
+
+ encodedScanline[0] = (byte)FilterType.Sub;
+
+ for (int x = 0; x < scanline.Length; x++)
+ {
+ byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scanline[x - bytesPerPixel];
+
+ encodedScanline[x + 1] = (byte)((scanline[x] - priorRawByte) % 256);
+ }
+
+ return encodedScanline;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Formats/Png/Filters/UpFilter.cs b/src/ImageProcessorCore/Formats/Png/Filters/UpFilter.cs
new file mode 100644
index 0000000000..a2ca0ed15f
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/Filters/UpFilter.cs
@@ -0,0 +1,35 @@
+namespace ImageProcessorCore.Formats
+{
+ internal static class UpFilter
+ {
+ public static byte[] Decode(byte[] scanline, byte[] previousScanline)
+ {
+ byte[] result = new byte[scanline.Length];
+
+ for (int x = 1; x < scanline.Length; x++)
+ {
+ byte above = previousScanline[x];
+
+ result[x] = (byte)((scanline[x] + above) % 256);
+ }
+
+ return result;
+ }
+
+ public static byte[] Encode(byte[] scanline, byte[] previousScanline)
+ {
+ var encodedScanline = new byte[scanline.Length + 1];
+
+ encodedScanline[0] = (byte)FilterType.Up;
+
+ for (int x = 0; x < scanline.Length; x++)
+ {
+ byte above = previousScanline[x];
+
+ encodedScanline[x + 1] = (byte)((scanline[x] - above) % 256);
+ }
+
+ return encodedScanline;
+ }
+ }
+}
diff --git a/src/ImageProcessorCore/Formats/Png/PngColorType.cs b/src/ImageProcessorCore/Formats/Png/PngColorType.cs
new file mode 100644
index 0000000000..574ed2a09e
--- /dev/null
+++ b/src/ImageProcessorCore/Formats/Png/PngColorType.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Formats
+{
+ ///
+ /// Provides enumeration of available png color types.
+ ///
+ public enum PngColorType
+ {
+ ///
+ /// Each pixel is a grayscale sample.
+ ///
+ Grayscale = 0,
+
+ ///
+ /// Each pixel is an R,G,B triple.
+ ///
+ Rgb = 2,
+
+ ///
+ /// Each pixel is a palette index; a PLTE chunk must appear.
+ ///
+ Palette = 3,
+
+ ///
+ /// Each pixel is a grayscale sample, followed by an alpha sample.
+ ///
+ GrayscaleWithAlpha = 4,
+
+ ///
+ /// Each pixel is an R,G,B triple, followed by an alpha sample.
+ ///
+ RgbWithAlpha = 6
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs
index 777a8e7664..36728a7388 100644
--- a/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs
@@ -22,11 +22,6 @@ namespace ImageProcessorCore.Formats
private static readonly Dictionary ColorTypes
= new Dictionary();
- ///
- /// The image to decode.
- ///
- //private IImage currentImage;
-
///
/// The stream to decode from.
///
diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs
index 3aaaf19a75..abe487d40c 100644
--- a/src/ImageProcessorCore/Formats/Png/PngEncoder.cs
+++ b/src/ImageProcessorCore/Formats/Png/PngEncoder.cs
@@ -20,6 +20,11 @@ namespace ImageProcessorCore.Formats
///
public int Quality { get; set; }
+ ///
+ /// Gets or sets the png color type
+ ///
+ public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
+
///
public string MimeType => "image/png";
@@ -76,6 +81,7 @@ namespace ImageProcessorCore.Formats
CompressionLevel = this.CompressionLevel,
Gamma = this.Gamma,
Quality = this.Quality,
+ PngColorType = PngColorType,
Quantizer = this.Quantizer,
WriteGamma = this.WriteGamma,
Threshold = this.Threshold
diff --git a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
index eeb7c651f4..71b9b6542a 100644
--- a/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
@@ -6,6 +6,7 @@
namespace ImageProcessorCore.Formats
{
using System;
+ using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
@@ -22,6 +23,21 @@ namespace ImageProcessorCore.Formats
///
private const int MaxBlockSize = 65535;
+ ///
+ /// Contains the raw pixel data from the image.
+ ///
+ byte[] pixelData;
+
+ ///
+ /// The image width.
+ ///
+ private int width;
+
+ ///
+ /// The image height.
+ ///
+ private int height;
+
///
/// The number of bits required to encode the colors in the png.
///
@@ -32,6 +48,11 @@ namespace ImageProcessorCore.Formats
///
public int Quality { get; set; }
+ ///
+ /// Gets or sets the png color type
+ ///
+ public PngColorType PngColorType { get; set; }
+
///
/// The compression level 1-9.
/// Defaults to 6.
@@ -76,6 +97,9 @@ namespace ImageProcessorCore.Formats
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[]
@@ -96,6 +120,13 @@ namespace ImageProcessorCore.Formats
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 (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;
@@ -123,19 +154,166 @@ namespace ImageProcessorCore.Formats
};
this.WriteHeaderChunk(stream, header);
- QuantizedImage quantized = this.WritePaletteChunk(stream, header, image);
- this.WritePhysicalChunk(stream, image);
- this.WriteGammaChunk(stream);
- using (IPixelAccessor pixels = image.Lock())
+ if (this.Quality <= 256)
{
- this.WriteDataChunks(stream, pixels, quantized);
+ // Quatize the image and get the pixels
+ QuantizedImage quantized = this.WritePaletteChunk(stream, header, image);
+ pixelData = quantized.Pixels;
}
+ else
+ {
+ // Copy the pixels across from the image.
+ // TODO: This should vary by bytes per pixel.
+ this.pixelData = new byte[this.width * this.height * 4];
+ int stride = this.width * 4;
+ using (IPixelAccessor pixels = image.Lock())
+ {
+ for (int y = 0; y < this.height; y++)
+ {
+ for (int x = 0; x < this.width; x++)
+ {
+ int dataOffset = (y * stride) + (x * 4);
+ byte[] source = pixels[x, y].ToBytes();
+
+ // r -> g -> b -> a
+ this.pixelData[dataOffset] = source[0];
+ this.pixelData[dataOffset + 1] = source[1];
+ this.pixelData[dataOffset + 2] = source[2];
+ this.pixelData[dataOffset + 3] = source[3];
+ }
+ }
+ }
+ }
+
+ this.WritePhysicalChunk(stream, image);
+ this.WriteGammaChunk(stream);
+
+ //using (IPixelAccessor pixels = image.Lock())
+ //{
+ // this.WriteDataChunks(stream, pixels, quantized);
+ //}
+ this.WriteDataChunks(stream);
this.WriteEndChunk(stream);
stream.Flush();
}
+ private byte[] EncodePixelData()
+ {
+ List filteredScanlines = new List();
+
+ int bytesPerPixel = CalculateBytesPerPixel();
+ byte[] previousScanline = new byte[width * bytesPerPixel];
+
+ for (int y = 0; y < height; y++)
+ {
+ byte[] rawScanline = GetRawScanline(y);
+ byte[] filteredScanline = GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerPixel);
+
+ filteredScanlines.Add(filteredScanline);
+
+ previousScanline = rawScanline;
+ }
+
+ List result = new List();
+
+ foreach (var encodedScanline in filteredScanlines)
+ {
+ result.AddRange(encodedScanline);
+ }
+
+ return result.ToArray();
+ }
+
+ ///
+ /// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed
+ /// to be most compressible, using lowest total variation as proxy for compressibility.
+ ///
+ ///
+ ///
+ ///
+ ///
+ private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerPixel)
+ {
+ List> candidates = new List>();
+
+ byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel);
+ candidates.Add(new Tuple(sub, CalculateTotalVariation(sub)));
+
+ byte[] up = UpFilter.Encode(rawScanline, previousScanline);
+ candidates.Add(new Tuple(up, CalculateTotalVariation(up)));
+
+ byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
+ candidates.Add(new Tuple(average, CalculateTotalVariation(average)));
+
+ byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
+ candidates.Add(new Tuple(paeth, CalculateTotalVariation(paeth)));
+
+ int lowestTotalVariation = int.MaxValue;
+ int lowestTotalVariationIndex = 0;
+
+ for (int i = 0; i < candidates.Count; i++)
+ {
+ if (candidates[i].Item2 < lowestTotalVariation)
+ {
+ lowestTotalVariationIndex = i;
+ lowestTotalVariation = candidates[i].Item2;
+ }
+ }
+
+ return candidates[lowestTotalVariationIndex].Item1;
+ }
+
+ ///
+ /// Calculates the total variation of given byte array. Total variation is the sum of the absolute values of
+ /// neighbour differences.
+ ///
+ ///
+ ///
+ private int CalculateTotalVariation(byte[] input)
+ {
+ int totalVariation = 0;
+
+ for (int i = 1; i < input.Length; i++)
+ {
+ totalVariation += Math.Abs(input[i] - input[i - 1]);
+ }
+
+ return totalVariation;
+ }
+
+ private byte[] GetRawScanline(int y)
+ {
+ // TODO: This should vary by bytes per pixel.
+ int stride = (this.PngColorType == PngColorType.Palette ? 1 : 4) * this.width;
+ byte[] rawScanline = new byte[stride];
+ Array.Copy(this.pixelData, y * stride, rawScanline, 0, stride);
+ return rawScanline;
+ }
+
+ private int CalculateBytesPerPixel()
+ {
+ switch (this.PngColorType)
+ {
+ case PngColorType.Grayscale:
+ return 1;
+
+ case PngColorType.GrayscaleWithAlpha:
+ return 2;
+
+ case PngColorType.Palette:
+ return 1;
+
+ case PngColorType.Rgb:
+ return 3;
+
+ // PngColorType.RgbWithAlpha
+ default:
+ return 4;
+ }
+ }
+
///
/// Writes an integer to the byte array.
///
@@ -313,94 +491,9 @@ namespace ImageProcessorCore.Formats
///
/// Writes the pixel information to the stream.
///
- /// The pixel format.
- /// The packed format. long, float.
- /// The containing image data.
- /// The image pixels.
- /// The quantized image.
- private void WriteDataChunks(Stream stream, IPixelAccessor pixels, QuantizedImage quantized)
- where T : IPackedVector
- where TP : struct
+ private void WriteDataChunks(Stream stream)
{
- byte[] data;
- int imageWidth = pixels.Width;
- int imageHeight = pixels.Height;
-
- // Indexed image.
- if (this.Quality <= 256)
- {
- int rowLength = imageWidth + 1;
- data = new byte[rowLength * imageHeight];
-
- Parallel.For(
- 0,
- imageHeight,
- Bootstrapper.Instance.ParallelOptions,
- 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++] = quantized.Pixels[(y * imageWidth) + x];
- if (y > 0)
- {
- data[dataOffset - 1] -= quantized.Pixels[((y - 1) * imageWidth) + x];
- }
- }
- });
- }
- else
- {
- // TrueColor image.
- data = new byte[(imageWidth * imageHeight * 4) + pixels.Height];
-
- int rowLength = (imageWidth * 4) + 1;
-
- Parallel.For(
- 0,
- imageHeight,
- Bootstrapper.Instance.ParallelOptions,
- y =>
- {
- byte compression = 0;
- if (y > 0)
- {
- compression = 2;
- }
-
- data[y * rowLength] = compression;
-
- for (int x = 0; x < imageWidth; x++)
- {
- byte[] color = pixels[x, y].ToBytes();
-
- // Calculate the offset for the new array.
- int dataOffset = (y * rowLength) + (x * 4) + 1;
-
- // Expected format
- data[dataOffset] = color[0];
- data[dataOffset + 1] = color[1];
- data[dataOffset + 2] = color[2];
- data[dataOffset + 3] = color[3];
-
- if (y > 0)
- {
- color = pixels[x, y - 1].ToBytes();
-
- data[dataOffset] -= color[0];
- data[dataOffset + 1] -= color[1];
- data[dataOffset + 2] -= color[2];
- data[dataOffset + 3] -= color[3];
- }
- }
- });
- }
+ byte[] data = this.EncodePixelData();
byte[] buffer;
int bufferLength;
@@ -443,6 +536,139 @@ namespace ImageProcessorCore.Formats
}
}
+ /////
+ ///// Writes the pixel information to the stream.
+ /////
+ ///// The pixel format.
+ ///// The packed format. long, float.
+ ///// The containing image data.
+ ///// The image pixels.
+ ///// The quantized image.
+ //private void WriteDataChunks(Stream stream, IPixelAccessor pixels, QuantizedImage quantized)
+ // where T : IPackedVector
+ // where TP : struct
+ //{
+ // byte[] data;
+ // int imageWidth = pixels.Width;
+ // int imageHeight = pixels.Height;
+
+ // // Indexed image.
+ // if (this.Quality <= 256)
+ // {
+ // int rowLength = imageWidth + 1;
+ // data = new byte[rowLength * imageHeight];
+
+ // Parallel.For(
+ // 0,
+ // imageHeight,
+ // Bootstrapper.Instance.ParallelOptions,
+ // 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++] = quantized.Pixels[(y * imageWidth) + x];
+ // if (y > 0)
+ // {
+ // data[dataOffset - 1] -= quantized.Pixels[((y - 1) * imageWidth) + x];
+ // }
+ // }
+ // });
+ // }
+ // else
+ // {
+ // // TrueColor image.
+ // data = new byte[(imageWidth * imageHeight * 4) + pixels.Height];
+
+ // int rowLength = (imageWidth * 4) + 1;
+
+ // Parallel.For(
+ // 0,
+ // imageHeight,
+ // Bootstrapper.Instance.ParallelOptions,
+ // y =>
+ // {
+ // byte compression = 0;
+ // if (y > 0)
+ // {
+ // compression = 2;
+ // }
+
+ // data[y * rowLength] = compression;
+
+ // for (int x = 0; x < imageWidth; x++)
+ // {
+ // byte[] color = pixels[x, y].ToBytes();
+
+ // // Calculate the offset for the new array.
+ // int dataOffset = (y * rowLength) + (x * 4) + 1;
+
+ // // Expected format
+ // data[dataOffset] = color[0];
+ // data[dataOffset + 1] = color[1];
+ // data[dataOffset + 2] = color[2];
+ // data[dataOffset + 3] = color[3];
+
+ // if (y > 0)
+ // {
+ // color = pixels[x, y - 1].ToBytes();
+
+ // data[dataOffset] -= color[0];
+ // data[dataOffset + 1] -= color[1];
+ // data[dataOffset + 2] -= color[2];
+ // data[dataOffset + 3] -= color[3];
+ // }
+ // }
+ // });
+ // }
+
+ // 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.
///