From bab1728eea7951653d8004f16f8fd9e137cf9cf1 Mon Sep 17 00:00:00 2001 From: popow Date: Thu, 26 Apr 2018 21:45:56 +0200 Subject: [PATCH 1/6] fix for unnecessary memory allocation, if only the metadata of the image will be read --- .../Components/Decoder/OrigComponent.cs | 2 +- .../Jpeg/GolangPort/OrigJpegDecoderCore.cs | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs index e2f21bd1c3..e2b72db057 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs @@ -246,7 +246,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public void Dispose() { - this.SpectralBlocks.Dispose(); + this.SpectralBlocks?.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index a4fbb17be3..9202108720 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// /// Encapsulates stream reading and processing data and operations for . - /// It's a value type for imporved data locality, and reduced number of CALLVIRT-s + /// It's a value type for improved data locality, and reduced number of CALLVIRT-s /// public InputProcessor InputProcessor; #pragma warning restore SA401 @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort public int MCUCountY => this.ImageSizeInMCU.Height; /// - /// Gets the the total number of MCU-s (Minimum Coded Units) in the image. + /// Gets the total number of MCU-s (Minimum Coded Units) in the image. /// public int TotalMCUCount => this.MCUCountX * this.MCUCountY; @@ -331,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort case OrigJpegConstants.Markers.SOF1: case OrigJpegConstants.Markers.SOF2: this.IsProgressive = marker == OrigJpegConstants.Markers.SOF2; - this.ProcessStartOfFrameMarker(remaining); + this.ProcessStartOfFrameMarker(remaining, metadataOnly); if (metadataOnly && this.isJFif) { return; @@ -634,7 +634,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// Processes the Start of Frame marker. Specified in section B.2.2. /// /// The remaining bytes in the segment block. - private void ProcessStartOfFrameMarker(int remaining) + /// Whether to decode metadata only. + private void ProcessStartOfFrameMarker(int remaining, bool metadataOnly) { if (this.ComponentCount != 0) { @@ -689,12 +690,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.ImageSizeInMCU = this.ImageSizeInPixels.DivideRoundUp(8 * h0, 8 * v0); - foreach (OrigComponent component in this.Components) + this.ColorSpace = this.DeduceJpegColorSpace(); + + if (!metadataOnly) { - component.InitializeDerivedData(this.configuration.MemoryManager, this); + foreach (OrigComponent component in this.Components) + { + component.InitializeDerivedData(this.configuration.MemoryManager, this); + } } - - this.ColorSpace = this.DeduceJpegColorSpace(); } /// From 5f599e6227a6fa20e49ae84fbda139b2d6f2de4f Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Fri, 27 Apr 2018 18:54:53 +0200 Subject: [PATCH 2/6] Performance improvements. Ditched the use of modulo (%). Modulo is slow, and if you use it to just get the lower 8 bits (1 byte), you can just directly cast to byte. Also moved one if < out of the loop for speed. The less branching the better. Similarly replaced an if < 128 by cast to sbyte and taking its abs value. Performance gain in writing to PNG file seems to be roughly 20% (release mode, 1000x1000 bitmap). In ImageFramesCollectionTests I've set the culture to en-US to ensure getting english exception texts. --- .../Formats/Png/Filters/AverageFilter.cs | 22 ++++++++++++++++ .../Formats/Png/Filters/PaethFilter.cs | 26 +++++++++++++++++-- .../Formats/Png/Filters/SubFilter.cs | 20 ++++++++++++++ .../Formats/Png/Filters/UpFilter.cs | 11 ++++++++ .../Image/ImageFramesCollectionTests.cs | 3 +++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index de62d47029..e832b2d7fb 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) resultBaseRef = 3; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { if (x - bytesPerPixel < 0) @@ -89,6 +90,27 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += res < 128 ? res : 256 - res; } } +#else + int x = 0; + for (; x < bytesPerPixel;) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - (above >> 1)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - Average(left, above)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 3; } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 7e05d736f9..652269e275 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters 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)); + scan = (byte)(scan + PaethPredictor(left, above, upperLeft)); } } @@ -70,6 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) resultBaseRef = 4; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { if (x - bytesPerPixel < 0) @@ -91,6 +92,27 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += res < 128 ? res : 256 - res; } } +#else + int x = 0; + for (; x < bytesPerPixel; ++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 - PaethPredictor(0, above, 0)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(left, above, upperLeft)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 4; } @@ -106,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredicator(byte left, byte above, byte upperLeft) + private static byte PaethPredictor(byte left, byte above, byte upperLeft) { int p = left + above - upperLeft; int pa = ImageMaths.FastAbs(p - left); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index c0db7da935..f389cb2cd0 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -60,6 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Sub(x) = Raw(x) - Raw(x-bpp) resultBaseRef = 1; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { if (x - bytesPerPixel < 0) @@ -78,6 +79,25 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += res < 128 ? res : 256 - res; } } +#else + int x = 0; + for (; x < bytesPerPixel;) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = scan; + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - prev); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 1; } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 81c063ea9e..45ece23efe 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -57,6 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Up(x) = Raw(x) - Prior(x) resultBaseRef = 2; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { byte scan = Unsafe.Add(ref scanBaseRef, x); @@ -65,6 +66,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters res = (byte)((scan - above) % 256); sum += res < 128 ? res : 256 - res; } +#else + for (int x = 0; x < scanline.Length;) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - above); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 2; } diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index 4f00931de9..4c760e6810 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -15,6 +15,9 @@ namespace SixLabors.ImageSharp.Tests public ImageFramesCollectionTests() { + // Needed to get English exception messages, which are checked in several tests. + System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + this.image = new Image(10, 10); this.collection = new ImageFrameCollection(this.image, 10, 10); } From 8db7cd5955e88356b4013aa01cd030e21a7ca7bb Mon Sep 17 00:00:00 2001 From: popow Date: Fri, 27 Apr 2018 20:00:02 +0200 Subject: [PATCH 3/6] added parameter metadataOnly to InitializeDerivedData: If true, memory allocation will not be done --- .../Jpeg/GolangPort/Components/Decoder/OrigComponent.cs | 8 ++++++-- .../Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs | 7 ++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs index e2b72db057..8445625bde 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigComponent.cs @@ -57,7 +57,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The to use for buffer allocations. /// The instance - public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder) + /// Whether to decode metadata only. If this is true, memory allocation for SpectralBlocks will not be necessary + public void InitializeDerivedData(MemoryManager memoryManager, OrigJpegDecoderCore decoder, bool metadataOnly) { // For 4-component images (either CMYK or YCbCrK), we only support two // hv vectors: [0x11 0x11 0x11 0x11] and [0x22 0x11 0x11 0x22]. @@ -80,7 +81,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors); } - this.SpectralBlocks = memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); + if (!metadataOnly) + { + this.SpectralBlocks = memoryManager.Allocate2D(this.SizeInBlocks.Width, this.SizeInBlocks.Height, true); + } } /// diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs index 9202108720..66b4601da9 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/OrigJpegDecoderCore.cs @@ -692,12 +692,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.ColorSpace = this.DeduceJpegColorSpace(); - if (!metadataOnly) + foreach (OrigComponent component in this.Components) { - foreach (OrigComponent component in this.Components) - { - component.InitializeDerivedData(this.configuration.MemoryManager, this); - } + component.InitializeDerivedData(this.configuration.MemoryManager, this, metadataOnly); } } From 555524d473b66ac4d7da4e1fa9c04a081efce6a8 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Fri, 27 Apr 2018 20:20:20 +0200 Subject: [PATCH 4/6] Removed some overhead in the Bgra32.ToVector4 and PackFromVector4 methods. It's now also more inline with the Rgba32 implementation. --- src/ImageSharp/PixelFormats/Bgra32.cs | 32 +++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/PixelFormats/Bgra32.cs b/src/ImageSharp/PixelFormats/Bgra32.cs index 20dfda5046..de660c05b7 100644 --- a/src/ImageSharp/PixelFormats/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/Bgra32.cs @@ -38,6 +38,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// public byte A; + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new Vector4(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new Vector4(0.5F); + /// /// Initializes a new instance of the struct. /// @@ -141,16 +151,14 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PackFromVector4(Vector4 vector) { - var rgba = default(Rgba32); - rgba.PackFromVector4(vector); - this.PackFromRgba32(rgba); + this.Pack(ref vector); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { - return this.ToRgba32().ToVector4(); + return new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; } /// @@ -243,5 +251,21 @@ namespace SixLabors.ImageSharp.PixelFormats /// The RGBA value [MethodImpl(MethodImplOptions.AggressiveInlining)] public Bgra32 ToBgra32() => this; + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Pack(ref Vector4 vector) { + vector *= MaxBytes; + vector += Half; + vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } } } \ No newline at end of file From a06600380a216ffa6e9a82471f6bcac558ef38d4 Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sat, 28 Apr 2018 11:19:59 +0200 Subject: [PATCH 5/6] Minor performance refactorings. Moved the division out of the loop, division is expensive. --- .../Drawing/Processors/FillRegionProcessor.cs | 10 +++++----- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillRegionProcessor.cs index aa0b2e9b23..95ac3fe298 100644 --- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillRegionProcessor.cs @@ -100,6 +100,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors using (BasicArrayBuffer scanline = source.MemoryManager.AllocateFake(scanlineWidth)) { bool scanlineDirty = true; + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; for (int y = minY; y < maxY; y++) { if (scanlineDirty) @@ -113,9 +115,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors scanlineDirty = false; } - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) + float yPlusOne = y + 1; + for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) { int pointsFound = region.Scan(subPixel + offset, buffer.Array, 0); if (pointsFound == 0) @@ -197,8 +198,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors private static void QuickSort(Span data) { - int hi = Math.Min(data.Length - 1, data.Length - 1); - QuickSort(data, 0, hi); + QuickSort(data, 0, data.Length - 1); } private static void QuickSort(Span data, int lo, int hi) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 777ee1f543..b95e102c76 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -321,9 +321,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// The private IManagedByteBuffer GetOptimalFilteredScanline() { - Span scanSpan = this.rawScanline.Span; - Span prevSpan = this.previousScanline.Span; - // Palette images don't compress well with adaptive filtering. if (this.pngColorType == PngColorType.Palette || this.bitDepth < 8) { @@ -331,6 +328,9 @@ namespace SixLabors.ImageSharp.Formats.Png return this.result; } + Span scanSpan = this.rawScanline.Span; + Span prevSpan = this.previousScanline.Span; + // 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(scanSpan, prevSpan, this.up.Span, out int currentSum); From 12ec82b25f50612c6dbae6569a59fbbf92c86e6f Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Sat, 28 Apr 2018 11:57:34 +0200 Subject: [PATCH 6/6] Processed review comments and did similar refactoring to the PNG decoding. --- .../Formats/Png/Filters/AverageFilter.cs | 53 +++++-------------- .../Formats/Png/Filters/PaethFilter.cs | 38 +++---------- .../Formats/Png/Filters/SubFilter.cs | 47 +++++----------- .../Formats/Png/Filters/UpFilter.cs | 15 +----- 4 files changed, 36 insertions(+), 117 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index e832b2d7fb..ffcf9b0f30 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -29,21 +29,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); // Average(x) + floor((Raw(x-bpp)+Prior(x))/2) - for (int x = 1; x < scanline.Length; x++) + int x = 1; + for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + scan = (byte)(scan + (above >> 1)); + } + + for (; x < scanline.Length; ++x) { - if (x - bytesPerPixel < 1) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)((scan + (above >> 1)) % 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); - } + 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)); } } @@ -69,30 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) resultBaseRef = 3; -#if OLD_AND_SLOW - for (int x = 0; x < scanline.Length; x++) - { - if (x - bytesPerPixel < 0) - { - 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 >> 1)) % 256); - sum += res < 128 ? res : 256 - res; - } - 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); - sum += res < 128 ? res : 256 - res; - } - } -#else int x = 0; - for (; x < bytesPerPixel;) { + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; @@ -101,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte left = Unsafe.Add(ref scanBaseRef, xLeft); byte above = Unsafe.Add(ref prevBaseRef, x); @@ -110,7 +86,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters res = (byte)(scan - Average(left, above)); sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } -#endif sum -= 3; } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 652269e275..0d3df079c9 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -30,15 +30,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline); // Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp)) - int offset = bytesPerPixel + 1; - for (int x = 1; x < offset; x++) + int offset = bytesPerPixel + 1; // Add one bcause x starts at one. + int x = 1; + for (; x < offset; x++) { ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); scan = (byte)(scan + above); } - for (int x = offset; x < scanline.Length; x++) + for (; x < scanline.Length; x++) { ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); @@ -70,39 +71,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) resultBaseRef = 4; -#if OLD_AND_SLOW - for (int x = 0; x < scanline.Length; x++) - { - if (x - bytesPerPixel < 0) - { - 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 - PaethPredicator(0, above, 0)) % 256); - sum += res < 128 ? res : 256 - res; - } - 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); - sum += res < 128 ? res : 256 - res; - } - } -#else int x = 0; - for (; x < bytesPerPixel; ++x) { + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); - ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); res = (byte)(scan - PaethPredictor(0, above, 0)); sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte left = Unsafe.Add(ref scanBaseRef, xLeft); byte above = Unsafe.Add(ref prevBaseRef, x); @@ -112,7 +91,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters res = (byte)(scan - PaethPredictor(left, above, upperLeft)); sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } -#endif sum -= 4; } diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index f389cb2cd0..cfb7781be4 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -25,19 +25,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); // Sub(x) + Raw(x-bpp) - for (int x = 1; x < scanline.Length; x++) + int x = 1; + for (; x <= bytesPerPixel /* Note the <= because x starts at 1 */; ++x) { - if (x - bytesPerPixel < 1) - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - scan = (byte)(scan % 256); - } - else - { - ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); - byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); - scan = (byte)((scan + prev) % 256); - } + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + } + + for (; x < scanline.Length; ++x) + { + ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); + scan = (byte)(scan + prev); } } @@ -60,28 +58,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Sub(x) = Raw(x) - Raw(x-bpp) resultBaseRef = 1; -#if OLD_AND_SLOW - for (int x = 0; x < scanline.Length; x++) - { - if (x - bytesPerPixel < 0) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1); - res = (byte)(scan % 256); - sum += res < 128 ? res : 256 - res; - } - 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); - sum += res < 128 ? res : 256 - res; - } - } -#else int x = 0; - for (; x < bytesPerPixel;) { + for (; x < bytesPerPixel; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); ++x; ref byte res = ref Unsafe.Add(ref resultBaseRef, x); @@ -89,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } - for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte prev = Unsafe.Add(ref scanBaseRef, xLeft); ++x; @@ -97,7 +75,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters res = (byte)(scan - prev); sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } -#endif sum -= 1; } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 45ece23efe..c6a297e33a 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters { ref byte scan = ref Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); - scan = (byte)((scan + above) % 256); + scan = (byte)(scan + above); } } @@ -57,17 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Up(x) = Raw(x) - Prior(x) resultBaseRef = 2; -#if OLD_AND_SLOW - 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); - sum += res < 128 ? res : 256 - res; - } -#else - for (int x = 0; x < scanline.Length;) { + for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */) { byte scan = Unsafe.Add(ref scanBaseRef, x); byte above = Unsafe.Add(ref prevBaseRef, x); ++x; @@ -75,7 +65,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters res = (byte)(scan - above); sum += ImageMaths.FastAbs(unchecked((sbyte)res)); } -#endif sum -= 2; }