Browse Source

Png encodes moar types [skip ci]

Former-commit-id: ff132f190934d317f9736ee236502d63a0d4531d
Former-commit-id: b5bc0988e86d58d6ecd39c9078d1f3f9b7c8b819
Former-commit-id: c4c5599022e0cb862cb78f7d81c5105f9c66a06c
af/merge-core
James Jackson-South 10 years ago
parent
commit
0cf20f91cd
  1. 40
      src/ImageProcessorCore/Formats/Png/Filters/AverageFilter.cs
  2. 1
      src/ImageProcessorCore/Formats/Png/Filters/FilterType.cs
  3. 1
      src/ImageProcessorCore/Formats/Png/Filters/NoneFilter.cs
  4. 75
      src/ImageProcessorCore/Formats/Png/Filters/PaethFilter.cs
  5. 29
      src/ImageProcessorCore/Formats/Png/Filters/SubFilter.cs
  6. 29
      src/ImageProcessorCore/Formats/Png/Filters/UpFilter.cs
  7. 4
      src/ImageProcessorCore/Formats/Png/PngColorType.cs
  8. 325
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

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

@ -1,11 +1,31 @@
namespace ImageProcessorCore.Formats
// <copyright file="AverageFilter.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;
/// <summary>
/// The Average filter uses the average of the two neighboring pixels (left and above) to predict
/// the value of a pixel.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary>
internal static class AverageFilter
{
/// <summary>
/// Decodes the scanline
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>
/// The <see cref="T:byte[]"/>
/// </returns>
public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
byte[] result = new byte[scanline.Length];
for (int x = 1; x < scanline.Length; x++)
@ -19,9 +39,17 @@
return result;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
var encodedScanline = new byte[scanline.Length + 1];
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
byte[] encodedScanline = new byte[scanline.Length + 1];
encodedScanline[0] = (byte)FilterType.Average;
@ -36,9 +64,15 @@
return encodedScanline;
}
/// <summary>
/// Calculates the average value of two bytes
/// </summary>
/// <param name="left">The left byte</param>
/// <param name="above">The above byte</param>
/// <returns>The <see cref="int"/></returns>
private static int Average(byte left, byte above)
{
return Convert.ToInt32(Math.Floor((left + above) / 2.0));
return Convert.ToInt32(Math.Floor((left + above) / 2.0D));
}
}
}

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

@ -7,6 +7,7 @@ namespace ImageProcessorCore.Formats
{
/// <summary>
/// Provides enumeration of the various png filter types.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary>
internal enum FilterType
{

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

@ -8,6 +8,7 @@ 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.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary>
internal static class NoneFilter
{

75
src/ImageProcessorCore/Formats/Png/Filters/PaethFilter.cs

@ -1,11 +1,30 @@
namespace ImageProcessorCore.Formats
// <copyright file="PaethFilter.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;
/// <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.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary>
internal static class PaethFilter
{
/// <summary>
/// Decodes the scanline
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
// Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
byte[] result = new byte[scanline.Length];
for (int x = 1; x < scanline.Length; x++)
@ -14,16 +33,23 @@
byte above = previousScanline[x];
byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : previousScanline[x - bytesPerPixel];
result[x] = (byte)((scanline[x] + PaethPredictor(left, above, upperLeft)) % 256);
result[x] = (byte)((scanline[x] + PaethPredicator(left, above, upperLeft)) % 256);
}
return result;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline, int bytesPerPixel)
{
var encodedScanline = new byte[scanline.Length + 1];
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
byte[] encodedScanline = new byte[scanline.Length + 1];
encodedScanline[0] = (byte)FilterType.Paeth;
for (int x = 0; x < scanline.Length; x++)
@ -32,35 +58,40 @@
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);
encodedScanline[x + 1] = (byte)((scanline[x] - PaethPredicator(left, above, upperLeft)) % 256);
}
return encodedScanline;
}
private static int PaethPredictor(int a, int b, int c)
/// <summary>
/// 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.
/// </summary>
/// <param name="left">The left neighbor pixel.</param>
/// <param name="above">The above neighbor pixel.</param>
/// <param name="upperLeft">The upper left neighbor pixel.</param>
/// <returns>
/// The <see cref="byte"/>.
/// </returns>
private static byte PaethPredicator(byte left, byte above, byte upperLeft)
{
int p = a + b - c;
int pa = Math.Abs(p - a);
int pb = Math.Abs(p - b);
int pc = Math.Abs(p - c);
int p = left + above - upperLeft;
int pa = Math.Abs(p - left);
int pb = Math.Abs(p - above);
int pc = Math.Abs(p - upperLeft);
if ((pa <= pb) && (pa <= pc))
if (pa <= pb && pa <= pc)
{
return a;
return left;
}
else
if (pb <= pc)
{
if (pb <= pc)
{
return b;
}
else
{
return c;
}
return above;
}
return upperLeft;
}
}
}

29
src/ImageProcessorCore/Formats/Png/Filters/SubFilter.cs

@ -1,9 +1,26 @@
namespace ImageProcessorCore.Formats
// <copyright file="SubFilter.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 Sub filter transmits the difference between each byte and the value of the corresponding byte
/// of the prior pixel.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary>
internal static class SubFilter
{
/// <summary>
/// Decodes the scanline
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline, int bytesPerPixel)
{
// Sub(x) + Raw(x-bpp)
byte[] result = new byte[scanline.Length];
for (int x = 1; x < scanline.Length; x++)
@ -16,10 +33,16 @@
return result;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, int bytesPerPixel)
{
var encodedScanline = new byte[scanline.Length + 1];
// Sub(x) = Raw(x) - Raw(x-bpp)
byte[] encodedScanline = new byte[scanline.Length + 1];
encodedScanline[0] = (byte)FilterType.Sub;
for (int x = 0; x < scanline.Length; x++)

29
src/ImageProcessorCore/Formats/Png/Filters/UpFilter.cs

@ -1,9 +1,26 @@
namespace ImageProcessorCore.Formats
// <copyright file="UpFilter.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 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.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary>
internal static class UpFilter
{
/// <summary>
/// Decodes the scanline
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Decode(byte[] scanline, byte[] previousScanline)
{
// Up(x) + Prior(x)
byte[] result = new byte[scanline.Length];
for (int x = 1; x < scanline.Length; x++)
@ -16,10 +33,16 @@
return result;
}
/// <summary>
/// Encodes the scanline
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
public static byte[] Encode(byte[] scanline, byte[] previousScanline)
{
var encodedScanline = new byte[scanline.Length + 1];
// Up(x) = Raw(x) - Prior(x)
byte[] encodedScanline = new byte[scanline.Length + 1];
encodedScanline[0] = (byte)FilterType.Up;
for (int x = 0; x < scanline.Length; x++)

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

@ -6,9 +6,9 @@
namespace ImageProcessorCore.Formats
{
/// <summary>
/// Provides enumeration of available png color types.
/// Provides enumeration of available PNG color types.
/// </summary>
public enum PngColorType
public enum PngColorType : byte
{
/// <summary>
/// Each pixel is a grayscale sample.

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

@ -43,6 +43,11 @@ namespace ImageProcessorCore.Formats
/// </summary>
private byte bitDepth;
/// <summary>
/// The number of bytes per pixel.
/// </summary>
private int bytesPerPixel;
/// <summary>
/// Gets or sets the quality of output for images.
/// </summary>
@ -120,7 +125,7 @@ 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.
// Set correct color type if the color count is 256 or less.
if (Quality <= 256)
{
this.PngColorType = PngColorType.Palette;
@ -141,12 +146,13 @@ namespace ImageProcessorCore.Formats
this.bitDepth = 8;
}
// TODO: Add more color options here.
this.bytesPerPixel = CalculateBytesPerPixel();
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.
ColorType = (byte)this.PngColorType,
BitDepth = this.bitDepth,
FilterMethod = 0, // None
CompressionMethod = 0,
@ -155,61 +161,139 @@ namespace ImageProcessorCore.Formats
this.WriteHeaderChunk(stream, header);
if (this.Quality <= 256)
// Collect the pixel data
if (this.PngColorType == PngColorType.Palette)
{
// Quatize the image and get the pixels
QuantizedImage<T, TP> quantized = this.WritePaletteChunk(stream, header, image);
pixelData = quantized.Pixels;
this.CollectIndexedBytes(image, stream, header);
}
else if (this.PngColorType == PngColorType.Grayscale || this.PngColorType == PngColorType.GrayscaleWithAlpha)
{
this.CollectGrayscaleBytes(image);
}
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.CollectColorBytes(image);
}
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);
stream.Flush();
}
/// <summary>
/// Collects the indexed pixel data.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>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<T, TP>(ImageBase<T, TP> image, Stream stream, PngHeader header)
where T : IPackedVector<TP>
where TP : struct
{
// Quatize the image and get the pixels
QuantizedImage<T, TP> quantized = this.WritePaletteChunk(stream, header, image);
pixelData = quantized.Pixels;
}
/// <summary>
/// Collects the grayscale pixel data.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="image">The image to encode.</param>
private void CollectGrayscaleBytes<T, TP>(ImageBase<T, TP> image)
where T : IPackedVector<TP>
where TP : struct
{
// Copy the pixels across from the image.
this.pixelData = new byte[this.width * this.height * this.bytesPerPixel];
int stride = this.width * this.bytesPerPixel;
using (IPixelAccessor<T, TP> pixels = image.Lock())
{
Parallel.For(
0,
this.height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
for (int x = 0; x < this.width; x++)
{
// Convert the color to YCbCr and store the luminance
// Optionally store the original color alpha.
int dataOffset = (y * stride) + (x * this.bytesPerPixel);
Color source = new Color(pixels[x, y].ToVector4());
YCbCr luminance = source;
for (int i = 0; i < this.bytesPerPixel; i++)
{
if (i == 0)
{
this.pixelData[dataOffset] = ((byte)luminance.Y).Clamp(0, 255);
}
else
{
this.pixelData[dataOffset + i] = source.A;
}
}
}
});
}
}
/// <summary>
/// Collects the true color pixel data.
/// </summary>
/// <typeparam name="T">The pixel format.</typeparam>
/// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam>
/// <param name="image">The image to encode.</param>
private void CollectColorBytes<T, TP>(ImageBase<T, TP> image)
where T : IPackedVector<TP>
where TP : struct
{
// Copy the pixels across from the image.
this.pixelData = new byte[this.width * this.height * this.bytesPerPixel];
int stride = this.width * this.bytesPerPixel;
using (IPixelAccessor<T, TP> pixels = image.Lock())
{
Parallel.For(
0,
this.height,
Bootstrapper.Instance.ParallelOptions,
y =>
{
// Color data is stored in r -> g -> b -> a order
for (int x = 0; x < this.width; x++)
{
int dataOffset = (y * stride) + (x * this.bytesPerPixel);
byte[] source = pixels[x, y].ToBytes();
for (int i = 0; i < this.bytesPerPixel; i++)
{
this.pixelData[dataOffset + i] = source[i];
}
}
});
}
}
/// <summary>
/// Encodes the pixel data line by line.
/// Each scanline is encoded in the most optimal manner to improve compression.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] EncodePixelData()
{
List<byte[]> filteredScanlines = new List<byte[]>();
int bytesPerPixel = CalculateBytesPerPixel();
byte[] previousScanline = new byte[width * bytesPerPixel];
byte[] previousScanline = new byte[width * this.bytesPerPixel];
for (int y = 0; y < height; y++)
{
byte[] rawScanline = GetRawScanline(y);
byte[] filteredScanline = GetOptimalFilteredScanline(rawScanline, previousScanline, bytesPerPixel);
byte[] filteredScanline = GetOptimalFilteredScanline(rawScanline, previousScanline, this.bytesPerPixel);
filteredScanlines.Add(filteredScanline);
@ -230,24 +314,24 @@ namespace ImageProcessorCore.Formats
/// 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>
/// <param name="rawScanline">The raw scanline</param>
/// <param name="previousScanline">The previous scanline</param>
/// <param name="byteCount">The number of bytes per pixel</param>
/// <returns></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int bytesPerPixel)
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, int byteCount)
{
List<Tuple<byte[], int>> candidates = new List<Tuple<byte[], int>>();
byte[] sub = SubFilter.Encode(rawScanline, bytesPerPixel);
byte[] sub = SubFilter.Encode(rawScanline, byteCount);
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);
byte[] average = AverageFilter.Encode(rawScanline, previousScanline, byteCount);
candidates.Add(new Tuple<byte[], int>(average, CalculateTotalVariation(average)));
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, bytesPerPixel);
byte[] paeth = PaethFilter.Encode(rawScanline, previousScanline, byteCount);
candidates.Add(new Tuple<byte[], int>(paeth, CalculateTotalVariation(paeth)));
int lowestTotalVariation = int.MaxValue;
@ -266,11 +350,11 @@ namespace ImageProcessorCore.Formats
}
/// <summary>
/// Calculates the total variation of given byte array. Total variation is the sum of the absolute values of
/// 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>
/// <param name="input">The scanline bytes</param>
/// <returns>The <see cref="int"/></returns>
private int CalculateTotalVariation(byte[] input)
{
int totalVariation = 0;
@ -283,15 +367,23 @@ namespace ImageProcessorCore.Formats
return totalVariation;
}
/// <summary>
/// Get the raw scanline data from the pixel data
/// </summary>
/// <param name="y">The row number</param>
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] GetRawScanline(int y)
{
// TODO: This should vary by bytes per pixel.
int stride = (this.PngColorType == PngColorType.Palette ? 1 : 4) * this.width;
int stride = this.bytesPerPixel * this.width;
byte[] rawScanline = new byte[stride];
Array.Copy(this.pixelData, y * stride, rawScanline, 0, stride);
return rawScanline;
}
/// <summary>
/// Calculates the correct number of bytes per pixel for the given color type.
/// </summary>
/// <returns>The <see cref="int"/></returns>
private int CalculateBytesPerPixel()
{
switch (this.PngColorType)
@ -309,6 +401,8 @@ namespace ImageProcessorCore.Formats
return 3;
// PngColorType.RgbWithAlpha
// TODO: Maybe figure out a way to detect if there are any transparent
// pixels and encode RGB if none.
default:
return 4;
}
@ -536,139 +630,6 @@ 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>
/// Writes the chunk end to the stream.
/// </summary>

Loading…
Cancel
Save