Browse Source

Begin greater png color format support. [skip ci]

Former-commit-id: ee12a350d066acd2580895f89d852ef3c579237b
Former-commit-id: cdce687f7966c694d232e7591667106fb9358918
Former-commit-id: 92ed20377ae53847bb43b2f1f8a6d88f74653f6f
af/merge-core
James Jackson-South 10 years ago
parent
commit
fca9b4d0b5
  1. 44
      src/ImageProcessorCore/Formats/Png/Filters/AverageFilter.cs
  2. 44
      src/ImageProcessorCore/Formats/Png/Filters/FilterType.cs
  3. 40
      src/ImageProcessorCore/Formats/Png/Filters/NoneFilter.cs
  4. 66
      src/ImageProcessorCore/Formats/Png/Filters/PaethFilter.cs
  5. 35
      src/ImageProcessorCore/Formats/Png/Filters/SubFilter.cs
  6. 35
      src/ImageProcessorCore/Formats/Png/Filters/UpFilter.cs
  7. 38
      src/ImageProcessorCore/Formats/Png/PngColorType.cs
  8. 5
      src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs
  9. 6
      src/ImageProcessorCore/Formats/Png/PngEncoder.cs
  10. 410
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

44
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));
}
}
}

44
src/ImageProcessorCore/Formats/Png/Filters/FilterType.cs

@ -0,0 +1,44 @@
// <copyright file="FilterType.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// Provides enumeration of the various png filter types.
/// </summary>
internal enum FilterType
{
/// <summary>
/// With the None filter, the scanline is transmitted unmodified; it is only necessary to
/// insert a filter type byte before the data.
/// </summary>
None = 0,
/// <summary>
/// The Sub filter transmits the difference between each byte and the value of the corresponding
/// byte of the prior pixel.
/// </summary>
Sub = 1,
/// <summary>
/// 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.
/// </summary>
Up = 2,
/// <summary>
/// The Average filter uses the average of the two neighboring pixels (left and above) to
/// predict the value of a pixel.
/// </summary>
Average = 3,
/// <summary>
/// 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
/// </summary>
Paeth = 4
}
}

40
src/ImageProcessorCore/Formats/Png/Filters/NoneFilter.cs

@ -0,0 +1,40 @@
// <copyright file="NoneFilter.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// The None filter, the scanline is transmitted unmodified; it is only necessary to
/// insert a filter type byte before the data.
/// </summary>
internal static class NoneFilter
{
/// <summary>
/// Decodes the scanline
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline)
{
// No change required.
return scanline;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <returns>The <see cref="T:byte[]"/></returns>
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;
}
}
}

66
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;
}
}
}
}
}

35
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;
}
}
}

35
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;
}
}
}

38
src/ImageProcessorCore/Formats/Png/PngColorType.cs

@ -0,0 +1,38 @@
// <copyright file="PngColorType.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore.Formats
{
/// <summary>
/// Provides enumeration of available png color types.
/// </summary>
public enum PngColorType
{
/// <summary>
/// Each pixel is a grayscale sample.
/// </summary>
Grayscale = 0,
/// <summary>
/// Each pixel is an R,G,B triple.
/// </summary>
Rgb = 2,
/// <summary>
/// Each pixel is a palette index; a PLTE chunk must appear.
/// </summary>
Palette = 3,
/// <summary>
/// Each pixel is a grayscale sample, followed by an alpha sample.
/// </summary>
GrayscaleWithAlpha = 4,
/// <summary>
/// Each pixel is an R,G,B triple, followed by an alpha sample.
/// </summary>
RgbWithAlpha = 6
}
}

5
src/ImageProcessorCore/Formats/Png/PngDecoderCore.cs

@ -22,11 +22,6 @@ namespace ImageProcessorCore.Formats
private static readonly Dictionary<int, PngColorTypeInformation> ColorTypes private static readonly Dictionary<int, PngColorTypeInformation> ColorTypes
= new Dictionary<int, PngColorTypeInformation>(); = new Dictionary<int, PngColorTypeInformation>();
/// <summary>
/// The image to decode.
/// </summary>
//private IImage currentImage;
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
/// </summary> /// </summary>

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

@ -20,6 +20,11 @@ namespace ImageProcessorCore.Formats
/// </summary> /// </summary>
public int Quality { get; set; } public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <inheritdoc/> /// <inheritdoc/>
public string MimeType => "image/png"; public string MimeType => "image/png";
@ -76,6 +81,7 @@ namespace ImageProcessorCore.Formats
CompressionLevel = this.CompressionLevel, CompressionLevel = this.CompressionLevel,
Gamma = this.Gamma, Gamma = this.Gamma,
Quality = this.Quality, Quality = this.Quality,
PngColorType = PngColorType,
Quantizer = this.Quantizer, Quantizer = this.Quantizer,
WriteGamma = this.WriteGamma, WriteGamma = this.WriteGamma,
Threshold = this.Threshold Threshold = this.Threshold

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

@ -6,6 +6,7 @@
namespace ImageProcessorCore.Formats namespace ImageProcessorCore.Formats
{ {
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -22,6 +23,21 @@ namespace ImageProcessorCore.Formats
/// </summary> /// </summary>
private const int MaxBlockSize = 65535; private const int MaxBlockSize = 65535;
/// <summary>
/// Contains the raw pixel data from the image.
/// </summary>
byte[] pixelData;
/// <summary>
/// The image width.
/// </summary>
private int width;
/// <summary>
/// The image height.
/// </summary>
private int height;
/// <summary> /// <summary>
/// The number of bits required to encode the colors in the png. /// The number of bits required to encode the colors in the png.
/// </summary> /// </summary>
@ -32,6 +48,11 @@ namespace ImageProcessorCore.Formats
/// </summary> /// </summary>
public int Quality { get; set; } public int Quality { get; set; }
/// <summary>
/// Gets or sets the png color type
/// </summary>
public PngColorType PngColorType { get; set; }
/// <summary> /// <summary>
/// The compression level 1-9. /// The compression level 1-9.
/// <remarks>Defaults to 6.</remarks> /// <remarks>Defaults to 6.</remarks>
@ -76,6 +97,9 @@ namespace ImageProcessorCore.Formats
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
this.width = image.Width;
this.height = image.Height;
// Write the png header. // Write the png header.
stream.Write( stream.Write(
new byte[] new byte[]
@ -96,6 +120,13 @@ namespace ImageProcessorCore.Formats
int quality = this.Quality > 0 ? this.Quality : image.Quality; int quality = this.Quality > 0 ? this.Quality : image.Quality;
this.Quality = quality > 0 ? quality.Clamp(1, int.MaxValue) : int.MaxValue; 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 this.bitDepth = this.Quality <= 256
? (byte)(ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8)) ? (byte)(ImageMaths.GetBitsNeededForColorDepth(this.Quality).Clamp(1, 8))
: (byte)8; : (byte)8;
@ -123,19 +154,166 @@ namespace ImageProcessorCore.Formats
}; };
this.WriteHeaderChunk(stream, header); this.WriteHeaderChunk(stream, header);
QuantizedImage<T, TP> quantized = this.WritePaletteChunk(stream, header, image);
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
using (IPixelAccessor<T, TP> pixels = image.Lock()) if (this.Quality <= 256)
{ {
this.WriteDataChunks(stream, pixels, quantized); // Quatize the image and get the pixels
QuantizedImage<T, TP> 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<T, TP> 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<T, TP> pixels = image.Lock())
//{
// this.WriteDataChunks(stream, pixels, quantized);
//}
this.WriteDataChunks(stream);
this.WriteEndChunk(stream); this.WriteEndChunk(stream);
stream.Flush(); stream.Flush();
} }
private byte[] EncodePixelData()
{
List<byte[]> filteredScanlines = new List<byte[]>();
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<byte> result = new List<byte>();
foreach (var encodedScanline in filteredScanlines)
{
result.AddRange(encodedScanline);
}
return result.ToArray();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="rawScanline"></param>
/// <param name="previousScanline"></param>
/// <param name="bytesPerPixel"></param>
/// <returns></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerPixel)
{
List<Tuple<byte[], int>> candidates = new List<Tuple<byte[], int>>();
byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel);
candidates.Add(new Tuple<byte[], int>(sub, CalculateTotalVariation(sub)));
byte[] up = UpFilter.Encode(rawScanline, previousScanline);
candidates.Add(new Tuple<byte[], int>(up, CalculateTotalVariation(up)));
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
candidates.Add(new Tuple<byte[], int>(average, CalculateTotalVariation(average)));
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
candidates.Add(new Tuple<byte[], int>(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;
}
/// <summary>
/// Calculates the total variation of given byte array. Total variation is the sum of the absolute values of
/// neighbour differences.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
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;
}
}
/// <summary> /// <summary>
/// Writes an integer to the byte array. /// Writes an integer to the byte array.
/// </summary> /// </summary>
@ -313,94 +491,9 @@ namespace ImageProcessorCore.Formats
/// <summary> /// <summary>
/// Writes the pixel information to the stream. /// Writes the pixel information to the stream.
/// </summary> /// </summary>
/// <typeparam name="T">The pixel format.</typeparam> private void WriteDataChunks(Stream stream)
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="pixels">The image pixels.</param>
/// <param name="quantized">The quantized image.</param>
private void WriteDataChunks<T, TP>(Stream stream, IPixelAccessor<T, TP> pixels, QuantizedImage<T, TP> quantized)
where T : IPackedVector<TP>
where TP : struct
{ {
byte[] data; byte[] data = this.EncodePixelData();
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; byte[] buffer;
int bufferLength; int bufferLength;
@ -443,6 +536,139 @@ namespace ImageProcessorCore.Formats
} }
} }
///// <summary>
///// Writes the pixel information to the stream.
///// </summary>
///// <typeparam name="T">The pixel format.</typeparam>
///// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
///// <param name="stream">The <see cref="Stream"/> containing image data.</param>
///// <param name="pixels">The image pixels.</param>
///// <param name="quantized">The quantized image.</param>
//private void WriteDataChunks<T, TP>(Stream stream, IPixelAccessor<T, TP> pixels, QuantizedImage<T, TP> quantized)
// where T : IPackedVector<TP>
// 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);
// }
//}
/// <summary> /// <summary>
/// Writes the chunk end to the stream. /// Writes the chunk end to the stream.
/// </summary> /// </summary>

Loading…
Cancel
Save