From bd2e24bad9fd02623194035a73dd210deffd68fe Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 9 May 2017 22:35:43 +1000 Subject: [PATCH] Use Unsafe.Add + BufferSpan for png encode filters --- .../Formats/Png/Filters/AverageFilter.cs | 29 ++++++---- .../Formats/Png/Filters/PaethFilter.cs | 31 ++++++---- .../Formats/Png/Filters/SubFilter.cs | 25 +++++--- .../Formats/Png/Filters/UpFilter.cs | 21 +++---- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 28 ++++----- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 58 +++++++++++++------ 6 files changed, 115 insertions(+), 77 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index 31d80c586..4c2b56e51 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -49,23 +49,30 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The filtered scanline result. + /// The number of bytes per scanline /// The bytes per pixel. [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); } } } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 986a291fe..79de59cb9 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -51,24 +51,31 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The filtered scanline result. + /// The number of bytes per scanline /// The bytes per pixel. [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); } } } diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index 1220dc5fb..90d0fa692 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -45,21 +45,28 @@ namespace ImageSharp.Formats /// /// The scanline to encode /// The filtered scanline result. + /// The number of bytes per scanline /// The bytes per pixel. [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); } } } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index ee758f64e..fa6733060 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -38,22 +38,19 @@ namespace ImageSharp.Formats /// The scanline to encode /// The previous scanline. /// The filtered scanline result. + /// The number of bytes per scanline [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); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index ae6fd859e..f4e1d8261 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/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(this.scanline); - ref byte scanPoint = ref scanBuffer.DangerousGetPinnableReference(); - var prevBuffer = new BufferSpan(this.previousScanline); - ref byte prevPoint = ref prevBuffer.DangerousGetPinnableReference(); + var scanSpan = new BufferSpan(this.scanline); + var prevSpan = new BufferSpan(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(this.scanline); - ref byte scanPointer = ref scanBuffer.DangerousGetPinnableReference(); - var prevBuffer = new BufferSpan(this.previousScanline); - ref byte prevPointer = ref prevBuffer.DangerousGetPinnableReference(); + var scanSpan = new BufferSpan(this.scanline); + var prevSpan = new BufferSpan(this.previousScanline); + ref byte scanPointer = ref scanSpan.DangerousGetPinnableReference(); + ref byte prevPointer = ref prevSpan.DangerousGetPinnableReference(); + var filterType = (FilterType)scanPointer; switch (filterType) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 31e8cd90e..342067307 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/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 /// private int bytesPerPixel; + /// + /// The number of bytes per scanline + /// + private int bytesPerScanline; + /// /// The buffer for the sub filter /// @@ -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 { // We can use the optimized PixelAccessor here and copy the bytes in unmanaged memory. - using (PixelArea pixelRow = new PixelArea(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz)) + using (var pixelRow = new PixelArea(this.width, rawScanline, this.bytesPerPixel == 4 ? ComponentOrder.Xyzw : ComponentOrder.Xyz)) { pixels.CopyTo(pixelRow, row); } @@ -354,6 +359,11 @@ namespace ImageSharp.Formats /// The private byte[] GetOptimalFilteredScanline(byte[] rawScanline, byte[] previousScanline, byte[] result) { + var scanSpan = new BufferSpan(rawScanline); + var prevSpan = new BufferSpan(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(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(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(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(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 /// The scanline bytes /// The last variation sum /// The - 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(PixelAccessor pixels, Stream stream) where TPixel : struct, IPixel { - 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++) {