Browse Source

Merge pull request #212 from JimBobSquarePants/james/png-bufferspan

Use Bufferspan + Unsafe.Add for png filters
pull/213/head
James Jackson-South 9 years ago
committed by GitHub
parent
commit
cfacce657b
  1. 38
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  2. 64
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  3. 17
      src/ImageSharp/Formats/Png/Filters/NoneFilter.cs
  4. 69
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  5. 48
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  6. 47
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  7. 78
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  8. 6
      src/ImageSharp/Formats/Png/PngEncoder.cs
  9. 151
      src/ImageSharp/Formats/Png/PngEncoderCore.cs

38
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -159,5 +159,43 @@ namespace ImageSharp
throw new ArgumentException(message, parameterName); throw new ArgumentException(message, parameterName);
} }
} }
/// <summary>
/// Verifies, that the target span is of same size than the 'other' span.
/// </summary>
/// <typeparam name="T">The element type of the spans</typeparam>
/// <param name="target">The target span.</param>
/// <param name="other">The 'other' span to compare 'target' to.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is true
/// </exception>
public static void MustBeSameSized<T>(BufferSpan<T> target, BufferSpan<T> other, string parameterName)
where T : struct
{
if (target.Length != other.Length)
{
throw new ArgumentException("Span-s must be the same size!", parameterName);
}
}
/// <summary>
/// Verifies, that the `target` span has the length of 'minSpan', or longer.
/// </summary>
/// <typeparam name="T">The element type of the spans</typeparam>
/// <param name="target">The target span.</param>
/// <param name="minSpan">The 'minSpan' span to compare 'target' to.</param>
/// <param name="parameterName">The name of the parameter that is to be checked.</param>
/// <exception cref="ArgumentException">
/// <paramref name="target"/> is true
/// </exception>
public static void MustBeSizedAtLeast<T>(BufferSpan<T> target, BufferSpan<T> minSpan, string parameterName)
where T : struct
{
if (target.Length < minSpan.Length)
{
throw new ArgumentException($"Span-s must be at least of length {minSpan.Length}!", parameterName);
}
}
} }
} }

64
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -12,28 +12,37 @@ namespace ImageSharp.Formats
/// the value of a pixel. /// the value of a pixel.
/// <see href="https://www.w3.org/TR/PNG-Filters.html"/> /// <see href="https://www.w3.org/TR/PNG-Filters.html"/>
/// </summary> /// </summary>
internal static unsafe class AverageFilter internal static class AverageFilter
{ {
/// <summary> /// <summary>
/// Decodes the scanline /// Decodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) public static void Decode(BufferSpan<byte> scanline, BufferSpan<byte> previousScanline, int bytesPerPixel)
{ {
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference();
// Average(x) + floor((Raw(x-bpp)+Prior(x))/2) // Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
fixed (byte* scan = scanline) for (int x = 1; x < scanline.Length; x++)
fixed (byte* prev = previousScanline)
{ {
for (int x = 1; x < bytesPerScanline; x++) if (x - bytesPerPixel < 1)
{ {
byte left = (x - bytesPerPixel < 1) ? (byte)0 : scan[x - bytesPerPixel]; ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
byte above = prev[x]; byte above = Unsafe.Add(ref prevBaseRef, x);
scan = (byte)((scan + (above >> 1)) % 256);
scan[x] = (byte)((scan[x] + Average(left, above)) % 256); }
else
{
ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel);
byte above = Unsafe.Add(ref prevBaseRef, x);
scan = (byte)((scan + Average(left, above)) % 256);
} }
} }
} }
@ -46,21 +55,34 @@ namespace ImageSharp.Formats
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) public static void Encode(BufferSpan<byte> scanline, BufferSpan<byte> previousScanline, BufferSpan<byte> result, int bytesPerPixel)
{ {
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference();
ref byte resultBaseRef = ref result.DangerousGetPinnableReference();
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
fixed (byte* scan = scanline) resultBaseRef = 3;
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
res[0] = 3;
for (int x = 0; x < scanline.Length; x++) for (int x = 0; x < scanline.Length; x++)
{
if (x - bytesPerPixel < 0)
{ {
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = prev[x]; byte above = Unsafe.Add(ref prevBaseRef, x);
ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256); res = (byte)((scan - (above >> 1)) % 256);
}
else
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel);
byte above = Unsafe.Add(ref prevBaseRef, x);
ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res = (byte)((scan - Average(left, above)) % 256);
} }
} }
} }

17
src/ImageSharp/Formats/Png/Filters/NoneFilter.cs

@ -15,29 +15,18 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
internal static class NoneFilter internal static class NoneFilter
{ {
/// <summary>
/// Decodes the scanline
/// </summary>
/// <param name="scanline">The scanline to decode</param>
/// <returns>The <see cref="T:byte[]"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] Decode(byte[] scanline)
{
// No change required.
return scanline;
}
/// <summary> /// <summary>
/// Encodes the scanline /// Encodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to encode</param> /// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] result) public static void Encode(BufferSpan<byte> scanline, BufferSpan<byte> result)
{ {
// Insert a byte before the data. // Insert a byte before the data.
result[0] = 0; result[0] = 0;
Buffer.BlockCopy(scanline, 0, result, 1, scanline.Length); result = result.Slice(1);
BufferSpan.Copy(scanline, result);
} }
} }
} }

69
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -5,7 +5,6 @@
namespace ImageSharp.Formats namespace ImageSharp.Formats
{ {
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
/// <summary> /// <summary>
@ -21,22 +20,31 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline, int bytesPerPixel) public static void Decode(BufferSpan<byte> scanline, BufferSpan<byte> previousScanline, int bytesPerPixel)
{ {
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference();
// Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
fixed (byte* scan = scanline) for (int x = 1; x < scanline.Length; x++)
fixed (byte* prev = previousScanline)
{ {
for (int x = 1; x < bytesPerScanline; x++) if (x - bytesPerPixel < 1)
{ {
byte left = (x - bytesPerPixel < 1) ? (byte)0 : scan[x - bytesPerPixel]; ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
byte above = prev[x]; byte above = Unsafe.Add(ref prevBaseRef, x);
byte upperLeft = (x - bytesPerPixel < 1) ? (byte)0 : prev[x - bytesPerPixel]; scan = (byte)((scan + PaethPredicator(0, above, 0)) % 256);
}
scan[x] = (byte)((scan[x] + PaethPredicator(left, above, upperLeft)) % 256); else
{
ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel);
byte above = Unsafe.Add(ref prevBaseRef, x);
byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel);
scan = (byte)((scan + PaethPredicator(left, above, upperLeft)) % 256);
} }
} }
} }
@ -49,22 +57,35 @@ namespace ImageSharp.Formats
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel) public static void Encode(BufferSpan<byte> scanline, BufferSpan<byte> previousScanline, BufferSpan<byte> result, int bytesPerPixel)
{ {
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference();
ref byte resultBaseRef = ref result.DangerousGetPinnableReference();
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
fixed (byte* scan = scanline) resultBaseRef = 4;
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
res[0] = 4;
for (int x = 0; x < scanline.Length; x++) for (int x = 0; x < scanline.Length; x++)
{
if (x - bytesPerPixel < 0)
{ {
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = prev[x]; byte above = Unsafe.Add(ref prevBaseRef, x);
byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : prev[x - bytesPerPixel]; ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res = (byte)((scan - PaethPredicator(0, above, 0)) % 256);
res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256); }
else
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel);
byte above = Unsafe.Add(ref prevBaseRef, x);
byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel);
ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res = (byte)((scan - PaethPredicator(left, above, upperLeft)) % 256);
} }
} }
} }
@ -100,4 +121,4 @@ namespace ImageSharp.Formats
return upperLeft; return upperLeft;
} }
} }
} }

48
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -18,18 +18,25 @@ namespace ImageSharp.Formats
/// Decodes the scanline /// Decodes the scanline
/// </summary> /// </summary>
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, int bytesPerScanline, int bytesPerPixel) public static void Decode(BufferSpan<byte> scanline, int bytesPerPixel)
{ {
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
// Sub(x) + Raw(x-bpp) // Sub(x) + Raw(x-bpp)
fixed (byte* scan = scanline) for (int x = 1; x < scanline.Length; x++)
{ {
for (int x = 1; x < bytesPerScanline; x++) if (x - bytesPerPixel < 1)
{
ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
scan = (byte)(scan % 256);
}
else
{ {
byte priorRawByte = (x - bytesPerPixel < 1) ? (byte)0 : scan[x - bytesPerPixel]; ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
scan[x] = (byte)((scan[x] + priorRawByte) % 256); byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel);
scan = (byte)((scan + prev) % 256);
} }
} }
} }
@ -41,19 +48,30 @@ namespace ImageSharp.Formats
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel) public static void Encode(BufferSpan<byte> scanline, BufferSpan<byte> result, int bytesPerPixel)
{ {
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
ref byte resultBaseRef = ref result.DangerousGetPinnableReference();
// Sub(x) = Raw(x) - Raw(x-bpp) // Sub(x) = Raw(x) - Raw(x-bpp)
fixed (byte* scan = scanline) resultBaseRef = 1;
fixed (byte* res = result)
{
res[0] = 1;
for (int x = 0; x < scanline.Length; x++) for (int x = 0; x < scanline.Length; x++)
{
if (x - bytesPerPixel < 0)
{ {
byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; byte scan = Unsafe.Add(ref scanBaseRef, x);
ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256); res = (byte)(scan % 256);
}
else
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel);
ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res = (byte)((scan - prev) % 256);
} }
} }
} }

47
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -19,20 +19,20 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
/// <param name="scanline">The scanline to decode</param> /// <param name="scanline">The scanline to decode</param>
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(byte[] scanline, byte[] previousScanline, int bytesPerScanline) public static void Decode(BufferSpan<byte> scanline, BufferSpan<byte> previousScanline)
{ {
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference();
// Up(x) + Prior(x) // Up(x) + Prior(x)
fixed (byte* scan = scanline) for (int x = 1; x < scanline.Length; x++)
fixed (byte* prev = previousScanline)
{ {
for (int x = 1; x < bytesPerScanline; x++) ref byte scan = ref Unsafe.Add(ref scanBaseRef, x);
{ byte above = Unsafe.Add(ref prevBaseRef, x);
byte above = prev[x]; scan = (byte)((scan + above) % 256);
scan[x] = (byte)((scan[x] + above) % 256);
}
} }
} }
@ -43,21 +43,24 @@ namespace ImageSharp.Formats
/// <param name="previousScanline">The previous scanline.</param> /// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param> /// <param name="result">The filtered scanline result.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result) public static void Encode(BufferSpan<byte> scanline, BufferSpan<byte> previousScanline, BufferSpan<byte> result)
{ {
// Up(x) = Raw(x) - Prior(x) DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
fixed (byte* scan = scanline) DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));
fixed (byte* prev = previousScanline)
fixed (byte* res = result) ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
{ ref byte prevBaseRef = ref previousScanline.DangerousGetPinnableReference();
res[0] = 2; ref byte resultBaseRef = ref result.DangerousGetPinnableReference();
for (int x = 0; x < scanline.Length; x++) // Up(x) = Raw(x) - Prior(x)
{ resultBaseRef = 2;
byte above = prev[x];
res[x + 1] = (byte)((scan[x] - above) % 256); for (int x = 0; x < scanline.Length; x++)
} {
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);
ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1);
res = (byte)((scan - above) % 256);
} }
} }
} }

78
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -131,12 +131,12 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Previous scanline processed /// Previous scanline processed
/// </summary> /// </summary>
private byte[] previousScanline; private Buffer<byte> previousScanline;
/// <summary> /// <summary>
/// The current scanline that is being processed /// The current scanline that is being processed
/// </summary> /// </summary>
private byte[] scanline; private Buffer<byte> scanline;
/// <summary> /// <summary>
/// The index of the current scanline being processed /// The index of the current scanline being processed
@ -184,14 +184,14 @@ namespace ImageSharp.Formats
public Image<TPixel> Decode<TPixel>(Stream stream) public Image<TPixel> Decode<TPixel>(Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
ImageMetaData metadata = new ImageMetaData(); var metadata = new ImageMetaData();
this.currentStream = stream; this.currentStream = stream;
this.currentStream.Skip(8); this.currentStream.Skip(8);
Image<TPixel> image = null; Image<TPixel> image = null;
PixelAccessor<TPixel> pixels = null; PixelAccessor<TPixel> pixels = null;
try try
{ {
using (ZlibInflateStream deframeStream = new ZlibInflateStream(this.currentStream)) using (var deframeStream = new ZlibInflateStream(this.currentStream))
{ {
PngChunk currentChunk; PngChunk currentChunk;
while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null) while (!this.isEndChunkReached && (currentChunk = this.ReadChunk()) != null)
@ -252,11 +252,8 @@ namespace ImageSharp.Formats
finally finally
{ {
pixels?.Dispose(); pixels?.Dispose();
if (this.previousScanline != null) this.scanline?.Dispose();
{ this.previousScanline?.Dispose();
ArrayPool<byte>.Shared.Return(this.previousScanline);
ArrayPool<byte>.Shared.Return(this.scanline);
}
} }
} }
@ -345,12 +342,8 @@ namespace ImageSharp.Formats
this.bytesPerSample = this.header.BitDepth / 8; this.bytesPerSample = this.header.BitDepth / 8;
} }
this.previousScanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline); this.previousScanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
this.scanline = ArrayPool<byte>.Shared.Rent(this.bytesPerScanline); this.scanline = Buffer<byte>.CreateClean(this.bytesPerScanline);
// Zero out the scanlines, because the bytes that are rented from the arraypool may not be zero.
Array.Clear(this.scanline, 0, this.bytesPerScanline);
Array.Clear(this.previousScanline, 0, this.bytesPerScanline);
} }
/// <summary> /// <summary>
@ -429,7 +422,7 @@ namespace ImageSharp.Formats
{ {
while (this.currentRow < this.header.Height) while (this.currentRow < this.header.Height)
{ {
int bytesRead = compressedStream.Read(this.scanline, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead; this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < this.bytesPerScanline) if (this.currentRowBytesRead < this.bytesPerScanline)
{ {
@ -437,45 +430,38 @@ namespace ImageSharp.Formats
} }
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
FilterType filterType = (FilterType)this.scanline[0]; var filterType = (FilterType)this.scanline[0];
switch (filterType) switch (filterType)
{ {
case FilterType.None: case FilterType.None:
NoneFilter.Decode(this.scanline);
break; break;
case FilterType.Sub: case FilterType.Sub:
SubFilter.Decode(this.scanline, this.bytesPerScanline, this.bytesPerPixel); SubFilter.Decode(this.scanline, this.bytesPerPixel);
break; break;
case FilterType.Up: case FilterType.Up:
UpFilter.Decode(this.scanline, this.previousScanline, this.bytesPerScanline); UpFilter.Decode(this.scanline, this.previousScanline);
break; break;
case FilterType.Average: case FilterType.Average:
AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerScanline, this.bytesPerPixel); AverageFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel);
break; break;
case FilterType.Paeth: case FilterType.Paeth:
PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerScanline, this.bytesPerPixel); PaethFilter.Decode(this.scanline, this.previousScanline, this.bytesPerPixel);
break; break;
default: default:
throw new ImageFormatException("Unknown filter type."); throw new ImageFormatException("Unknown filter type.");
} }
this.ProcessDefilteredScanline(this.scanline, pixels); this.ProcessDefilteredScanline(this.scanline.Array, pixels);
Swap(ref this.scanline, ref this.previousScanline); Swap(ref this.scanline, ref this.previousScanline);
this.currentRow++; this.currentRow++;
@ -508,7 +494,7 @@ namespace ImageSharp.Formats
while (this.currentRow < this.header.Height) while (this.currentRow < this.header.Height)
{ {
int bytesRead = compressedStream.Read(this.scanline, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead; this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < bytesPerInterlaceScanline) if (this.currentRowBytesRead < bytesPerInterlaceScanline)
{ {
@ -517,45 +503,40 @@ namespace ImageSharp.Formats
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
FilterType filterType = (FilterType)this.scanline[0]; BufferSpan<byte> scanSpan = this.scanline.Slice(0, bytesPerInterlaceScanline);
BufferSpan<byte> prevSpan = this.previousScanline.Span.Slice(0, bytesPerInterlaceScanline);
var filterType = (FilterType)scanSpan[0];
switch (filterType) switch (filterType)
{ {
case FilterType.None: case FilterType.None:
NoneFilter.Decode(this.scanline);
break; break;
case FilterType.Sub: case FilterType.Sub:
SubFilter.Decode(this.scanline, bytesPerInterlaceScanline, this.bytesPerPixel); SubFilter.Decode(scanSpan, this.bytesPerPixel);
break; break;
case FilterType.Up: case FilterType.Up:
UpFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline); UpFilter.Decode(scanSpan, prevSpan);
break; break;
case FilterType.Average: case FilterType.Average:
AverageFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); AverageFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel);
break; break;
case FilterType.Paeth: case FilterType.Paeth:
PaethFilter.Decode(this.scanline, this.previousScanline, bytesPerInterlaceScanline, this.bytesPerPixel); PaethFilter.Decode(scanSpan, prevSpan, this.bytesPerPixel);
break; break;
default: default:
throw new ImageFormatException("Unknown filter type."); throw new ImageFormatException("Unknown filter type.");
} }
this.ProcessInterlacedDefilteredScanline(this.scanline, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); this.ProcessInterlacedDefilteredScanline(this.scanline.Array, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]);
Swap(ref this.scanline, ref this.previousScanline); Swap(ref this.scanline, ref this.previousScanline);
@ -583,9 +564,10 @@ namespace ImageSharp.Formats
private void ProcessDefilteredScanline<TPixel>(byte[] defilteredScanline, PixelAccessor<TPixel> pixels) private void ProcessDefilteredScanline<TPixel>(byte[] defilteredScanline, PixelAccessor<TPixel> pixels)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel color = default(TPixel); var color = default(TPixel);
BufferSpan<TPixel> pixelBuffer = pixels.GetRowSpan(this.currentRow); BufferSpan<TPixel> pixelBuffer = pixels.GetRowSpan(this.currentRow);
BufferSpan<byte> scanlineBuffer = new BufferSpan<byte>(defilteredScanline, 1); var scanlineBuffer = new BufferSpan<byte>(defilteredScanline, 1);
switch (this.PngColorType) switch (this.PngColorType)
{ {
case PngColorType.Grayscale: case PngColorType.Grayscale:
@ -646,7 +628,7 @@ namespace ImageSharp.Formats
{ {
byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth);
byte[] palette = this.palette; byte[] palette = this.palette;
TPixel color = default(TPixel); var color = default(TPixel);
if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) if (this.paletteAlpha != null && this.paletteAlpha.Length > 0)
{ {
@ -703,7 +685,7 @@ namespace ImageSharp.Formats
private void ProcessInterlacedDefilteredScanline<TPixel>(byte[] defilteredScanline, int row, PixelAccessor<TPixel> pixels, int pixelOffset = 0, int increment = 1) private void ProcessInterlacedDefilteredScanline<TPixel>(byte[] defilteredScanline, int row, PixelAccessor<TPixel> pixels, int pixelOffset = 0, int increment = 1)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel color = default(TPixel); var color = default(TPixel);
switch (this.PngColorType) switch (this.PngColorType)
{ {
@ -901,7 +883,7 @@ namespace ImageSharp.Formats
/// </returns> /// </returns>
private PngChunk ReadChunk() private PngChunk ReadChunk()
{ {
PngChunk chunk = new PngChunk(); var chunk = new PngChunk();
this.ReadChunkLength(chunk); this.ReadChunkLength(chunk);
if (chunk.Length < 0) if (chunk.Length < 0)
{ {

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

@ -33,8 +33,10 @@ namespace ImageSharp.Formats
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IPngEncoderOptions options) public void Encode<TPixel>(Image<TPixel> image, Stream stream, IPngEncoderOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
PngEncoderCore encode = new PngEncoderCore(options); using (var encode = new PngEncoderCore(options))
encode.Encode(image, stream); {
encode.Encode(image, stream);
}
} }
} }
} }

151
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -9,7 +9,7 @@ namespace ImageSharp.Formats
using System.Buffers; using System.Buffers;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using ImageSharp.PixelFormats; using ImageSharp.PixelFormats;
using Quantizers; using Quantizers;
@ -19,7 +19,7 @@ namespace ImageSharp.Formats
/// <summary> /// <summary>
/// Performs the png encoding operation. /// Performs the png encoding operation.
/// </summary> /// </summary>
internal sealed class PngEncoderCore internal sealed class PngEncoderCore : IDisposable
{ {
/// <summary> /// <summary>
/// The maximum block size, defaults at 64k for uncompressed blocks. /// The maximum block size, defaults at 64k for uncompressed blocks.
@ -71,25 +71,45 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private int bytesPerPixel; private int bytesPerPixel;
/// <summary>
/// The number of bytes per scanline.
/// </summary>
private int bytesPerScanline;
/// <summary>
/// The previous scanline.
/// </summary>
private Buffer<byte> previousScanline;
/// <summary>
/// The raw scanline.
/// </summary>
private Buffer<byte> rawScanline;
/// <summary>
/// The filtered scanline result.
/// </summary>
private Buffer<byte> result;
/// <summary> /// <summary>
/// The buffer for the sub filter /// The buffer for the sub filter
/// </summary> /// </summary>
private byte[] sub; private Buffer<byte> sub;
/// <summary> /// <summary>
/// The buffer for the up filter /// The buffer for the up filter
/// </summary> /// </summary>
private byte[] up; private Buffer<byte> up;
/// <summary> /// <summary>
/// The buffer for the average filter /// The buffer for the average filter
/// </summary> /// </summary>
private byte[] average; private Buffer<byte> average;
/// <summary> /// <summary>
/// The buffer for the paeth filter /// The buffer for the paeth filter
/// </summary> /// </summary>
private byte[] paeth; private Buffer<byte> paeth;
/// <summary> /// <summary>
/// The quality of output for images. /// The quality of output for images.
@ -177,7 +197,7 @@ namespace ImageSharp.Formats
this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerPixel = this.CalculateBytesPerPixel();
PngHeader header = new PngHeader var header = new PngHeader
{ {
Width = image.Width, Width = image.Width,
Height = image.Height, Height = image.Height,
@ -207,6 +227,20 @@ namespace ImageSharp.Formats
stream.Flush(); stream.Flush();
} }
/// <summary>
/// Disposes PngEncoderCore instance, disposing it's internal buffers.
/// </summary>
public void Dispose()
{
this.previousScanline?.Dispose();
this.rawScanline?.Dispose();
this.result?.Dispose();
this.sub?.Dispose();
this.up?.Dispose();
this.average?.Dispose();
this.paeth?.Dispose();
}
/// <summary> /// <summary>
/// Writes an integer to the byte array. /// Writes an integer to the byte array.
/// </summary> /// </summary>
@ -268,10 +302,11 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixels accessor.</param> /// <param name="pixels">The image pixels accessor.</param>
/// <param name="row">The row index.</param> /// <param name="row">The row index.</param>
/// <param name="rawScanline">The raw scanline.</param> private void CollectGrayscaleBytes<TPixel>(PixelAccessor<TPixel> pixels, int row)
private void CollectGrayscaleBytes<TPixel>(PixelAccessor<TPixel> pixels, int row, byte[] rawScanline)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
byte[] rawScanlineArray = this.rawScanline.Array;
// Copy the pixels across from the image. // Copy the pixels across from the image.
// Reuse the chunk type buffer. // Reuse the chunk type buffer.
for (int x = 0; x < this.width; x++) for (int x = 0; x < this.width; x++)
@ -286,11 +321,11 @@ namespace ImageSharp.Formats
{ {
if (i == 0) if (i == 0)
{ {
rawScanline[offset] = luminance; rawScanlineArray[offset] = luminance;
} }
else else
{ {
rawScanline[offset + i] = this.chunkTypeBuffer[3]; rawScanlineArray[offset + i] = this.chunkTypeBuffer[3];
} }
} }
} }
@ -302,14 +337,18 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="pixels">The image pixel accessor.</param>
/// <param name="row">The row index.</param> /// <param name="row">The row index.</param>
/// <param name="rawScanline">The raw scanline.</param> private void CollecTPixelBytes<TPixel>(PixelAccessor<TPixel> pixels, int row)
private void CollecTPixelBytes<TPixel>(PixelAccessor<TPixel> pixels, int row, byte[] rawScanline)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory. BufferSpan<TPixel> rowSpan = pixels.GetRowSpan(row);
using (PixelArea<TPixel> pixelRow = new PixelArea<TPixel>(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz))
if (this.bytesPerPixel == 4)
{
PixelOperations<TPixel>.Instance.ToXyzwBytes(rowSpan, this.rawScanline, this.width);
}
else
{ {
pixels.CopyTo(pixelRow, row); PixelOperations<TPixel>.Instance.ToXyzBytes(rowSpan, this.rawScanline, this.width);
} }
} }
@ -320,81 +359,79 @@ namespace ImageSharp.Formats
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The image pixel accessor.</param> /// <param name="pixels">The image pixel accessor.</param>
/// <param name="row">The row.</param> /// <param name="row">The row.</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="rawScanline">The raw scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private byte[] EncodePixelRow<TPixel>(PixelAccessor<TPixel> pixels, int row, byte[] previousScanline, byte[] rawScanline, byte[] result) private Buffer<byte> EncodePixelRow<TPixel>(PixelAccessor<TPixel> pixels, int row)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
switch (this.pngColorType) switch (this.pngColorType)
{ {
case PngColorType.Palette: case PngColorType.Palette:
Buffer.BlockCopy(this.palettePixelData, row * rawScanline.Length, rawScanline, 0, rawScanline.Length); Buffer.BlockCopy(this.palettePixelData, row * this.rawScanline.Length, this.rawScanline.Array, 0, this.rawScanline.Length);
break; break;
case PngColorType.Grayscale: case PngColorType.Grayscale:
case PngColorType.GrayscaleWithAlpha: case PngColorType.GrayscaleWithAlpha:
this.CollectGrayscaleBytes(pixels, row, rawScanline); this.CollectGrayscaleBytes(pixels, row);
break; break;
default: default:
this.CollecTPixelBytes(pixels, row, rawScanline); this.CollecTPixelBytes(pixels, row);
break; break;
} }
return this.GetOptimalFilteredScanline(rawScanline, previousScanline, result); return this.GetOptimalFilteredScanline();
} }
/// <summary> /// <summary>
/// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed /// 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. /// to be most compressible, using lowest total variation as proxy for compressibility.
/// </summary> /// </summary>
/// <param name="rawScanline">The raw scanline</param>
/// <param name="previousScanline">The previous scanline</param>
/// <param name="result">The filtered scanline result.</param>
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) private Buffer<byte> GetOptimalFilteredScanline()
{ {
BufferSpan<byte> scanSpan = this.rawScanline.Span;
BufferSpan<byte> prevSpan = this.previousScanline.Span;
// Palette images don't compress well with adaptive filtering. // Palette images don't compress well with adaptive filtering.
if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8)
{ {
NoneFilter.Encode(rawScanline, result); NoneFilter.Encode(this.rawScanline, this.result);
return result; return this.result;
} }
// This order, while different to the enumerated order is more likely to produce a smaller sum // This order, while different to the enumerated order is more likely to produce a smaller sum
// early on which shaves a couple of milliseconds off the processing time. // early on which shaves a couple of milliseconds off the processing time.
UpFilter.Encode(rawScanline, previousScanline, this.up); UpFilter.Encode(scanSpan, prevSpan, this.up);
int currentSum = this.CalculateTotalVariation(this.up, int.MaxValue); int currentSum = this.CalculateTotalVariation(this.up, int.MaxValue);
int lowestSum = currentSum; int lowestSum = currentSum;
result = this.up; Buffer<byte> actualResult = this.up;
PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel); PaethFilter.Encode(scanSpan, prevSpan, this.paeth, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(this.paeth, currentSum); currentSum = this.CalculateTotalVariation(this.paeth, currentSum);
if (currentSum < lowestSum) if (currentSum < lowestSum)
{ {
lowestSum = currentSum; lowestSum = currentSum;
result = this.paeth; actualResult = this.paeth;
} }
SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel); SubFilter.Encode(scanSpan, this.sub, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(this.sub, int.MaxValue); currentSum = this.CalculateTotalVariation(this.sub, int.MaxValue);
if (currentSum < lowestSum) if (currentSum < lowestSum)
{ {
lowestSum = currentSum; lowestSum = currentSum;
result = this.sub; actualResult = this.sub;
} }
AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel); AverageFilter.Encode(scanSpan, prevSpan, this.average, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(this.average, currentSum); currentSum = this.CalculateTotalVariation(this.average, currentSum);
if (currentSum < lowestSum) if (currentSum < lowestSum)
{ {
result = this.average; actualResult = this.average;
} }
return result; return actualResult;
} }
/// <summary> /// <summary>
@ -404,17 +441,19 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline bytes</param> /// <param name="scanline">The scanline bytes</param>
/// <param name="lastSum">The last variation sum</param> /// <param name="lastSum">The last variation sum</param>
/// <returns>The <see cref="int"/></returns> /// <returns>The <see cref="int"/></returns>
private int CalculateTotalVariation(byte[] scanline, int lastSum) [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CalculateTotalVariation(BufferSpan<byte> scanline, int lastSum)
{ {
ref byte scanBaseRef = ref scanline.DangerousGetPinnableReference();
int sum = 0; int sum = 0;
for (int i = 1; i < scanline.Length; i++) for (int i = 1; i < this.bytesPerScanline; i++)
{ {
byte v = scanline[i]; byte v = Unsafe.Add(ref scanBaseRef, i);
sum += v < 128 ? v : 256 - v; sum += v < 128 ? v : 256 - v;
// No point continuing if we are larger. // No point continuing if we are larger.
if (sum > lastSum) if (sum >= lastSum)
{ {
break; break;
} }
@ -601,18 +640,19 @@ namespace ImageSharp.Formats
private void WriteDataChunks<TPixel>(PixelAccessor<TPixel> pixels, Stream stream) private void WriteDataChunks<TPixel>(PixelAccessor<TPixel> pixels, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
int bytesPerScanline = this.width * this.bytesPerPixel; this.bytesPerScanline = this.width * this.bytesPerPixel;
byte[] previousScanline = new byte[bytesPerScanline]; int resultLength = this.bytesPerScanline + 1;
byte[] rawScanline = new byte[bytesPerScanline];
int resultLength = bytesPerScanline + 1; this.previousScanline = new Buffer<byte>(this.bytesPerScanline);
byte[] result = new byte[resultLength]; this.rawScanline = new Buffer<byte>(this.bytesPerScanline);
this.result = new Buffer<byte>(resultLength);
if (this.pngColorType != PngColorType.Palette) if (this.pngColorType != PngColorType.Palette)
{ {
this.sub = new byte[resultLength]; this.sub = Buffer<byte>.CreateClean(resultLength);
this.up = new byte[resultLength]; this.up = Buffer<byte>.CreateClean(resultLength);
this.average = new byte[resultLength]; this.average = Buffer<byte>.CreateClean(resultLength);
this.paeth = new byte[resultLength]; this.paeth = Buffer<byte>.CreateClean(resultLength);
} }
byte[] buffer; byte[] buffer;
@ -621,13 +661,14 @@ namespace ImageSharp.Formats
try try
{ {
memoryStream = new MemoryStream(); memoryStream = new MemoryStream();
using (ZlibDeflateStream deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel)) using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
{ {
for (int y = 0; y < this.height; y++) for (int y = 0; y < this.height; y++)
{ {
deflateStream.Write(this.EncodePixelRow(pixels, y, previousScanline, rawScanline, result), 0, resultLength); Buffer<byte> r = this.EncodePixelRow(pixels, y);
deflateStream.Write(r.Array, 0, resultLength);
Swap(ref rawScanline, ref previousScanline); Swap(ref this.rawScanline, ref this.previousScanline);
} }
} }

Loading…
Cancel
Save