Browse Source

Use Unsafe.Add + BufferSpan for png encode filters

pull/212/head
James Jackson-South 9 years ago
parent
commit
bd2e24bad9
  1. 29
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  2. 31
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  3. 25
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  4. 21
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  5. 28
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  6. 58
      src/ImageSharp/Formats/Png/PngEncoderCore.cs

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

@ -49,23 +49,30 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel)
public static void Encode(ref byte scanline, ref byte previousScanline, ref byte result, int bytesPerScanline, int bytesPerPixel)
{
// Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
res[0] = 3;
result = 3;
for (int x = 0; x < scanline.Length; x++)
for (int x = 0; x < bytesPerScanline; x++)
{
if (x - bytesPerPixel < 0)
{
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel];
byte above = prev[x];
res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256);
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte above = ref Unsafe.Add(ref previousScanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - (above >> 1)) % 256);
}
else
{
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte left = ref Unsafe.Add(ref scanline, x - bytesPerPixel);
ref byte above = ref Unsafe.Add(ref previousScanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - Average(left, above)) % 256);
}
}
}

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

@ -51,24 +51,31 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result, int bytesPerPixel)
public static void Encode(ref byte scanline, ref byte previousScanline, ref byte result, int bytesPerScanline, int bytesPerPixel)
{
// Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
res[0] = 4;
result = 4;
for (int x = 0; x < scanline.Length; x++)
for (int x = 0; x < bytesPerScanline; x++)
{
if (x - bytesPerPixel < 0)
{
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel];
byte above = prev[x];
byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : prev[x - bytesPerPixel];
res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 256);
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte above = ref Unsafe.Add(ref previousScanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - PaethPredicator(0, above, 0)) % 256);
}
else
{
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte left = ref Unsafe.Add(ref scanline, x - bytesPerPixel);
ref byte above = ref Unsafe.Add(ref previousScanline, x);
ref byte upperLeft = ref Unsafe.Add(ref previousScanline, x - bytesPerPixel);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - PaethPredicator(left, above, upperLeft)) % 256);
}
}
}

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

@ -45,21 +45,28 @@ namespace ImageSharp.Formats
/// </summary>
/// <param name="scanline">The scanline to encode</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
/// <param name="bytesPerPixel">The bytes per pixel.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] result, int bytesPerPixel)
public static void Encode(ref byte scanline, ref byte result, int bytesPerScanline, int bytesPerPixel)
{
// Sub(x) = Raw(x) - Raw(x-bpp)
fixed (byte* scan = scanline)
fixed (byte* res = result)
{
res[0] = 1;
result = 1;
for (int x = 0; x < scanline.Length; x++)
for (int x = 0; x < bytesPerScanline; x++)
{
if (x - bytesPerPixel < 0)
{
byte priorRawByte = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel];
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256);
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)(scan % 256);
}
else
{
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte prev = ref Unsafe.Add(ref scanline, x - bytesPerPixel);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - prev) % 256);
}
}
}

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

@ -38,22 +38,19 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline to encode</param>
/// <param name="previousScanline">The previous scanline.</param>
/// <param name="result">The filtered scanline result.</param>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(byte[] scanline, byte[] previousScanline, byte[] result)
public static void Encode(ref byte scanline, ref byte previousScanline, ref byte result, int bytesPerScanline)
{
// Up(x) = Raw(x) - Prior(x)
fixed (byte* scan = scanline)
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
res[0] = 2;
for (int x = 0; x < scanline.Length; x++)
{
byte above = prev[x];
result = 2;
res[x + 1] = (byte)((scan[x] - above) % 256);
}
for (int x = 0; x < bytesPerScanline; x++)
{
ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte above = ref Unsafe.Add(ref previousScanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - above) % 256);
}
}
}

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

@ -438,11 +438,11 @@ namespace ImageSharp.Formats
this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0];
var scanBuffer = new BufferSpan<byte>(this.scanline);
ref byte scanPoint = ref scanBuffer.DangerousGetPinnableReference();
var prevBuffer = new BufferSpan<byte>(this.previousScanline);
ref byte prevPoint = ref prevBuffer.DangerousGetPinnableReference();
var scanSpan = new BufferSpan<byte>(this.scanline);
var prevSpan = new BufferSpan<byte>(this.previousScanline);
ref byte scanPointer = ref scanSpan.DangerousGetPinnableReference();
ref byte prevPointer = ref prevSpan.DangerousGetPinnableReference();
var filterType = (FilterType)scanPointer;
switch (filterType)
{
@ -451,22 +451,22 @@ namespace ImageSharp.Formats
case FilterType.Sub:
SubFilter.Decode(ref scanPoint, this.bytesPerScanline, this.bytesPerPixel);
SubFilter.Decode(ref scanPointer, this.bytesPerScanline, this.bytesPerPixel);
break;
case FilterType.Up:
UpFilter.Decode(ref scanPoint, ref prevPoint, this.bytesPerScanline);
UpFilter.Decode(ref scanPointer, ref prevPointer, this.bytesPerScanline);
break;
case FilterType.Average:
AverageFilter.Decode(ref scanPoint, ref prevPoint, this.bytesPerScanline, this.bytesPerPixel);
AverageFilter.Decode(ref scanPointer, ref prevPointer, this.bytesPerScanline, this.bytesPerPixel);
break;
case FilterType.Paeth:
PaethFilter.Decode(ref scanPoint, ref prevPoint, this.bytesPerScanline, this.bytesPerPixel);
PaethFilter.Decode(ref scanPointer, ref prevPointer, this.bytesPerScanline, this.bytesPerPixel);
break;
default:
@ -515,11 +515,11 @@ namespace ImageSharp.Formats
this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0];
var scanBuffer = new BufferSpan<byte>(this.scanline);
ref byte scanPointer = ref scanBuffer.DangerousGetPinnableReference();
var prevBuffer = new BufferSpan<byte>(this.previousScanline);
ref byte prevPointer = ref prevBuffer.DangerousGetPinnableReference();
var scanSpan = new BufferSpan<byte>(this.scanline);
var prevSpan = new BufferSpan<byte>(this.previousScanline);
ref byte scanPointer = ref scanSpan.DangerousGetPinnableReference();
ref byte prevPointer = ref prevSpan.DangerousGetPinnableReference();
var filterType = (FilterType)scanPointer;
switch (filterType)
{

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

@ -9,7 +9,7 @@ namespace ImageSharp.Formats
using System.Buffers;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using ImageSharp.PixelFormats;
using Quantizers;
@ -71,6 +71,11 @@ namespace ImageSharp.Formats
/// </summary>
private int bytesPerPixel;
/// <summary>
/// The number of bytes per scanline
/// </summary>
private int bytesPerScanline;
/// <summary>
/// The buffer for the sub filter
/// </summary>
@ -177,7 +182,7 @@ namespace ImageSharp.Formats
this.bytesPerPixel = this.CalculateBytesPerPixel();
PngHeader header = new PngHeader
var header = new PngHeader
{
Width = image.Width,
Height = image.Height,
@ -307,7 +312,7 @@ namespace ImageSharp.Formats
where TPixel : struct, IPixel<TPixel>
{
// We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory.
using (PixelArea<TPixel> pixelRow = new PixelArea<TPixel>(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz))
using (var pixelRow = new PixelArea<TPixel>(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz))
{
pixels.CopyTo(pixelRow, row);
}
@ -354,6 +359,11 @@ namespace ImageSharp.Formats
/// <returns>The <see cref="T:byte[]"/></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result)
{
var scanSpan = new BufferSpan<byte>(rawScanline);
var prevSpan = new BufferSpan<byte>(previousScanline);
ref byte scanPointer = ref scanSpan.DangerousGetPinnableReference();
ref byte prevPointer = ref prevSpan.DangerousGetPinnableReference();
// Palette images don't compress well with adaptive filtering.
if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8)
{
@ -363,13 +373,18 @@ namespace ImageSharp.Formats
// 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.
UpFilter.Encode(rawScanline, previousScanline, this.up);
int currentSum = this.CalculateTotalVariation(this.up, int.MaxValue);
var upSpan = new BufferSpan<byte>(this.up);
ref byte upPointer = ref upSpan.DangerousGetPinnableReference();
UpFilter.Encode(ref scanPointer, ref prevPointer, ref upPointer, this.bytesPerScanline);
int currentSum = this.CalculateTotalVariation(ref upPointer, int.MaxValue);
int lowestSum = currentSum;
result = this.up;
PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(this.paeth, currentSum);
var paethSpan = new BufferSpan<byte>(this.paeth);
ref byte paethPointer = ref paethSpan.DangerousGetPinnableReference();
PaethFilter.Encode(ref scanPointer, ref prevPointer, ref paethPointer, this.bytesPerScanline, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(ref paethPointer, currentSum);
if (currentSum < lowestSum)
{
@ -377,8 +392,10 @@ namespace ImageSharp.Formats
result = this.paeth;
}
SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(this.sub, int.MaxValue);
var subSpan = new BufferSpan<byte>(this.sub);
ref byte subPointer = ref subSpan.DangerousGetPinnableReference();
SubFilter.Encode(ref scanPointer, ref subPointer, this.bytesPerScanline, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(ref subPointer, int.MaxValue);
if (currentSum < lowestSum)
{
@ -386,8 +403,10 @@ namespace ImageSharp.Formats
result = this.sub;
}
AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(this.average, currentSum);
var averageSpan = new BufferSpan<byte>(this.average);
ref byte averagePointer = ref averageSpan.DangerousGetPinnableReference();
AverageFilter.Encode(ref scanPointer, ref prevPointer, ref averagePointer, this.bytesPerScanline, this.bytesPerPixel);
currentSum = this.CalculateTotalVariation(ref averagePointer, currentSum);
if (currentSum < lowestSum)
{
@ -404,13 +423,14 @@ namespace ImageSharp.Formats
/// <param name="scanline">The scanline bytes</param>
/// <param name="lastSum">The last variation sum</param>
/// <returns>The <see cref="int"/></returns>
private int CalculateTotalVariation(byte[] scanline, int lastSum)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CalculateTotalVariation(ref byte scanline, int lastSum)
{
int sum = 0;
for (int i = 1; i < scanline.Length; i++)
for (int i = 1; i < this.bytesPerScanline; i++)
{
byte v = scanline[i];
ref byte v = ref Unsafe.Add(ref scanline, i);
sum += v < 128 ? v : 256 - v;
// No point continuing if we are larger.
@ -601,10 +621,10 @@ namespace ImageSharp.Formats
private void WriteDataChunks<TPixel>(PixelAccessor<TPixel> pixels, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
int bytesPerScanline = this.width * this.bytesPerPixel;
byte[] previousScanline = new byte[bytesPerScanline];
byte[] rawScanline = new byte[bytesPerScanline];
int resultLength = bytesPerScanline + 1;
this.bytesPerScanline = this.width * this.bytesPerPixel;
byte[] previousScanline = new byte[this.bytesPerScanline];
byte[] rawScanline = new byte[this.bytesPerScanline];
int resultLength = this.bytesPerScanline + 1;
byte[] result = new byte[resultLength];
if (this.pngColorType != PngColorType.Palette)
@ -621,7 +641,7 @@ namespace ImageSharp.Formats
try
{
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++)
{

Loading…
Cancel
Save