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="scanline">The scanline to encode</param>
/// <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>
/// <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 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) // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
fixed (byte* scan = scanline) result = 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 < bytesPerScanline; x++)
{
if (x - bytesPerPixel < 0)
{ {
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; ref byte scan = ref Unsafe.Add(ref scanline, x);
byte above = prev[x]; ref byte above = ref Unsafe.Add(ref previousScanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res[x + 1] = (byte)((scan[x] - Average(left, above)) % 256); 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="scanline">The scanline to encode</param>
/// <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>
/// <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 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)) // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp))
fixed (byte* scan = scanline) result = 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 < bytesPerScanline; x++)
{
if (x - bytesPerPixel < 0)
{ {
byte left = (x - bytesPerPixel < 0) ? (byte)0 : scan[x - bytesPerPixel]; ref byte scan = ref Unsafe.Add(ref scanline, x);
byte above = prev[x]; ref byte above = ref Unsafe.Add(ref previousScanline, x);
byte upperLeft = (x - bytesPerPixel < 0) ? (byte)0 : prev[x - bytesPerPixel]; ref byte res = ref Unsafe.Add(ref result, x + 1);
res = (byte)((scan - PaethPredicator(0, above, 0)) % 256);
res[x + 1] = (byte)((scan[x] - PaethPredicator(left, above, upperLeft)) % 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> /// </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>
/// <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 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) // Sub(x) = Raw(x) - Raw(x-bpp)
fixed (byte* scan = scanline) result = 1;
fixed (byte* res = result)
{
res[0] = 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]; ref byte scan = ref Unsafe.Add(ref scanline, x);
ref byte res = ref Unsafe.Add(ref result, x + 1);
res[x + 1] = (byte)((scan[x] - priorRawByte) % 256); 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="scanline">The scanline to encode</param>
/// <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>
/// <param name="bytesPerScanline">The number of bytes per scanline</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [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) // Up(x) = Raw(x) - Prior(x)
fixed (byte* scan = scanline) result = 2;
fixed (byte* prev = previousScanline)
fixed (byte* res = result)
{
res[0] = 2;
for (int x = 0; x < scanline.Length; x++)
{
byte above = prev[x];
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; this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0]; var scanSpan = new BufferSpan<byte>(this.scanline);
var scanBuffer = new BufferSpan<byte>(this.scanline); var prevSpan = new BufferSpan<byte>(this.previousScanline);
ref byte scanPoint = ref scanBuffer.DangerousGetPinnableReference(); ref byte scanPointer = ref scanSpan.DangerousGetPinnableReference();
var prevBuffer = new BufferSpan<byte>(this.previousScanline); ref byte prevPointer = ref prevSpan.DangerousGetPinnableReference();
ref byte prevPoint = ref prevBuffer.DangerousGetPinnableReference(); var filterType = (FilterType)scanPointer;
switch (filterType) switch (filterType)
{ {
@ -451,22 +451,22 @@ namespace ImageSharp.Formats
case FilterType.Sub: case FilterType.Sub:
SubFilter.Decode(ref scanPoint, this.bytesPerScanline, this.bytesPerPixel); SubFilter.Decode(ref scanPointer, this.bytesPerScanline, this.bytesPerPixel);
break; break;
case FilterType.Up: case FilterType.Up:
UpFilter.Decode(ref scanPoint, ref prevPoint, this.bytesPerScanline); UpFilter.Decode(ref scanPointer, ref prevPointer, this.bytesPerScanline);
break; break;
case FilterType.Average: case FilterType.Average:
AverageFilter.Decode(ref scanPoint, ref prevPoint, this.bytesPerScanline, this.bytesPerPixel); AverageFilter.Decode(ref scanPointer, ref prevPointer, this.bytesPerScanline, this.bytesPerPixel);
break; break;
case FilterType.Paeth: case FilterType.Paeth:
PaethFilter.Decode(ref scanPoint, ref prevPoint, this.bytesPerScanline, this.bytesPerPixel); PaethFilter.Decode(ref scanPointer, ref prevPointer, this.bytesPerScanline, this.bytesPerPixel);
break; break;
default: default:
@ -515,11 +515,11 @@ namespace ImageSharp.Formats
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
var filterType = (FilterType)this.scanline[0]; var scanSpan = new BufferSpan<byte>(this.scanline);
var scanBuffer = new BufferSpan<byte>(this.scanline); var prevSpan = new BufferSpan<byte>(this.previousScanline);
ref byte scanPointer = ref scanBuffer.DangerousGetPinnableReference(); ref byte scanPointer = ref scanSpan.DangerousGetPinnableReference();
var prevBuffer = new BufferSpan<byte>(this.previousScanline); ref byte prevPointer = ref prevSpan.DangerousGetPinnableReference();
ref byte prevPointer = ref prevBuffer.DangerousGetPinnableReference(); var filterType = (FilterType)scanPointer;
switch (filterType) switch (filterType)
{ {

58
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;
@ -71,6 +71,11 @@ namespace ImageSharp.Formats
/// </summary> /// </summary>
private int bytesPerPixel; private int bytesPerPixel;
/// <summary>
/// The number of bytes per scanline
/// </summary>
private int bytesPerScanline;
/// <summary> /// <summary>
/// The buffer for the sub filter /// The buffer for the sub filter
/// </summary> /// </summary>
@ -177,7 +182,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,
@ -307,7 +312,7 @@ namespace ImageSharp.Formats
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory. // 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); pixels.CopyTo(pixelRow, row);
} }
@ -354,6 +359,11 @@ namespace ImageSharp.Formats
/// <returns>The <see cref="T:byte[]"/></returns> /// <returns>The <see cref="T:byte[]"/></returns>
private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) 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. // 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)
{ {
@ -363,13 +373,18 @@ namespace ImageSharp.Formats
// 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); var upSpan = new BufferSpan<byte>(this.up);
int currentSum = this.CalculateTotalVariation(this.up, int.MaxValue); 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; int lowestSum = currentSum;
result = this.up; result = this.up;
PaethFilter.Encode(rawScanline, previousScanline, this.paeth, this.bytesPerPixel); var paethSpan = new BufferSpan<byte>(this.paeth);
currentSum = this.CalculateTotalVariation(this.paeth, currentSum); 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) if (currentSum < lowestSum)
{ {
@ -377,8 +392,10 @@ namespace ImageSharp.Formats
result = this.paeth; result = this.paeth;
} }
SubFilter.Encode(rawScanline, this.sub, this.bytesPerPixel); var subSpan = new BufferSpan<byte>(this.sub);
currentSum = this.CalculateTotalVariation(this.sub, int.MaxValue); 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) if (currentSum < lowestSum)
{ {
@ -386,8 +403,10 @@ namespace ImageSharp.Formats
result = this.sub; result = this.sub;
} }
AverageFilter.Encode(rawScanline, previousScanline, this.average, this.bytesPerPixel); var averageSpan = new BufferSpan<byte>(this.average);
currentSum = this.CalculateTotalVariation(this.average, currentSum); 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) if (currentSum < lowestSum)
{ {
@ -404,13 +423,14 @@ 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(ref byte scanline, int lastSum)
{ {
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]; ref byte v = ref Unsafe.Add(ref scanline, 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.
@ -601,10 +621,10 @@ 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]; byte[] previousScanline = new byte[this.bytesPerScanline];
byte[] rawScanline = new byte[bytesPerScanline]; byte[] rawScanline = new byte[this.bytesPerScanline];
int resultLength = bytesPerScanline + 1; int resultLength = this.bytesPerScanline + 1;
byte[] result = new byte[resultLength]; byte[] result = new byte[resultLength];
if (this.pngColorType != PngColorType.Palette) if (this.pngColorType != PngColorType.Palette)
@ -621,7 +641,7 @@ 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++)
{ {

Loading…
Cancel
Save