From eafbcb4fd77bdde2e20f3210b2391ea1d488952e Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 15 Sep 2017 07:50:48 +0100 Subject: [PATCH 01/29] Expose advanced pixel reference API. --- src/ImageSharp/Advanced/ImageExtensions.cs | 44 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs index f4043b5ad..a517ea5b3 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/ImageExtensions.cs @@ -12,13 +12,35 @@ namespace SixLabors.ImageSharp.Advanced /// internal static class ImageExtensions { + /// + /// Gets a reference to the pixel at the specified position. + /// + /// The source image frame + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + public static ref TPixel GetPixelReference(this ImageFrame source, int x, int y) + where TPixel : struct, IPixel + => ref GetPixelReference((IPixelSource)source, x, y); + + /// + /// Gets a reference to the pixel at the specified position. + /// + /// The source image frame + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + public static ref TPixel GetPixelReference(this Image source, int x, int y) + where TPixel : struct, IPixel + => ref source.Frames.RootFrame.GetPixelReference(x, y); + /// /// Gets the representation of the pixels as an area of contiguous memory in the given pixel format. /// /// The type of the pixel. /// The source. /// The - public static Span GetPixelSpan(this ImageFrame source) + internal static Span GetPixelSpan(this ImageFrame source) where TPixel : struct, IPixel => GetSpan(source); @@ -29,7 +51,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - public static Span GetPixelRowSpan(this ImageFrame source, int row) + internal static Span GetPixelRowSpan(this ImageFrame source, int row) where TPixel : struct, IPixel => GetSpan(source, row); @@ -39,7 +61,7 @@ namespace SixLabors.ImageSharp.Advanced /// The type of the pixel. /// The source. /// The - public static Span GetPixelSpan(this Image source) + internal static Span GetPixelSpan(this Image source) where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelSpan(); @@ -50,7 +72,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - public static Span GetPixelRowSpan(this Image source, int row) + internal static Span GetPixelRowSpan(this Image source, int row) where TPixel : struct, IPixel => source.Frames.RootFrame.GetPixelRowSpan(row); @@ -60,7 +82,7 @@ namespace SixLabors.ImageSharp.Advanced /// The Pixel format. /// The source image /// Returns the configuration. - public static Configuration GetConfiguration(this Image source) + internal static Configuration GetConfiguration(this Image source) where TPixel : struct, IPixel => GetConfiguration((IConfigurable)source); @@ -107,5 +129,17 @@ namespace SixLabors.ImageSharp.Advanced /// Returns the bounds of the image private static Configuration GetConfiguration(IConfigurable source) => source?.Configuration ?? Configuration.Default; + + /// + /// Gets a reference to the pixel at the specified position. + /// + /// The source image frame + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + private static ref TPixel GetPixelReference(IPixelSource source, int x, int y) + where TPixel : struct, IPixel + => ref source.PixelBuffer[x, y]; + } } From febe488bbc245c5244857f28b6ef1a8f1daf33ac Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 15 Sep 2017 08:02:47 +0100 Subject: [PATCH 02/29] fix style cop issues --- src/ImageSharp/Advanced/ImageExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs index a517ea5b3..fa299f811 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/ImageExtensions.cs @@ -140,6 +140,5 @@ namespace SixLabors.ImageSharp.Advanced private static ref TPixel GetPixelReference(IPixelSource source, int x, int y) where TPixel : struct, IPixel => ref source.PixelBuffer[x, y]; - } } From 7ee1a8b173d07d6cc7369bdc84a85bea9b5995c7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 15 Sep 2017 19:18:41 +0100 Subject: [PATCH 03/29] rename api to DangerousGetPinnableReferenceToPixelBuffer --- src/ImageSharp/Advanced/ImageExtensions.cs | 33 ++++++++++------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs index fa299f811..63a66de83 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/ImageExtensions.cs @@ -13,26 +13,24 @@ namespace SixLabors.ImageSharp.Advanced internal static class ImageExtensions { /// - /// Gets a reference to the pixel at the specified position. + /// Returns a reference to the 0th element of the Pixel buffer. + /// Such a reference can be used for pinning but must never be dereferenced. /// /// The source image frame - /// The x coordinate (row) - /// The y coordinate (position at row) - /// A reference to the element. - public static ref TPixel GetPixelReference(this ImageFrame source, int x, int y) + /// A pinnable reference the first root of the pixel buffer. + public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source) where TPixel : struct, IPixel - => ref GetPixelReference((IPixelSource)source, x, y); + => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source); /// - /// Gets a reference to the pixel at the specified position. + /// Returns a reference to the 0th element of the Pixel buffer. + /// Such a reference can be used for pinning but must never be dereferenced. /// - /// The source image frame - /// The x coordinate (row) - /// The y coordinate (position at row) - /// A reference to the element. - public static ref TPixel GetPixelReference(this Image source, int x, int y) + /// The source image + /// A pinnable reference the first root of the pixel buffer. + public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source) where TPixel : struct, IPixel - => ref source.Frames.RootFrame.GetPixelReference(x, y); + => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer(); /// /// Gets the representation of the pixels as an area of contiguous memory in the given pixel format. @@ -131,14 +129,13 @@ namespace SixLabors.ImageSharp.Advanced => source?.Configuration ?? Configuration.Default; /// - /// Gets a reference to the pixel at the specified position. + /// Returns a reference to the 0th element of the Pixel buffer. + /// Such a reference can be used for pinning but must never be dereferenced. /// /// The source image frame - /// The x coordinate (row) - /// The y coordinate (position at row) /// A reference to the element. - private static ref TPixel GetPixelReference(IPixelSource source, int x, int y) + private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) where TPixel : struct, IPixel - => ref source.PixelBuffer[x, y]; + => ref source.PixelBuffer.Span.DangerousGetPinnableReference(); } } From c9e578644dddd9c9d842418390fcbd7cac87751f Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 15 Sep 2017 21:18:44 +0100 Subject: [PATCH 04/29] make class public --- src/ImageSharp/Advanced/ImageExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs index 63a66de83..19e37689d 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/ImageExtensions.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// Extension methods over Image{TPixel} /// - internal static class ImageExtensions + public static class ImageExtensions { /// /// Returns a reference to the 0th element of the Pixel buffer. From 9652438946bc6d15b9e20721c4121ae30112870c Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Fri, 15 Sep 2017 21:26:06 +0100 Subject: [PATCH 05/29] fix style issues --- src/ImageSharp/Advanced/ImageExtensions.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs index 19e37689d..aaff68312 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/ImageExtensions.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Advanced /// Returns a reference to the 0th element of the Pixel buffer. /// Such a reference can be used for pinning but must never be dereferenced. /// + /// The Pixel format. /// The source image frame /// A pinnable reference the first root of the pixel buffer. public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source) @@ -26,6 +27,7 @@ namespace SixLabors.ImageSharp.Advanced /// Returns a reference to the 0th element of the Pixel buffer. /// Such a reference can be used for pinning but must never be dereferenced. /// + /// The Pixel format. /// The source image /// A pinnable reference the first root of the pixel buffer. public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source) From 356b8c389722524bd72cbc4059415dbd70a04462 Mon Sep 17 00:00:00 2001 From: Anton Firsov Date: Sat, 16 Sep 2017 00:15:39 +0200 Subject: [PATCH 06/29] Updated docs on DangerousGetPinnableReferenceToPixelBuffer() --- src/ImageSharp/Advanced/ImageExtensions.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/ImageExtensions.cs index aaff68312..8ab245aee 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/ImageExtensions.cs @@ -13,8 +13,9 @@ namespace SixLabors.ImageSharp.Advanced public static class ImageExtensions { /// - /// Returns a reference to the 0th element of the Pixel buffer. - /// Such a reference can be used for pinning but must never be dereferenced. + /// Returns a reference to the 0th element of the Pixel buffer, + /// allowing direct manipulation of pixel data through unsafe operations. + /// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order. /// /// The Pixel format. /// The source image frame @@ -24,8 +25,9 @@ namespace SixLabors.ImageSharp.Advanced => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source); /// - /// Returns a reference to the 0th element of the Pixel buffer. - /// Such a reference can be used for pinning but must never be dereferenced. + /// Returns a reference to the 0th element of the Pixel buffer, + /// allowing direct manipulation of pixel data through unsafe operations. + /// The pixel buffer is a contigous memory area containing Width*Height TPixel elements layed out in row-major order. /// /// The Pixel format. /// The source image From 1a02468afd279c799706693b8f6da7efd0ec1129 Mon Sep 17 00:00:00 2001 From: Eric Mellino Date: Sat, 16 Sep 2017 03:04:09 -0700 Subject: [PATCH 07/29] Add a test case for DangerousGetPinnableReferenceToPixelBuffer. --- .../Advanced/ImageExtensionsTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs diff --git a/tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs new file mode 100644 index 000000000..3bd6bf19b --- /dev/null +++ b/tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; +using SixLabors.ImageSharp.Advanced; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Tests.Advanced +{ + public class ImageExtensionsTests + { + [Fact] + public unsafe void DangerousGetPinnableReference_CopyToBuffer() + { + var image = new Image(128, 128); + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + { + image[x, y] = new Rgba32(x, 255 - y, x + y); + } + + Rgba32[] targetBuffer = new Rgba32[image.Width * image.Height]; + + fixed (Rgba32* targetPtr = targetBuffer) + fixed (Rgba32* pixelBasePtr = &image.DangerousGetPinnableReferenceToPixelBuffer()) + { + uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf()); + Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes); + } + + for (int y = 0; y < image.Height; y++) + for (int x = 0; x < image.Width; x++) + { + int linearIndex = y * image.Width + x; + Assert.Equal(image[x, y], targetBuffer[linearIndex]); + } + } + } +} From b4f0dd89f86103ba39c636e33ce3cc936d189f74 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 16 Sep 2017 11:59:47 +0100 Subject: [PATCH 08/29] update dependencies to prevent stylecop.json being included in package --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 6 +++--- src/ImageSharp/ImageSharp.csproj | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 829b5d850..6045a9c6a 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -37,9 +37,9 @@ - - - + + + All diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index df58be487..480d2d4d8 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -34,7 +34,7 @@ - + All From fef175dc7ee7cdd6c1bd836c06b674af2bdd8685 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 16 Sep 2017 17:08:04 +0200 Subject: [PATCH 09/29] Renamed *.Advanced.ImageExtensions to AdvancedImageExtensions, new ImageExtensions.SavePixelData() overloads, better tests --- ...tensions.cs => AdvancedImageExtensions.cs} | 2 +- src/ImageSharp/Image/ImageExtensions.cs | 52 ++++++++---- .../Advanced/AdvancedImageExtensionsTests.cs | 38 +++++++++ .../Advanced/ImageExtensionsTests.cs | 39 --------- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 80 ++++++------------- .../TestUtilities/TestImageExtensions.cs | 24 +++++- 6 files changed, 121 insertions(+), 114 deletions(-) rename src/ImageSharp/Advanced/{ImageExtensions.cs => AdvancedImageExtensions.cs} (99%) create mode 100644 tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs delete mode 100644 tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs diff --git a/src/ImageSharp/Advanced/ImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs similarity index 99% rename from src/ImageSharp/Advanced/ImageExtensions.cs rename to src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 8ab245aee..511e66c64 100644 --- a/src/ImageSharp/Advanced/ImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// Extension methods over Image{TPixel} /// - public static class ImageExtensions + public static class AdvancedImageExtensions { /// /// Returns a reference to the 0th element of the Pixel buffer, diff --git a/src/ImageSharp/Image/ImageExtensions.cs b/src/ImageSharp/Image/ImageExtensions.cs index c5b3d6f31..c4de1c298 100644 --- a/src/ImageSharp/Image/ImageExtensions.cs +++ b/src/ImageSharp/Image/ImageExtensions.cs @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp } /// - /// Saves the raw image to the given bytes. + /// Saves the raw image pixels to a byte array in row-major order. /// /// The Pixel format. /// The source image @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp => source.GetPixelSpan().AsBytes().ToArray(); /// - /// Saves the raw image to the given bytes. + /// Saves the raw image pixels to the given byte array in row-major order. /// /// The Pixel format. /// The source image @@ -131,26 +131,21 @@ namespace SixLabors.ImageSharp /// Thrown if the stream is null. public static void SavePixelData(this ImageFrame source, byte[] buffer) where TPixel : struct, IPixel - => SavePixelData(source, new Span(buffer)); + => SavePixelData(source, buffer.AsSpan().NonPortableCast()); /// - /// Saves the raw image to the given bytes. + /// Saves the raw image pixels to the given TPixel array in row-major order. /// /// The Pixel format. /// The source image /// The buffer to save the raw pixel data to. /// Thrown if the stream is null. - private static void SavePixelData(this ImageFrame source, Span buffer) + public static void SavePixelData(this ImageFrame source, TPixel[] buffer) where TPixel : struct, IPixel - { - Span byteBuffer = source.GetPixelSpan().AsBytes(); - Guard.MustBeGreaterThanOrEqualTo(buffer.Length, byteBuffer.Length, nameof(buffer)); - - byteBuffer.CopyTo(buffer); - } + => SavePixelData(source, new Span(buffer)); /// - /// Saves the raw image to the given bytes. + /// Saves the raw image pixels to a byte array in row-major order. /// /// The Pixel format. /// The source image @@ -161,7 +156,7 @@ namespace SixLabors.ImageSharp => source.Frames.RootFrame.SavePixelData(); /// - /// Saves the raw image to the given bytes. + /// Saves the raw image pixels to the given byte array in row-major order. /// /// The Pixel format. /// The source image @@ -172,13 +167,13 @@ namespace SixLabors.ImageSharp => source.Frames.RootFrame.SavePixelData(buffer); /// - /// Saves the raw image to the given bytes. + /// Saves the raw image pixels to the given TPixel array in row-major order. /// /// The Pixel format. /// The source image /// The buffer to save the raw pixel data to. /// Thrown if the stream is null. - private static void SavePixelData(this Image source, Span buffer) + public static void SavePixelData(this Image source, TPixel[] buffer) where TPixel : struct, IPixel => source.Frames.RootFrame.SavePixelData(buffer); @@ -200,5 +195,32 @@ namespace SixLabors.ImageSharp return $"data:{format.DefaultMimeType};base64,{Convert.ToBase64String(stream.ToArray())}"; } } + + /// + /// Saves the raw image to the given bytes. + /// + /// The Pixel format. + /// The source image + /// The buffer to save the raw pixel data to. + /// Thrown if the stream is null. + internal static void SavePixelData(this Image source, Span buffer) + where TPixel : struct, IPixel + => source.Frames.RootFrame.SavePixelData(buffer.NonPortableCast()); + + /// + /// Saves the raw image to the given bytes. + /// + /// The Pixel format. + /// The source image + /// The buffer to save the raw pixel data to. + /// Thrown if the stream is null. + internal static void SavePixelData(this ImageFrame source, Span buffer) + where TPixel : struct, IPixel + { + Span sourceBuffer = source.GetPixelSpan(); + Guard.MustBeGreaterThanOrEqualTo(buffer.Length, sourceBuffer.Length, nameof(buffer)); + + sourceBuffer.CopyTo(buffer); + } } } diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs new file mode 100644 index 000000000..4291e775d --- /dev/null +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; +using SixLabors.ImageSharp.Advanced; +using System.Runtime.CompilerServices; +// ReSharper disable InconsistentNaming + +namespace SixLabors.ImageSharp.Tests.Advanced +{ + using SixLabors.ImageSharp.PixelFormats; + + public class AdvancedImageExtensionsTests + { + [Theory] + [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public unsafe void DangerousGetPinnableReference_CopyToBuffer(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + TPixel[] targetBuffer = new TPixel[image.Width * image.Height]; + + ref byte source = ref Unsafe.As(ref targetBuffer[0]); + ref byte dest = ref Unsafe.As(ref image.DangerousGetPinnableReferenceToPixelBuffer()); + + fixed (byte* targetPtr = &source) + fixed (byte* pixelBasePtr = &dest) + { + uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf()); + Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes); + } + + image.ComparePixelBufferTo(targetBuffer); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs deleted file mode 100644 index 3bd6bf19b..000000000 --- a/tests/ImageSharp.Tests/Advanced/ImageExtensionsTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using Xunit; -using SixLabors.ImageSharp.Advanced; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Tests.Advanced -{ - public class ImageExtensionsTests - { - [Fact] - public unsafe void DangerousGetPinnableReference_CopyToBuffer() - { - var image = new Image(128, 128); - for (int y = 0; y < image.Height; y++) - for (int x = 0; x < image.Width; x++) - { - image[x, y] = new Rgba32(x, 255 - y, x + y); - } - - Rgba32[] targetBuffer = new Rgba32[image.Width * image.Height]; - - fixed (Rgba32* targetPtr = targetBuffer) - fixed (Rgba32* pixelBasePtr = &image.DangerousGetPinnableReferenceToPixelBuffer()) - { - uint dataSizeInBytes = (uint)(image.Width * image.Height * Unsafe.SizeOf()); - Unsafe.CopyBlock(targetPtr, pixelBasePtr, dataSizeInBytes); - } - - for (int y = 0; y < image.Height; y++) - for (int x = 0; x < image.Width; x++) - { - int linearIndex = y * image.Width + x; - Assert.Equal(image[x, y], targetBuffer[linearIndex]); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 36d3b3c05..5b672059c 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -9,9 +9,12 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; using Moq; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { + using System.Runtime.CompilerServices; + /// /// Tests the class. /// @@ -47,76 +50,39 @@ namespace SixLabors.ImageSharp.Tests this.Image = new Image(config, 1, 1); } - [Fact] - public void SavePixelData_Rgba32() + [Theory] + [WithTestPatternImages(13, 19, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void SavePixelData_ToPixelStructArray(TestImageProvider provider) + where TPixel : struct, IPixel { - using (var img = new Image(2, 2)) + using (Image image = provider.GetImage()) { - img[0, 0] = Rgba32.White; - img[1, 0] = Rgba32.Black; + TPixel[] buffer = new TPixel[image.Width*image.Height]; + image.SavePixelData(buffer); - img[0, 1] = Rgba32.Red; - img[1, 1] = Rgba32.Blue; - var buffer = new byte[2 * 2 * 4]; // width * height * bytes per pixel - img.SavePixelData(buffer); - - Assert.Equal(255, buffer[0]); // 0, 0, R - Assert.Equal(255, buffer[1]); // 0, 0, G - Assert.Equal(255, buffer[2]); // 0, 0, B - Assert.Equal(255, buffer[3]); // 0, 0, A - - Assert.Equal(0, buffer[4]); // 1, 0, R - Assert.Equal(0, buffer[5]); // 1, 0, G - Assert.Equal(0, buffer[6]); // 1, 0, B - Assert.Equal(255, buffer[7]); // 1, 0, A - - Assert.Equal(255, buffer[8]); // 0, 1, R - Assert.Equal(0, buffer[9]); // 0, 1, G - Assert.Equal(0, buffer[10]); // 0, 1, B - Assert.Equal(255, buffer[11]); // 0, 1, A - - Assert.Equal(0, buffer[12]); // 1, 1, R - Assert.Equal(0, buffer[13]); // 1, 1, G - Assert.Equal(255, buffer[14]); // 1, 1, B - Assert.Equal(255, buffer[15]); // 1, 1, A + image.ComparePixelBufferTo(buffer); + + // TODO: We need a separate test-case somewhere ensuring that image pixels are stored in row-major order! } } - - [Fact] - public void SavePixelData_Bgr24() + [Theory] + [WithTestPatternImages(19, 13, PixelTypes.Rgba32 | PixelTypes.Bgr24)] + public void SavePixelData_ToByteArray(TestImageProvider provider) + where TPixel : struct, IPixel { - using (var img = new Image(2, 2)) + using (Image image = provider.GetImage()) { - img[0, 0] = NamedColors.White; - img[1, 0] = NamedColors.Black; - - img[0, 1] = NamedColors.Red; - img[1, 1] = NamedColors.Blue; - - var buffer = new byte[2 * 2 * 3]; // width * height * bytes per pixel - img.SavePixelData(buffer); + byte[] buffer = new byte[image.Width*image.Height*Unsafe.SizeOf()]; - Assert.Equal(255, buffer[0]); // 0, 0, B - Assert.Equal(255, buffer[1]); // 0, 0, G - Assert.Equal(255, buffer[2]); // 0, 0, R + image.SavePixelData(buffer); - Assert.Equal(0, buffer[3]); // 1, 0, B - Assert.Equal(0, buffer[4]); // 1, 0, G - Assert.Equal(0, buffer[5]); // 1, 0, R - - Assert.Equal(0, buffer[6]); // 0, 1, B - Assert.Equal(0, buffer[7]); // 0, 1, G - Assert.Equal(255, buffer[8]); // 0, 1, R - - Assert.Equal(255, buffer[9]); // 1, 1, B - Assert.Equal(0, buffer[10]); // 1, 1, G - Assert.Equal(0, buffer[11]); // 1, 1, R + image.ComparePixelBufferTo(buffer.AsSpan().NonPortableCast()); } } - + [Fact] - public void SavePixelData_Rgba32_Buffer_must_be_bigger() + public void SavePixelData_Rgba32_WhenBufferIsTooSmall_Throws() { using (var img = new Image(2, 2)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index b3dd763c7..b367ecbd9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -17,6 +17,8 @@ namespace SixLabors.ImageSharp.Tests using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; + using Xunit; + public static class TestImageExtensions { /// @@ -81,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests grayscale, appendPixelTypeToFileName); } - + /// /// Compares the image against the expected Reference output, throws an exception if the images are not similar enough. /// The output file should be named identically to the output produced by . @@ -153,6 +155,24 @@ namespace SixLabors.ImageSharp.Tests } } + public static Image ComparePixelBufferTo( + this Image image, + Span expectedPixels) + where TPixel : struct, IPixel + { + Span actual = image.GetPixelSpan(); + + Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); + + for (int i = 0; i < expectedPixels.Length; i++) + { + Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!" ); + } + + return image; + } + + public static Image CompareToOriginal( this Image image, ITestImageProvider provider) @@ -160,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests { return CompareToOriginal(image, provider, ImageComparer.Tolerant()); } - + public static Image CompareToOriginal( this Image image, ITestImageProvider provider, From 18c1068e338f1fb018c55d4cd218cd39f44255ec Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 16 Sep 2017 17:14:23 +0200 Subject: [PATCH 10/29] unrelated to PR: Skipping AVX2-tests if the CPU does not support AVX2 --- src/ImageSharp/Common/Extensions/SimdUtils.cs | 4 +-- .../PixelFormats/Rgba32.PixelOperations.cs | 2 +- .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 30 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs index cb80a672a..d6b2fad09 100644 --- a/src/ImageSharp/Common/Extensions/SimdUtils.cs +++ b/src/ImageSharp/Common/Extensions/SimdUtils.cs @@ -17,11 +17,11 @@ namespace SixLabors.ImageSharp /// /// Indicates AVX2 architecture where both float and integer registers are of size 256 byte. /// - public static readonly bool IsAvx2 = Vector.Count == 8 && Vector.Count == 8; + public static readonly bool IsAvx2CompatibleArchitecture = Vector.Count == 8 && Vector.Count == 8; internal static void GuardAvx2(string operation) { - if (!IsAvx2) + if (!IsAvx2CompatibleArchitecture) { throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); } diff --git a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs index 6f4f93d87..552ac0a01 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp { GuardSpans(sourceVectors, nameof(sourceVectors), destColors, nameof(destColors), count); - if (!SimdUtils.IsAvx2) + if (!SimdUtils.IsAvx2CompatibleArchitecture) { base.PackFromVector4(sourceVectors, destColors, count); return; diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 44762a243..e5f2fd5e7 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -109,6 +109,16 @@ namespace SixLabors.ImageSharp.Tests.Common AssertEvenRoundIsCorrect(r, v); } + private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); + return true; + } + return false; + } + [Theory] [InlineData(1, 0)] [InlineData(1, 8)] @@ -116,6 +126,11 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(3, 128)] public void BulkConvertNormalizedFloatToByte_WithRoundedData(int seed, int count) { + if (this.SkipOnNonAvx2()) + { + return; + } + float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, 0, 256); float[] normalized = orig.Select(f => f / 255f).ToArray(); @@ -135,6 +150,11 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(3, 128)] public void BulkConvertNormalizedFloatToByte_WithNonRoundedData(int seed, int count) { + if (this.SkipOnNonAvx2()) + { + return; + } + float[] source = new Random(seed).GenerateRandomFloatArray(count, 0, 1f); byte[] dest = new byte[count]; @@ -155,6 +175,11 @@ namespace SixLabors.ImageSharp.Tests.Common [InlineData(3, 128)] public void BulkConvertNormalizedFloatToByteClampOverflows(int seed, int count) { + if (this.SkipOnNonAvx2()) + { + return; + } + float[] orig = new Random(seed).GenerateRandomRoundedFloatArray(count, -50, 444); float[] normalized = orig.Select(f => f / 255f).ToArray(); @@ -185,6 +210,11 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] private void BulkConvertNormalizedFloatToByte_Step() { + if (this.SkipOnNonAvx2()) + { + return; + } + float[] source = {0, 7, 42, 255, 0.5f, 1.1f, 2.6f, 16f}; byte[] expected = source.Select(f => (byte)Math.Round(f)).ToArray(); From 043373972025d4d833725d12386b161ec19ac96c Mon Sep 17 00:00:00 2001 From: Ryan Elian Date: Sat, 16 Sep 2017 23:46:31 +0700 Subject: [PATCH 11/29] Updated README with package badges linked to install page (NuGet / MyGet). --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31ca5cef3..13161220b 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,12 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb ### Installation -At present the code is pre-release but when ready it will be available on [Nuget](http://www.nuget.org). -**Pre-release downloads** - -We already have a [MyGet package repository](https://www.myget.org/gallery/sixlabors) - for bleeding-edge / development NuGet releases. +| Package Name | Release (NuGet) | Nightly (MyGet) | +|--------------------------------|-----------------|-----------------| +| `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) | +| `SixLabors.ImageSharp.Drawing` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Drawing.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Drawing/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Drawing.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Drawing) | +| `SixLabors.ImageSharp.Web` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Web.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Web/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Web.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Web) | ### Packages @@ -49,6 +50,9 @@ Packages include: - Various vector drawing methods for drawing paths, polygons etc. - Text drawing. +- **SixLabors.ImageSharp.Web** + - Extensible middleware for ASP.NET Core projects that allows processing and caching of images. + ### Manual build If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need: From 1406761cf1451afb2f05b10bc3058d7c3f7bca2c Mon Sep 17 00:00:00 2001 From: Ryan Elian Date: Sun, 17 Sep 2017 00:11:39 +0700 Subject: [PATCH 12/29] Removed ImageSharp.Web from README. --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 13161220b..9ae2fb535 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb |--------------------------------|-----------------|-----------------| | `SixLabors.ImageSharp` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp) | | `SixLabors.ImageSharp.Drawing` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Drawing.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Drawing/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Drawing.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Drawing) | -| `SixLabors.ImageSharp.Web` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Web.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Web/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Web.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Web) | ### Packages @@ -50,9 +49,6 @@ Packages include: - Various vector drawing methods for drawing paths, polygons etc. - Text drawing. -- **SixLabors.ImageSharp.Web** - - Extensible middleware for ASP.NET Core projects that allows processing and caching of images. - ### Manual build If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need: From 27d5152ed8bcbd6b957f41551bf456f729bc4493 Mon Sep 17 00:00:00 2001 From: Anton Firsov Date: Sat, 16 Sep 2017 20:15:23 +0200 Subject: [PATCH 13/29] Update & Cleanup README --- README.md | 82 ++++++++++++++++++++++--------------------------------- 1 file changed, 32 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 9ae2fb535..b1907cb39 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,19 @@ -# ImageSharp ImageSharp - -**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. - -Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. - -> **ImageSharp** has made excellent progress and contains many great features but is still considered by us to be still in early stages (alpha). As such, we cannot support its use on production environments until the library reaches release candidate status. -> -> Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/sixlabors). - [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt) -[![GitHub issues](https://img.shields.io/github/issues/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/issues) -[![GitHub stars](https://img.shields.io/github/stars/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/network) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) [![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors) +# ImageSharp ImageSharp +**ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. -| |Build Status|Code Coverage| -|-------------|:----------:|:-----------:| -|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +### Questions? + +Do you have questions? We are happy to help! Please [join our gitter channel](https://gitter.im/ImageSharp/General), or ask them on [stackoverflow](https://stackoverflow.com) using the `ImageSharp` tag. ### Installation @@ -34,10 +23,7 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb | `SixLabors.ImageSharp.Drawing` | [![NuGet](https://img.shields.io/nuget/v/SixLabors.ImageSharp.Drawing.svg)](https://www.nuget.org/packages/SixLabors.ImageSharp.Drawing/) | [![MyGet](https://img.shields.io/myget/sixlabors/v/SixLabors.ImageSharp.Drawing.svg)](https://www.myget.org/feed/sixlabors/package/nuget/SixLabors.ImageSharp.Drawing) | ### Packages - -The **ImageSharp** library is made up of multiple packages. - -Packages include: +The **ImageSharp** library is made up of multiple packages: - **SixLabors.ImageSharp** - Contains the generic `Image` class, PixelFormats, Primitives, Configuration, and other core functionality. - The `IImageFormat` interface, Jpeg, Png, Bmp, and Gif formats. @@ -49,23 +35,12 @@ Packages include: - Various vector drawing methods for drawing paths, polygons etc. - Text drawing. -### Manual build - -If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need: - -- [Visual Studio 2017 (or above)](https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes) -- The [.NET Core SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link. - -Alternatively on Linux you can use: - -- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) -- [.Net Core](https://www.microsoft.com/net/core#linuxubuntu) +### Build Status -To clone it locally click the "Clone in Windows" button above or run the following git commands. - -```bash -git clone https://github.com/SixLabors/ImageSharp -``` +| |Build Status|Code Coverage| +|-------------|:----------:|:-----------:| +|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| ### Features @@ -73,16 +48,8 @@ There's plenty there and more coming. Check out the [current features](features. ### API -Without the constraints of `System.Drawing` We have been able to develop something much more flexible, easier to code against, and much, much less prone to memory leaks. - -Gone are system-wide process-locks; ImageSharp images are thread-safe and fully supported in web environments. - -Many `Image` methods are also fluent. - Here's an example of the code required to resize an image using the default Bicubic resampler then turn the colors into their grayscale equivalent using the BT709 standard matrix. -`Rgba32` is our default PixelFormat, equivalent to `System.Drawing Color`. - On platforms supporting netstandard 1.3+ ```csharp // Image.Load(string path) is a shortcut for our default type. Other pixel formats use Image.Load(string path)) @@ -118,14 +85,29 @@ using (Image image = new Image(400, 400)) } ``` -For optimized synchronous access within a loop it is recommended that the following methods are used. +`Rgba32` is our default PixelFormat, equivalent to `System.Drawing Color`. For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. -1. `image.GetRowSpan(y)` -2. `image.GetRowSpan(x, y)` +All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. -For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +Check out [this blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) or our [samples repository](https://github.com/SixLabors/ImageSharp/tree/master/samples) for more examples! -All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. +### Manual build + +If you prefer, you can compile ImageSharp yourself (please do and help!), you'll need: + +- [Visual Studio 2017 (or above)](https://www.visualstudio.com/en-us/news/releasenotes/vs2017-relnotes) +- The [.NET Core SDK Installer](https://www.microsoft.com/net/core#windows) - Non VSCode link. + +Alternatively on Linux you can use: + +- [Visual Studio Code](https://code.visualstudio.com/) with [C# Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) +- [.Net Core](https://www.microsoft.com/net/core#linuxubuntu) + +To clone it locally click the "Clone in Windows" button above or run the following git commands. + +```bash +git clone https://github.com/SixLabors/ImageSharp +``` ### How can you help? From a5e4568b8f32e0590b15132a254739d0130214b6 Mon Sep 17 00:00:00 2001 From: Anton Firsov Date: Sat, 16 Sep 2017 20:55:03 +0200 Subject: [PATCH 14/29] Linking our brand-new Samples repository --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1907cb39..9206e5b96 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ using (Image image = new Image(400, 400)) All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. -Check out [this blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) or our [samples repository](https://github.com/SixLabors/ImageSharp/tree/master/samples) for more examples! +Check out [this blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) or our [Samples repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) for more examples! ### Manual build From 5c62132ba9a1884e15fa760ef424ee38b71bf15d Mon Sep 17 00:00:00 2001 From: Anton Firsov Date: Sat, 16 Sep 2017 20:59:29 +0200 Subject: [PATCH 15/29] using bold text to highlight references to additional examples --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9206e5b96..03ca0653f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ using (Image image = new Image(400, 400)) All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. -Check out [this blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) or our [Samples repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) for more examples! +**Check out [this blog post](https://sixlabors.com/blog/announcing-imagesharp-beta-1/) or our [Samples Repository](https://github.com/SixLabors/Samples/tree/master/ImageSharp) for more examples!** ### Manual build From 37866b78a0cdead747941abb2c1582eb82af2c60 Mon Sep 17 00:00:00 2001 From: Anton Firsov Date: Sat, 16 Sep 2017 22:34:03 +0200 Subject: [PATCH 16/29] Disable benchmark tests --- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index 063f42c00..a228bc236 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -34,15 +34,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg444, }; - [Theory] // Benchmark, enable manually - [MemberData(nameof(DecodeJpegData))] + // [Theory] // Benchmark, enable manually + // [MemberData(nameof(DecodeJpegData))] public void DecodeJpeg_Original(string fileName) { this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder()); } - [Theory] // Benchmark, enable manually - [MemberData(nameof(DecodeJpegData))] + // [Theory] // Benchmark, enable manually + // [MemberData(nameof(DecodeJpegData))] public void DecodeJpeg_PdfJs(string fileName) { this.DecodeJpegBenchmarkImpl(fileName, new PdfJsJpegDecoder()); @@ -106,4 +106,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } -} \ No newline at end of file +} From 2a7ce4cd90bbb767e2b1de2a706b11a384b3ff34 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 00:48:33 +0200 Subject: [PATCH 17/29] fix rounding in YCbCr conversion, started refactoring dequantiziation logic --- .../Formats/Jpeg/Common/Block8x8F.cs | 33 +++++++++++--- .../Common/Decoder/JpegBlockPostProcessor.cs | 20 ++++----- .../Decoder/JpegColorConverter.FromYCbCr.cs | 44 ++++++++++++++++--- .../Decoder/JpegComponentPostProcessor.cs | 4 +- .../Jpeg/Common/FastFloatingPointDCT.cs | 4 +- .../Formats/Jpeg/Common/UnzigData.cs | 1 - .../Formats/Jpg/Block8x8FTests.cs | 38 +++++++++++++++- .../Jpg/Utils/ReferenceImplementations.cs | 15 +++++++ .../TestUtilities/ApproximateFloatComparer.cs | 2 +- .../ImageComparison/ImageSimilarityReport.cs | 21 +++++++-- .../ImageProviders/FileProvider.cs | 9 ++-- 11 files changed, 154 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 191fa50ca..3ef498e10 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -421,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Vector to multiply by [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyAllInplace(float scaleVec) + public void MultiplyInplace(float scaleVec) { this.V0L *= scaleVec; this.V0R *= scaleVec; @@ -441,6 +441,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common this.V7R *= scaleVec; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MultiplyInplace(ref Block8x8F other) + { + this.V0L *= other.V0L; + this.V0R *= other.V0R; + this.V1L *= other.V1L; + this.V1R *= other.V1R; + this.V2L *= other.V2L; + this.V2R *= other.V2R; + this.V3L *= other.V3L; + this.V3R *= other.V3R; + this.V4L *= other.V4L; + this.V4R *= other.V4R; + this.V5L *= other.V5L; + this.V5R *= other.V5R; + this.V6L *= other.V6L; + this.V6R *= other.V6R; + this.V7L *= other.V7L; + this.V7R *= other.V7R; + } + /// /// Adds a vector to all elements of the block. /// @@ -472,16 +493,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Block pointer /// Qt pointer /// Unzig pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) { float* b = (float*)blockPtr; float* qtp = (float*)qtPtr; - for (int zig = 0; zig < Size; zig++) + for (int qtIndex = 0; qtIndex < Size; qtIndex++) { - float* unzigPos = b + unzigPtr[zig]; + int blockIndex = unzigPtr[qtIndex]; + float* unzigPos = b + blockIndex; + float val = *unzigPos; - val *= qtp[zig]; + val *= qtp[qtIndex]; *unzigPos = val; } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 4c4701753..e679fddcf 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -23,21 +23,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// private DataPointers pointers; + private Size subSamplingDivisors; + /// /// Initialize the instance on the stack. /// - /// The instance - public static void Init(JpegBlockPostProcessor* postProcessor) + public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component) { postProcessor->data = ComputationData.Create(); postProcessor->pointers = new DataPointers(&postProcessor->data); + + int qtIndex = component.QuantizationTableIndex; + postProcessor->data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + postProcessor->subSamplingDivisors = component.SubSamplingDivisors; } - public void QuantizeAndTransform(IRawJpegData decoder, IJpegComponent component, ref Block8x8 sourceBlock) + public void QuantizeAndTransform(ref Block8x8 sourceBlock) { this.data.SourceBlock = sourceBlock.AsFloatBlock(); - int qtIndex = component.QuantizationTableIndex; - this.data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; Block8x8F* b = this.pointers.SourceBlock; @@ -47,12 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder } public void ProcessBlockColorsInto( - IRawJpegData decoder, - IJpegComponent component, ref Block8x8 sourceBlock, BufferArea destArea) { - this.QuantizeAndTransform(decoder, component, ref sourceBlock); + this.QuantizeAndTransform(ref sourceBlock); this.data.WorkspaceBlock1.NormalizeColorsInplace(); @@ -61,8 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder // Unfortunately, we need to emulate this to be "more accurate" :( this.data.WorkspaceBlock1.RoundInplace(); - Size divs = component.SubSamplingDivisors; - this.data.WorkspaceBlock1.CopyTo(destArea, divs.Width, divs.Height); + this.data.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } /// diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs index 059b2e89a..693afc6f2 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -121,9 +121,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder tmp.MultiplyInplace(1.772F); b.AddInplace(ref tmp); - r.RoundAndDownscale(); - g.RoundAndDownscale(); - b.RoundAndDownscale(); + if (Vector.Count == 4) + { + // TODO: Find a way to properly run & test this path on modern AVX2 PC-s! (Have I already mentioned that Vector is terrible?) + r.RoundAndDownscaleBasic(); + g.RoundAndDownscaleBasic(); + b.RoundAndDownscaleBasic(); + } + else if (Vector.Count == 8) + { + r.RoundAndDownscaleAvx2(); + g.RoundAndDownscaleAvx2(); + b.RoundAndDownscaleAvx2(); + } + else + { + // TODO: Run fallback scalar code here + // However, no issues expected before someone implements this: https://github.com/dotnet/coreclr/issues/12007 + throw new NotImplementedException("Your CPU architecture is too modern!"); + } // Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); @@ -145,17 +161,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder private static readonly Vector4 Half = new Vector4(0.5f); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RoundAndDownscale() + public void RoundAndDownscaleBasic() { - // Emulate rounding: - this.A += Half; - this.B += Half; + ref Vector a = ref Unsafe.As>(ref this.A); + a = a.FastRound(); + + ref Vector b = ref Unsafe.As>(ref this.B); + b = b.FastRound(); // Downscale by 1/255 this.A *= Scale; this.B *= Scale; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RoundAndDownscaleAvx2() + { + ref Vector self = ref Unsafe.As>(ref this); + Vector v = self; + v = v.FastRound(); + + // Downscale by 1/255 + v *= new Vector(1 / 255f); + self = v; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MultiplyInplace(float value) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs index feb5164d7..53aafed01 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public unsafe void CopyBlocksToColorBuffer() { var blockPp = default(JpegBlockPostProcessor); - JpegBlockPostProcessor.Init(&blockPp); + JpegBlockPostProcessor.Init(&blockPp, this.ImagePostProcessor.RawJpeg, this.Component); for (int y = 0; y < this.BlockRowsPerStep; y++) { @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder this.blockAreaSize.Width, this.blockAreaSize.Height); - blockPp.ProcessBlockColorsInto(this.ImagePostProcessor.RawJpeg, this.Component, ref block, destArea); + blockPp.ProcessBlockColorsInto(ref block, destArea); } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs index 8a4f56e3d..bdea14793 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common IDCT8x4_RightPart(ref temp, ref dest); // TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing? - dest.MultiplyAllInplace(C_0_125); + dest.MultiplyInplace(C_0_125); } /// @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common FDCT8x4_LeftPart(ref temp, ref dest); FDCT8x4_RightPart(ref temp, ref dest); - dest.MultiplyAllInplace(C_0_125); + dest.MultiplyInplace(C_0_125); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs index e243938e3..aaefbb3af 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs @@ -6,7 +6,6 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Common { /// - /// TODO: This should be simply just a ! /// Holds the Jpeg UnZig array in a value/stack type. /// Unzig maps from the zigzag ordering to the natural ordering. For example, /// unzig[3] is the column and row of the fourth element in zigzag order. The diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 52c38bee8..59fa19be4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] data = Create8x8FloatData(); var block = new Block8x8F(); block.LoadFrom(data); - block.MultiplyAllInplace(5); + block.MultiplyInplace(5); int stride = 256; int height = 42; @@ -370,5 +370,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual); } } + + [Fact] + public void MultiplyInplace_ByOtherBlock() + { + Block8x8F original = CreateRandomFloatBlock(-500, 500, 42); + Block8x8F m = CreateRandomFloatBlock(-500, 500, 42); + + Block8x8F actual = original; + + actual.MultiplyInplace(ref m); + + for (int i = 0; i < Block8x8F.Size; i++) + { + Assert.Equal(original[i]*m[i], actual[i]); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public unsafe void DequantizeBlock(int seed) + { + Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); + Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); + + var unzig = UnzigData.Create(); + + Block8x8F expected = original; + Block8x8F actual = original; + + ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); + Block8x8F.DequantizeBlock(&actual, &qt, unzig.Data); + + this.CompareBlocks(expected, actual, 0); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index c8240bf08..38339e58f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -19,6 +19,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// internal static partial class ReferenceImplementations { + public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, int* unzigPtr) + { + float* b = (float*)blockPtr; + float* qtp = (float*)qtPtr; + for (int qtIndex = 0; qtIndex < Block8x8F.Size; qtIndex++) + { + int i = unzigPtr[qtIndex]; + float* unzigPos = b + i; + + float val = *unzigPos; + val *= qtp[qtIndex]; + *unzigPos = val; + } + } + /// /// Transpose 8x8 block stored linearly in a (inplace) /// diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 6b3f6ccd2..70d4df273 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests { float d = x - y; - return d > -this.Eps && d < this.Eps; + return d >= -this.Eps && d <= this.Eps; } public int GetHashCode(float obj) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 78e390bbd..9501a6c88 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -26,9 +26,24 @@ // TODO: This should not be a nullable value! public float? TotalNormalizedDifference { get; } - public string DifferencePercentageString => this.TotalNormalizedDifference.HasValue - ? $"{this.TotalNormalizedDifference.Value * 100:0.0000}%" - : "?"; + public string DifferencePercentageString + { + get + { + if (!this.TotalNormalizedDifference.HasValue) + { + return "?"; + } + else if (this.TotalNormalizedDifference == 0) + { + return "0%"; + } + else + { + return $"{this.TotalNormalizedDifference.Value * 100:0.0000}%"; + } + } + } public PixelDifference[] Differences { get; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 8ff532b2f..0a5eb8f61 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -109,11 +109,10 @@ namespace SixLabors.ImageSharp.Tests { this.FilePath = filePath; } - - public FileProvider() - { - } - + + /// + /// Gets the file path relative to the "~/tests/images" folder + /// public string FilePath { get; private set; } public override string SourceFileOrDescription => this.FilePath; From f475e1b658879c1a014f68e2047e58deb06090e4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 01:16:40 +0200 Subject: [PATCH 18/29] improved Jpeg regression testing --- .../Formats/Jpg/JpegColorConverterTests.cs | 2 +- .../Formats/Jpg/JpegDecoderTests.cs | 126 ++++++++++++------ .../ImageProviders/FileProvider.cs | 6 + 3 files changed, 91 insertions(+), 43 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 706fa1e20..0197dd917 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class JpegColorConverterTests { - private const float Precision = 1/255f; + private const float Precision = 0.1f / 255; public static readonly TheoryData CommonConversionData = new TheoryData diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index d8d24224b..e26ab2cc9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -9,6 +9,7 @@ using System; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { + using System.Collections.Generic; using System.IO; using System.Linq; @@ -24,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using Xunit; using Xunit.Abstractions; + // TODO: Scatter test cases into multiple test classes public class JpegDecoderTests { public static string[] BaselineTestJpegs = @@ -50,16 +52,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, }; + private static readonly Dictionary CustomToleranceValues = new Dictionary + { + // Baseline: + [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, + [TestImages.Jpeg.Baseline.Bad.ExifUndefType] = 0.011f / 100, + [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + + // Progressive: + [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, + [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, + [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, + [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, + [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, + [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, + }; + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector; - // TODO: We should make this comparer less tolerant ... - private static readonly ImageComparer VeryTolerantJpegComparer = - ImageComparer.Tolerant(0.005f, perPixelManhattanThreshold: 4); + private const float BaselineTolerance_Orig = 0.001f / 100; + private const float BaselineTolerance_PdfJs = 0.005f; - // BUG: PDF.js output is wrong on spectral level! - private static readonly ImageComparer PdfJsProgressiveComparer = - ImageComparer.Tolerant(0.015f, perPixelManhattanThreshold: 4); + private const float ProgressiveTolerance_Orig = 0.2f / 100; + private const float ProgressiveTolerance_PdfJs = 1.5f / 100; // PDF.js Progressive output is wrong on spectral level! + private ImageComparer GetImageComparerForOrigDecoder(TestImageProvider provider) + where TPixel : struct, IPixel + { + string file = provider.SourceFileOrDescription; + + if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) + { + tolerance = file.ToLower().Contains("baseline") ? BaselineTolerance_Orig : ProgressiveTolerance_Orig; + } + + return ImageComparer.Tolerant(tolerance); + } public JpegDecoderTests(ITestOutputHelper output) { @@ -71,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static IImageDecoder OrigJpegDecoder => new OrigJpegDecoder(); private static IImageDecoder PdfJsJpegDecoder => new PdfJsJpegDecoder(); - + [Fact] public void ParseStream_BasicPropertiesAreCorrect1_PdfJs() { @@ -84,50 +113,56 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg VerifyJpeg.VerifyComponentSizes3(decoder.Frame.Components, 43, 61, 22, 31, 22, 31); } } - + public const string DecodeBaselineJpegOutputName = "DecodeBaselineJpeg"; + [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider) + [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)] + public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(PdfJsJpegDecoder)) + IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder; + using (Image image = provider.GetImage(decoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(provider, ImageComparer.Tolerant(BaselineTolerance_PdfJs), appendPixelTypeToFileName: false); } } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, false)] - [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes, true)] - public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider, bool useOldDecoder) + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] + public void DecodeBaselineJpeg_Orig(TestImageProvider provider) where TPixel : struct, IPixel { - IImageDecoder decoder = useOldDecoder ? OrigJpegDecoder : PdfJsJpegDecoder; - using (Image image = provider.GetImage(decoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { image.DebugSave(provider); - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + this.GetImageComparerForOrigDecoder(provider), + appendPixelTypeToFileName: false); } } - + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg_Orig(TestImageProvider provider) + public void DecodeBaselineJpeg_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(OrigJpegDecoder)) + using (Image image = provider.GetImage(PdfJsJpegDecoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + ImageComparer.Tolerant(BaselineTolerance_PdfJs), + appendPixelTypeToFileName: false); } } @@ -144,37 +179,43 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider) + public void DecodeProgressiveJpeg_Orig(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(PdfJsJpegDecoder)) + using (Image image = provider.GetImage(OrigJpegDecoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput(provider, PdfJsProgressiveComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + this.GetImageComparerForOrigDecoder(provider), + appendPixelTypeToFileName: false); } } [Theory] [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg_Orig(TestImageProvider provider) + public void DecodeProgressiveJpeg_PdfJs(TestImageProvider provider) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(OrigJpegDecoder)) + using (Image image = provider.GetImage(PdfJsJpegDecoder)) { image.DebugSave(provider); provider.Utility.TestName = DecodeProgressiveJpegOutputName; - image.CompareToReferenceOutput(provider, VeryTolerantJpegComparer, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput( + provider, + ImageComparer.Tolerant(ProgressiveTolerance_PdfJs), + appendPixelTypeToFileName: false); } } - - private float GetDifferenceInPercents(Image image, TestImageProvider provider) + + private string GetDifferenceInPercentageString(Image image, TestImageProvider provider) where TPixel : struct, IPixel { var reportingComparer = ImageComparer.Tolerant(0, 0); - + ImageSimilarityReport report = image.GetReferenceOutputSimilarityReports( provider, reportingComparer, @@ -183,10 +224,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (report != null && report.TotalNormalizedDifference.HasValue) { - return report.TotalNormalizedDifference.Value * 100; + return report.DifferencePercentageString; } - return 0; + return "0%"; } private void CompareJpegDecodersImpl(TestImageProvider provider, string testName) @@ -202,14 +243,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Image image = provider.GetImage(OrigJpegDecoder)) { - double d = this.GetDifferenceInPercents(image, provider); - this.Output.WriteLine($"Difference using ORIGINAL decoder: {d:0.0000}%"); + string d = this.GetDifferenceInPercentageString(image, provider); + + this.Output.WriteLine($"Difference using ORIGINAL decoder: {d}"); } using (Image image = provider.GetImage(PdfJsJpegDecoder)) { - double d = this.GetDifferenceInPercents(image, provider); - this.Output.WriteLine($"Difference using PDFJS decoder: {d:0.0000}%"); + string d = this.GetDifferenceInPercentageString(image, provider); + this.Output.WriteLine($"Difference using PDFJS decoder: {d}"); } } @@ -228,7 +270,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { this.CompareJpegDecodersImpl(provider, DecodeProgressiveJpegOutputName); } - + [Theory] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 75)] [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 100)] @@ -256,7 +298,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var mirror = Image.Load(data, OrigJpegDecoder); mirror.DebugSave(provider, $"_{subsample}_Q{quality}"); } - + [Fact] public void Decoder_Reads_Correct_Resolution_From_Jfif() { @@ -314,7 +356,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // into "\tests\Images\ActualOutput\JpegDecoderTests\" //[Theory] //[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] - public void ValidateProgressivePdfJsOutput(TestImageProvider provider, + public void ValidateProgressivePdfJsOutput(TestImageProvider provider, string pdfJsOriginalResultImage) where TPixel : struct, IPixel { @@ -335,7 +377,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { ImageSimilarityReport originalReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsOriginalResult); ImageSimilarityReport portReport = comparer.CompareImagesOrFrames(expectedImage, pdfJsPortResult); - + this.Output.WriteLine($"Difference for PDF.js ORIGINAL: {originalReport.DifferencePercentageString}"); this.Output.WriteLine($"Difference for PORT: {portReport.DifferencePercentageString}"); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 0a5eb8f61..92332eba0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -105,6 +105,12 @@ namespace SixLabors.ImageSharp.Tests private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + // Needed for deserialization! + // ReSharper disable once UnusedMember.Local + public FileProvider() + { + } + public FileProvider(string filePath) { this.FilePath = filePath; From e6ef197bcbc4b9c4d2f6b1bd8ad9b0772ff0cd35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 01:30:27 +0200 Subject: [PATCH 20/29] FormattableString, why are you so cruel to Eastern Europeans? --- .../TestUtilities/ImagingTestCaseUtility.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 5867eaf85..7fd7a59a9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -89,6 +89,8 @@ namespace SixLabors.ImageSharp.Tests return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{details}{extension}"; } + private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable); + /// /// Gets the recommended file name for the output of the test /// @@ -111,14 +113,16 @@ namespace SixLabors.ImageSharp.Tests TypeInfo info = type.GetTypeInfo(); if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) { - detailsString = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", testOutputDetails); + detailsString = Inv($"{testOutputDetails}"); } else { IEnumerable properties = testOutputDetails.GetType().GetRuntimeProperties(); - detailsString = String.Join( - "_", properties.ToDictionary(x => x.Name, x => string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", x.GetValue(testOutputDetails))).Select(x => $"{x.Key}-{x.Value}") + detailsString = string.Join( + "_", + properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) + .Select(x => Inv($"{x.Key}-{x.Value}")) ); } } From 005a454b40c8f67c1e4c006ddfcbcd0dcc0ab094 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 02:22:13 +0200 Subject: [PATCH 21/29] optimized quantization --- .../Common/Decoder/JpegBlockPostProcessor.cs | 28 ++++++++----------- .../Jpeg/Common/{UnzigData.cs => ZigZag.cs} | 23 ++++++++++++--- .../OrigJpegScanDecoder.ComputationData.cs | 4 +-- .../Jpeg/GolangPort/JpegEncoderCore.cs | 4 +-- .../Formats/Jpg/Block8x8FTests.cs | 26 +++++++++++++++-- .../Formats/Jpg/JpegProfilingBenchmarks.cs | 4 +-- .../Jpg/Utils/ReferenceImplementations.cs | 2 +- 7 files changed, 62 insertions(+), 29 deletions(-) rename src/ImageSharp/Formats/Jpeg/Common/{UnzigData.cs => ZigZag.cs} (70%) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index e679fddcf..7139260ec 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -34,26 +34,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder postProcessor->pointers = new DataPointers(&postProcessor->data); int qtIndex = component.QuantizationTableIndex; - postProcessor->data.QuantiazationTable = decoder.QuantizationTables[qtIndex]; + postProcessor->data.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); postProcessor->subSamplingDivisors = component.SubSamplingDivisors; } - public void QuantizeAndTransform(ref Block8x8 sourceBlock) + public void ProcessBlockColorsInto( + ref Block8x8 sourceBlock, + BufferArea destArea) { this.data.SourceBlock = sourceBlock.AsFloatBlock(); Block8x8F* b = this.pointers.SourceBlock; - Block8x8F.DequantizeBlock(b, this.pointers.QuantiazationTable, this.pointers.Unzig); + // Dequantize: + b->MultiplyInplace(ref this.data.DequantiazationTable); FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2); - } - - public void ProcessBlockColorsInto( - ref Block8x8 sourceBlock, - BufferArea destArea) - { - this.QuantizeAndTransform(ref sourceBlock); this.data.WorkspaceBlock1.NormalizeColorsInplace(); @@ -89,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// The quantization table as /// - public Block8x8F QuantiazationTable; + public Block8x8F DequantiazationTable; /// /// The jpeg unzig data /// - public UnzigData Unzig; + public ZigZag Unzig; /// /// Creates and initializes a new instance @@ -103,7 +99,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public static ComputationData Create() { var data = default(ComputationData); - data.Unzig = UnzigData.Create(); + data.Unzig = ZigZag.CreateUnzigTable(); return data; } } @@ -129,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder public Block8x8F* WorkspaceBlock2; /// - /// Pointer to + /// Pointer to /// - public Block8x8F* QuantiazationTable; + public Block8x8F* DequantiazationTable; /// /// Pointer to as int* @@ -147,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder this.SourceBlock = &dataPtr->SourceBlock; this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1; this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2; - this.QuantiazationTable = &dataPtr->QuantiazationTable; + this.DequantiazationTable = &dataPtr->DequantiazationTable; this.Unzig = dataPtr->Unzig.Data; } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs b/src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs similarity index 70% rename from src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs rename to src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs index aaefbb3af..18270f5ba 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/UnzigData.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/ZigZag.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// unzig[3] is the column and row of the fourth element in zigzag order. The /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). /// - internal unsafe struct UnzigData + internal unsafe struct ZigZag { /// /// Copy of in a value type @@ -32,15 +32,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common }; /// - /// Creates and fills an instance of with Jpeg unzig indices + /// Creates and fills an instance of with Jpeg unzig indices /// /// The new instance - public static UnzigData Create() + public static ZigZag CreateUnzigTable() { - UnzigData result = default(UnzigData); + ZigZag result = default(ZigZag); int* unzigPtr = result.Data; Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64); return result; } + + /// + /// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them. + /// + public static Block8x8F CreateDequantizationTable(ref Block8x8F qt) + { + Block8x8F result = default(Block8x8F); + + for (int i = 0; i < 64; i++) + { + result[Unzig[i]] = qt[i]; + } + + return result; + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs index 6252d8209..d426ed4f1 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/Components/Decoder/OrigJpegScanDecoder.ComputationData.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder /// /// The jpeg unzig data /// - public UnzigData Unzig; + public ZigZag Unzig; /// /// The buffer storing the -s for each component @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort.Components.Decoder public static ComputationData Create() { ComputationData data = default(ComputationData); - data.Unzig = UnzigData.Create(); + data.Unzig = ZigZag.CreateUnzigTable(); return data; } } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 984fb828c..3a24bded3 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -455,7 +455,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - UnzigData unzig = UnzigData.Create(); + ZigZag unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; @@ -912,7 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable; Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable; - UnzigData unzig = UnzigData.Create(); + ZigZag unzig = ZigZag.CreateUnzigTable(); // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 59fa19be4..66c9bb3e1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -309,7 +309,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var qt = new Block8x8F(); qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - var unzig = UnzigData.Create(); + var unzig = ZigZag.CreateUnzigTable(); int* expectedResults = stackalloc int[Block8x8F.Size]; ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data); @@ -396,7 +396,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); - var unzig = UnzigData.Create(); + var unzig = ZigZag.CreateUnzigTable(); Block8x8F expected = original; Block8x8F actual = original; @@ -406,5 +406,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0); } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + public unsafe void ZigZag_CreateDequantizationTable_MultiplicationShouldQuantize(int seed) + { + Block8x8F original = CreateRandomFloatBlock(-500, 500, seed); + Block8x8F qt = CreateRandomFloatBlock(0, 10, seed + 42); + + var unzig = ZigZag.CreateUnzigTable(); + Block8x8F zigQt = ZigZag.CreateDequantizationTable(ref qt); + + Block8x8F expected = original; + Block8x8F actual = original; + + ReferenceImplementations.DequantizeBlock(&expected, &qt, unzig.Data); + + actual.MultiplyInplace(ref zigQt); + + this.CompareBlocks(expected, actual, 0); + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index 063f42c00..a1ff3550a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -41,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder()); } - [Theory] // Benchmark, enable manually - [MemberData(nameof(DecodeJpegData))] + //[Theory] // Benchmark, enable manually + //[MemberData(nameof(DecodeJpegData))] public void DecodeJpeg_PdfJs(string fileName) { this.DecodeJpegBenchmarkImpl(fileName, new PdfJsJpegDecoder()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index 38339e58f..f3722b5d6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// The input block /// The destination block of integers /// The quantization table - /// Pointer to + /// Pointer to public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) { float* s = (float*)src; From c1c49f64af7da2de01b6fe1aa6febefeaa4a4c35 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 02:36:35 +0200 Subject: [PATCH 22/29] inlined stuff in CopyTo2x2() --- .../Formats/Jpeg/Common/Block8x8F.CopyTo.cs | 146 ++++++++++++++++++ .../Formats/Jpeg/Common/Block8x8F.cs | 106 +------------ 2 files changed, 147 insertions(+), 105 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs new file mode 100644 index 000000000..c1c5cfcde --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.CopyTo.cs @@ -0,0 +1,146 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Formats.Jpeg.Common +{ + internal partial struct Block8x8F + { + /// + /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical. + /// + public void CopyTo(BufferArea area, int horizontalScale, int verticalScale) + { + if (horizontalScale == 1 && verticalScale == 1) + { + this.CopyTo(area); + return; + } + else if (horizontalScale == 2 && verticalScale == 2) + { + this.CopyTo2x2(area); + return; + } + + // TODO: Optimize: implement all the cases with loopless special code! (T4?) + for (int y = 0; y < 8; y++) + { + int yy = y * verticalScale; + int y8 = y * 8; + + for (int x = 0; x < 8; x++) + { + int xx = x * horizontalScale; + + float value = this[y8 + x]; + + for (int i = 0; i < verticalScale; i++) + { + for (int j = 0; j < horizontalScale; j++) + { + area[xx + j, yy + i] = value; + } + } + } + } + } + + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyTo(BufferArea area) + { + ref byte selfBase = ref Unsafe.As(ref this); + ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); + int destStride = area.Stride * sizeof(float); + + CopyRowImpl(ref selfBase, ref destBase, destStride, 0); + CopyRowImpl(ref selfBase, ref destBase, destStride, 1); + CopyRowImpl(ref selfBase, ref destBase, destStride, 2); + CopyRowImpl(ref selfBase, ref destBase, destStride, 3); + CopyRowImpl(ref selfBase, ref destBase, destStride, 4); + CopyRowImpl(ref selfBase, ref destBase, destStride, 5); + CopyRowImpl(ref selfBase, ref destBase, destStride, 6); + CopyRowImpl(ref selfBase, ref destBase, destStride, 7); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) + { + ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); + ref byte d = ref Unsafe.Add(ref destBase, row * destStride); + Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); + } + + private void CopyTo2x2(BufferArea area) + { + ref float destBase = ref area.GetReferenceToOrigo(); + int destStride = area.Stride; + + this.WidenCopyImpl2x2(ref destBase, 0, destStride); + this.WidenCopyImpl2x2(ref destBase, 1, destStride); + this.WidenCopyImpl2x2(ref destBase, 2, destStride); + this.WidenCopyImpl2x2(ref destBase, 3, destStride); + this.WidenCopyImpl2x2(ref destBase, 4, destStride); + this.WidenCopyImpl2x2(ref destBase, 5, destStride); + this.WidenCopyImpl2x2(ref destBase, 6, destStride); + this.WidenCopyImpl2x2(ref destBase, 7, destStride); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void WidenCopyImpl2x2(ref float destBase, int row, int destStride) + { + ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row); + ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); + ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); + + Unsafe.Add(ref destLocalOrigo, 0) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 1) = selfLeft.X; + Unsafe.Add(ref destLocalOrigo, 2) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 3) = selfLeft.Y; + Unsafe.Add(ref destLocalOrigo, 4) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 5) = selfLeft.Z; + Unsafe.Add(ref destLocalOrigo, 6) = selfLeft.W; + Unsafe.Add(ref destLocalOrigo, 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, 8), 7) = selfRight.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 0) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 1) = selfLeft.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 2) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 3) = selfLeft.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 4) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 5) = selfLeft.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 6) = selfLeft.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride), 7) = selfLeft.W; + + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 0) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 1) = selfRight.X; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 2) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 3) = selfRight.Y; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 4) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 5) = selfRight.Z; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 6) = selfRight.W; + Unsafe.Add(ref Unsafe.Add(ref destLocalOrigo, destStride + 8), 7) = selfRight.W; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WidenCopyImpl(ref Vector4 s, ref float destBase) + { + Unsafe.Add(ref destBase, 0) = s.X; + Unsafe.Add(ref destBase, 1) = s.X; + Unsafe.Add(ref destBase, 2) = s.Y; + Unsafe.Add(ref destBase, 3) = s.Y; + Unsafe.Add(ref destBase, 4) = s.Z; + Unsafe.Add(ref destBase, 5) = s.Z; + Unsafe.Add(ref destBase, 6) = s.W; + Unsafe.Add(ref destBase, 7) = s.W; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 3ef498e10..aecd5c59e 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -7,7 +7,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; -using SixLabors.ImageSharp.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Formats.Jpeg.Common @@ -306,109 +305,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(BufferArea area) - { - ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigo()); - int destStride = area.Stride * sizeof(float); - - CopyRowImpl(ref selfBase, ref destBase, destStride, 0); - CopyRowImpl(ref selfBase, ref destBase, destStride, 1); - CopyRowImpl(ref selfBase, ref destBase, destStride, 2); - CopyRowImpl(ref selfBase, ref destBase, destStride, 3); - CopyRowImpl(ref selfBase, ref destBase, destStride, 4); - CopyRowImpl(ref selfBase, ref destBase, destStride, 5); - CopyRowImpl(ref selfBase, ref destBase, destStride, 6); - CopyRowImpl(ref selfBase, ref destBase, destStride, 7); - } - - public void CopyTo(BufferArea area, int horizontalScale, int verticalScale) - { - if (horizontalScale == 1 && verticalScale == 1) - { - this.CopyTo(area); - return; - } - else if (horizontalScale == 2 && verticalScale == 2) - { - this.CopyTo2x2(area); - return; - } - - // TODO: Optimize: implement all the cases with loopless special code! (T4?) - for (int y = 0; y < 8; y++) - { - int yy = y * verticalScale; - int y8 = y * 8; - - for (int x = 0; x < 8; x++) - { - int xx = x * horizontalScale; - - float value = this[y8 + x]; - - for (int i = 0; i < verticalScale; i++) - { - for (int j = 0; j < horizontalScale; j++) - { - area[xx + j, yy + i] = value; - } - } - } - } - } - - private void CopyTo2x2(BufferArea area) - { - ref float destBase = ref area.GetReferenceToOrigo(); - int destStride = area.Stride; - - this.CopyRow2x2Impl(ref destBase, 0, destStride); - this.CopyRow2x2Impl(ref destBase, 1, destStride); - this.CopyRow2x2Impl(ref destBase, 2, destStride); - this.CopyRow2x2Impl(ref destBase, 3, destStride); - this.CopyRow2x2Impl(ref destBase, 4, destStride); - this.CopyRow2x2Impl(ref destBase, 5, destStride); - this.CopyRow2x2Impl(ref destBase, 6, destStride); - this.CopyRow2x2Impl(ref destBase, 7, destStride); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CopyRowImpl(ref byte selfBase, ref byte destBase, int destStride, int row) - { - ref byte s = ref Unsafe.Add(ref selfBase, row * 8 * sizeof(float)); - ref byte d = ref Unsafe.Add(ref destBase, row * destStride); - Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void CopyRow2x2Impl(ref float destBase, int row, int destStride) - { - ref Vector4 selfLeft = ref Unsafe.Add(ref this.V0L, 2 * row); - ref Vector4 selfRight = ref Unsafe.Add(ref selfLeft, 1); - ref float destLocalOrigo = ref Unsafe.Add(ref destBase, row * 2 * destStride); - - Stride2VectorCopyImpl(ref selfLeft, ref destLocalOrigo); - Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, 8)); - - Stride2VectorCopyImpl(ref selfLeft, ref Unsafe.Add(ref destLocalOrigo, destStride)); - Stride2VectorCopyImpl(ref selfRight, ref Unsafe.Add(ref destLocalOrigo, destStride + 8)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Stride2VectorCopyImpl(ref Vector4 s, ref float destBase) - { - Unsafe.Add(ref destBase, 0) = s.X; - Unsafe.Add(ref destBase, 1) = s.X; - Unsafe.Add(ref destBase, 2) = s.Y; - Unsafe.Add(ref destBase, 3) = s.Y; - Unsafe.Add(ref destBase, 4) = s.Z; - Unsafe.Add(ref destBase, 5) = s.Z; - Unsafe.Add(ref destBase, 6) = s.W; - Unsafe.Add(ref destBase, 7) = s.W; - } - public float[] ToArray() { float[] result = new float[Size]; @@ -543,7 +439,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Source block /// Destination block /// The quantization table - /// Pointer to elements of + /// Pointer to elements of public static unsafe void UnzigDivRound( Block8x8F* block, Block8x8F* dest, From 58dbfb90cb057c4e60b33f0192df4a7c86e97dc4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 06:39:52 +0200 Subject: [PATCH 23/29] add library level tuples --- src/ImageSharp/Common/Tuples/Tuple8.cs | 251 ++++++++++++++++++ src/ImageSharp/Common/Tuples/Vector4Pair.cs | 73 +++++ .../Formats/Jpeg/Common/Block8x8.cs | 89 ++++++- .../Formats/Jpeg/Common/Block8x8F.cs | 2 +- .../Common/Decoder/JpegBlockPostProcessor.cs | 2 +- .../ImageSharp.Tests/Common/SimdUtilsTests.cs | 10 +- 6 files changed, 414 insertions(+), 13 deletions(-) create mode 100644 src/ImageSharp/Common/Tuples/Tuple8.cs create mode 100644 src/ImageSharp/Common/Tuples/Vector4Pair.cs diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs new file mode 100644 index 000000000..c0b9a9002 --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Tuple8.cs @@ -0,0 +1,251 @@ +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Common.Tuples +{ + /// + /// Contains value type tuples of 8 elements. + /// TODO: Should T4 this stuff to be DRY + /// + internal static class Tuple8 + { + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] + public struct OfSingle + { + [FieldOffset(0 * sizeof(float))] + public float V0; + + [FieldOffset(1 * sizeof(float))] + public float V1; + + [FieldOffset(2 * sizeof(float))] + public float V2; + + [FieldOffset(3 * sizeof(float))] + public float V3; + + [FieldOffset(4 * sizeof(float))] + public float V4; + + [FieldOffset(5 * sizeof(float))] + public float V5; + + [FieldOffset(6 * sizeof(float))] + public float V6; + + [FieldOffset(7 * sizeof(float))] + public float V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(int))] + public struct OfInt32 + { + [FieldOffset(0 * sizeof(int))] + public int V0; + + [FieldOffset(1 * sizeof(int))] + public int V1; + + [FieldOffset(2 * sizeof(int))] + public int V2; + + [FieldOffset(3 * sizeof(int))] + public int V3; + + [FieldOffset(4 * sizeof(int))] + public int V4; + + [FieldOffset(5 * sizeof(int))] + public int V5; + + [FieldOffset(6 * sizeof(int))] + public int V6; + + [FieldOffset(7 * sizeof(int))] + public int V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(uint))] + public struct OfUInt32 + { + [FieldOffset(0 * sizeof(uint))] + public uint V0; + + [FieldOffset(1 * sizeof(uint))] + public uint V1; + + [FieldOffset(2 * sizeof(uint))] + public uint V2; + + [FieldOffset(3 * sizeof(uint))] + public uint V3; + + [FieldOffset(4 * sizeof(uint))] + public uint V4; + + [FieldOffset(5 * sizeof(uint))] + public uint V5; + + [FieldOffset(6 * sizeof(uint))] + public uint V6; + + [FieldOffset(7 * sizeof(uint))] + public uint V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + + public void LoadFrom(ref OfUInt16 i) + { + this.V0 = i.V0; + this.V1 = i.V1; + this.V2 = i.V2; + this.V3 = i.V3; + this.V4 = i.V4; + this.V5 = i.V5; + this.V6 = i.V6; + this.V7 = i.V7; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(ushort))] + public struct OfUInt16 + { + [FieldOffset(0 * sizeof(ushort))] + public ushort V0; + + [FieldOffset(1 * sizeof(ushort))] + public ushort V1; + + [FieldOffset(2 * sizeof(ushort))] + public ushort V2; + + [FieldOffset(3 * sizeof(ushort))] + public ushort V3; + + [FieldOffset(4 * sizeof(ushort))] + public ushort V4; + + [FieldOffset(5 * sizeof(ushort))] + public ushort V5; + + [FieldOffset(6 * sizeof(ushort))] + public ushort V6; + + [FieldOffset(7 * sizeof(ushort))] + public ushort V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(short))] + public struct OfInt16 + { + [FieldOffset(0 * sizeof(short))] + public short V0; + + [FieldOffset(1 * sizeof(short))] + public short V1; + + [FieldOffset(2 * sizeof(short))] + public short V2; + + [FieldOffset(3 * sizeof(short))] + public short V3; + + [FieldOffset(4 * sizeof(short))] + public short V4; + + [FieldOffset(5 * sizeof(short))] + public short V5; + + [FieldOffset(6 * sizeof(short))] + public short V6; + + [FieldOffset(7 * sizeof(short))] + public short V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + } + + /// + /// Value type tuple of 8 -s + /// + [StructLayout(LayoutKind.Explicit, Size = 8)] + public struct OfByte + { + [FieldOffset(0)] + public byte V0; + + [FieldOffset(1)] + public byte V1; + + [FieldOffset(2)] + public byte V2; + + [FieldOffset(3)] + public byte V3; + + [FieldOffset(4)] + public byte V4; + + [FieldOffset(5)] + public byte V5; + + [FieldOffset(6)] + public byte V6; + + [FieldOffset(7)] + public byte V7; + + public override string ToString() + { + return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; + } + + public void LoadFrom(ref OfUInt32 i) + { + this.V0 = (byte)i.V0; + this.V1 = (byte)i.V1; + this.V2 = (byte)i.V2; + this.V3 = (byte)i.V3; + this.V4 = (byte)i.V4; + this.V5 = (byte)i.V5; + this.V6 = (byte)i.V6; + this.V7 = (byte)i.V7; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs new file mode 100644 index 000000000..1be936b30 --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -0,0 +1,73 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Common.Tuples +{ + /// + /// Its faster to process multiple Vector4-s together, so let's pair them! + /// On AVX2 this pair should be convertible to of ! + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Vector4Pair + { + public Vector4 A; + + public Vector4 B; + + private static readonly Vector4 Scale = new Vector4(1 / 255f); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void MultiplyInplace(float value) + { + this.A *= value; + this.B *= value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddInplace(Vector4 value) + { + this.A += value; + this.B += value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddInplace(ref Vector4Pair other) + { + this.A += other.A; + this.B += other.B; + } + + /// + /// Color-conversion specific downscale method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RoundAndDownscaleBasic() + { + ref Vector a = ref Unsafe.As>(ref this.A); + a = a.FastRound(); + + ref Vector b = ref Unsafe.As>(ref this.B); + b = b.FastRound(); + + // Downscale by 1/255 + this.A *= Scale; + this.B *= Scale; + } + + /// + /// AVX2-only color-conversion specific downscale method. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RoundAndDownscaleAvx2() + { + ref Vector self = ref Unsafe.As>(ref this); + Vector v = self; + v = v.FastRound(); + + // Downscale by 1/255 + v *= new Vector(1 / 255f); + self = v; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index 3f4c69c3e..1291f160a 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -176,17 +176,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } /// - /// Convert into + /// Convert to /// public Block8x8F AsFloatBlock() { - // TODO: Optimize this var result = default(Block8x8F); - for (int i = 0; i < Size; i++) - { - result[i] = this[i]; - } - + this.CopyToFloatBlock(ref result); return result; } @@ -302,5 +297,85 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } + + /// + /// Convert values into as -s + /// + public void CopyToFloatBlock(ref Block8x8F dest) + { + ref short selfRef = ref Unsafe.As(ref this); + + dest.V0L.X = Unsafe.Add(ref selfRef, 0); + dest.V0L.Y = Unsafe.Add(ref selfRef, 1); + dest.V0L.Z = Unsafe.Add(ref selfRef, 2); + dest.V0L.W = Unsafe.Add(ref selfRef, 3); + dest.V0R.X = Unsafe.Add(ref selfRef, 4); + dest.V0R.Y = Unsafe.Add(ref selfRef, 5); + dest.V0R.Z = Unsafe.Add(ref selfRef, 6); + dest.V0R.W = Unsafe.Add(ref selfRef, 7); + + dest.V1L.X = Unsafe.Add(ref selfRef, 8); + dest.V1L.Y = Unsafe.Add(ref selfRef, 9); + dest.V1L.Z = Unsafe.Add(ref selfRef, 10); + dest.V1L.W = Unsafe.Add(ref selfRef, 11); + dest.V1R.X = Unsafe.Add(ref selfRef, 12); + dest.V1R.Y = Unsafe.Add(ref selfRef, 13); + dest.V1R.Z = Unsafe.Add(ref selfRef, 14); + dest.V1R.W = Unsafe.Add(ref selfRef, 15); + + dest.V2L.X = Unsafe.Add(ref selfRef, 16); + dest.V2L.Y = Unsafe.Add(ref selfRef, 17); + dest.V2L.Z = Unsafe.Add(ref selfRef, 18); + dest.V2L.W = Unsafe.Add(ref selfRef, 19); + dest.V2R.X = Unsafe.Add(ref selfRef, 20); + dest.V2R.Y = Unsafe.Add(ref selfRef, 21); + dest.V2R.Z = Unsafe.Add(ref selfRef, 22); + dest.V2R.W = Unsafe.Add(ref selfRef, 23); + + dest.V3L.X = Unsafe.Add(ref selfRef, 24); + dest.V3L.Y = Unsafe.Add(ref selfRef, 25); + dest.V3L.Z = Unsafe.Add(ref selfRef, 26); + dest.V3L.W = Unsafe.Add(ref selfRef, 27); + dest.V3R.X = Unsafe.Add(ref selfRef, 28); + dest.V3R.Y = Unsafe.Add(ref selfRef, 29); + dest.V3R.Z = Unsafe.Add(ref selfRef, 30); + dest.V3R.W = Unsafe.Add(ref selfRef, 31); + + dest.V4L.X = Unsafe.Add(ref selfRef, 32); + dest.V4L.Y = Unsafe.Add(ref selfRef, 33); + dest.V4L.Z = Unsafe.Add(ref selfRef, 34); + dest.V4L.W = Unsafe.Add(ref selfRef, 35); + dest.V4R.X = Unsafe.Add(ref selfRef, 36); + dest.V4R.Y = Unsafe.Add(ref selfRef, 37); + dest.V4R.Z = Unsafe.Add(ref selfRef, 38); + dest.V4R.W = Unsafe.Add(ref selfRef, 39); + + dest.V5L.X = Unsafe.Add(ref selfRef, 40); + dest.V5L.Y = Unsafe.Add(ref selfRef, 41); + dest.V5L.Z = Unsafe.Add(ref selfRef, 42); + dest.V5L.W = Unsafe.Add(ref selfRef, 43); + dest.V5R.X = Unsafe.Add(ref selfRef, 44); + dest.V5R.Y = Unsafe.Add(ref selfRef, 45); + dest.V5R.Z = Unsafe.Add(ref selfRef, 46); + dest.V5R.W = Unsafe.Add(ref selfRef, 47); + + dest.V6L.X = Unsafe.Add(ref selfRef, 48); + dest.V6L.Y = Unsafe.Add(ref selfRef, 49); + dest.V6L.Z = Unsafe.Add(ref selfRef, 50); + dest.V6L.W = Unsafe.Add(ref selfRef, 51); + dest.V6R.X = Unsafe.Add(ref selfRef, 52); + dest.V6R.Y = Unsafe.Add(ref selfRef, 53); + dest.V6R.Z = Unsafe.Add(ref selfRef, 54); + dest.V6R.W = Unsafe.Add(ref selfRef, 55); + + dest.V7L.X = Unsafe.Add(ref selfRef, 56); + dest.V7L.Y = Unsafe.Add(ref selfRef, 57); + dest.V7L.Z = Unsafe.Add(ref selfRef, 58); + dest.V7L.W = Unsafe.Add(ref selfRef, 59); + dest.V7R.X = Unsafe.Add(ref selfRef, 60); + dest.V7R.Y = Unsafe.Add(ref selfRef, 61); + dest.V7R.Z = Unsafe.Add(ref selfRef, 62); + dest.V7R.W = Unsafe.Add(ref selfRef, 63); + } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index aecd5c59e..4ffb63480 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -556,7 +556,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } - private void RoundInplaceSlow() + internal void RoundInplaceSlow() { for (int i = 0; i < Size; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 7139260ec..2db869de7 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder ref Block8x8 sourceBlock, BufferArea destArea) { - this.data.SourceBlock = sourceBlock.AsFloatBlock(); + sourceBlock.CopyToFloatBlock(ref this.data.SourceBlock); Block8x8F* b = this.pointers.SourceBlock; diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index e5f2fd5e7..15a794144 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -8,6 +8,8 @@ namespace SixLabors.ImageSharp.Tests.Common using System.Linq; using System.Runtime.CompilerServices; + using SixLabors.ImageSharp.Common.Tuples; + using Xunit.Abstractions; using Xunit.Sdk; @@ -243,15 +245,15 @@ namespace SixLabors.ImageSharp.Tests.Common x = (x * scale) + magick; - SimdUtils.Octet.OfUInt32 ii = default(SimdUtils.Octet.OfUInt32); + Tuple8.OfUInt32 ii = default(Tuple8.OfUInt32); - ref Vector iiRef = ref Unsafe.As>(ref ii); + ref Vector iiRef = ref Unsafe.As>(ref ii); iiRef = x; - //SimdUtils.Octet.OfUInt32 ii = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x); + //Tuple8.OfUInt32 ii = Unsafe.As, Tuple8.OfUInt32>(ref x); - ref SimdUtils.Octet.OfByte d = ref dest.NonPortableCast()[0]; + ref Tuple8.OfByte d = ref dest.NonPortableCast()[0]; d.LoadFrom(ref ii); this.Output.WriteLine(ii.ToString()); From 57ccde4521cc87cf058d5eefb1b0a2dff749e70d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 07:30:54 +0200 Subject: [PATCH 24/29] NormalizeColorsAndRoundAvx2() + JpegBlockPostProcessor cleanup --- .../Formats/Jpeg/Common/Block8x8F.cs | 60 ++++---- .../Common/Decoder/JpegBlockPostProcessor.cs | 129 ++++-------------- .../Decoder/JpegColorConverter.FromYCbCr.cs | 4 +- .../Jpeg/Common/FastFloatingPointDCT.cs | 3 + .../Formats/Jpg/Block8x8FTests.cs | 26 +++- 5 files changed, 87 insertions(+), 135 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 4ffb63480..6ba69bec4 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -529,34 +529,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - public void RoundInplace() + public void NormalizeColorsAndRoundInplaceAvx2() + { + Vector off = new Vector(128f); + Vector max = new Vector(255F); + + ref Vector row0 = ref Unsafe.As>(ref this.V0L); + row0 = NormalizeAndRound(row0, off, max); + ref Vector row1 = ref Unsafe.As>(ref this.V1L); + row1 = NormalizeAndRound(row1, off, max); + ref Vector row2 = ref Unsafe.As>(ref this.V2L); + row2 = NormalizeAndRound(row2, off, max); + ref Vector row3 = ref Unsafe.As>(ref this.V3L); + row3 = NormalizeAndRound(row3, off, max); + ref Vector row4 = ref Unsafe.As>(ref this.V4L); + row4 = NormalizeAndRound(row4, off, max); + ref Vector row5 = ref Unsafe.As>(ref this.V5L); + row5 = NormalizeAndRound(row5, off, max); + ref Vector row6 = ref Unsafe.As>(ref this.V6L); + row6 = NormalizeAndRound(row6, off, max); + ref Vector row7 = ref Unsafe.As>(ref this.V7L); + row7 = NormalizeAndRound(row7, off, max); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) { - if (Vector.Count == 8 && Vector.Count == 8) - { - ref Vector row0 = ref Unsafe.As>(ref this.V0L); - row0 = row0.FastRound(); - ref Vector row1 = ref Unsafe.As>(ref this.V1L); - row1 = row1.FastRound(); - ref Vector row2 = ref Unsafe.As>(ref this.V2L); - row2 = row2.FastRound(); - ref Vector row3 = ref Unsafe.As>(ref this.V3L); - row3 = row3.FastRound(); - ref Vector row4 = ref Unsafe.As>(ref this.V4L); - row4 = row4.FastRound(); - ref Vector row5 = ref Unsafe.As>(ref this.V5L); - row5 = row5.FastRound(); - ref Vector row6 = ref Unsafe.As>(ref this.V6L); - row6 = row6.FastRound(); - ref Vector row7 = ref Unsafe.As>(ref this.V7L); - row7 = row7.FastRound(); - } - else - { - this.RoundInplaceSlow(); - } + row += off; + row = Vector.Max(row, Vector.Zero); + row = Vector.Min(row, max); + return row.FastRound(); } - internal void RoundInplaceSlow() + public void RoundInplace() { for (int i = 0; i < Size; i++) { @@ -598,10 +603,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common DebugGuard.MustBeLessThan(idx, Size, nameof(idx)); DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx)); } - - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] - private struct Row - { - } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 2db869de7..0459a5df0 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -14,14 +14,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder internal unsafe struct JpegBlockPostProcessor { /// - /// The + /// Source block /// - private ComputationData data; + public Block8x8F SourceBlock; /// - /// Pointers to elements of + /// Temporal block 1 to store intermediate and/or final computation results /// - private DataPointers pointers; + public Block8x8F WorkspaceBlock1; + + /// + /// Temporal block 2 to store intermediate and/or final computation results + /// + public Block8x8F WorkspaceBlock2; + + /// + /// The quantization table as + /// + public Block8x8F DequantiazationTable; private Size subSamplingDivisors; @@ -30,11 +40,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component) { - postProcessor->data = ComputationData.Create(); - postProcessor->pointers = new DataPointers(&postProcessor->data); - int qtIndex = component.QuantizationTableIndex; - postProcessor->data.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); + postProcessor->DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); postProcessor->subSamplingDivisors = component.SubSamplingDivisors; } @@ -42,110 +49,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder ref Block8x8 sourceBlock, BufferArea destArea) { - sourceBlock.CopyToFloatBlock(ref this.data.SourceBlock); - - Block8x8F* b = this.pointers.SourceBlock; + ref Block8x8F b = ref this.SourceBlock; + sourceBlock.CopyToFloatBlock(ref b); // Dequantize: - b->MultiplyInplace(ref this.data.DequantiazationTable); + b.MultiplyInplace(ref this.DequantiazationTable); - FastFloatingPointDCT.TransformIDCT(ref *b, ref this.data.WorkspaceBlock1, ref this.data.WorkspaceBlock2); - - this.data.WorkspaceBlock1.NormalizeColorsInplace(); + FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2); // To conform better to libjpeg we actually NEED TO loose precision here. // This is because they store blocks as Int16 between all the operations. - // Unfortunately, we need to emulate this to be "more accurate" :( - this.data.WorkspaceBlock1.RoundInplace(); - - this.data.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); - } - - /// - /// Holds the "large" data blocks needed for computations. - /// - [StructLayout(LayoutKind.Sequential)] - public struct ComputationData - { - /// - /// Source block - /// - public Block8x8F SourceBlock; - - /// - /// Temporal block 1 to store intermediate and/or final computation results - /// - public Block8x8F WorkspaceBlock1; - - /// - /// Temporal block 2 to store intermediate and/or final computation results - /// - public Block8x8F WorkspaceBlock2; - - /// - /// The quantization table as - /// - public Block8x8F DequantiazationTable; - - /// - /// The jpeg unzig data - /// - public ZigZag Unzig; - - /// - /// Creates and initializes a new instance - /// - /// The - public static ComputationData Create() + // To be "more accurate", we need to emulate this by rounding! + if (SimdUtils.IsAvx2CompatibleArchitecture) { - var data = default(ComputationData); - data.Unzig = ZigZag.CreateUnzigTable(); - return data; + this.WorkspaceBlock1.NormalizeColorsAndRoundInplaceAvx2(); } - } - - /// - /// Contains pointers to the memory regions of so they can be easily passed around to pointer based utility methods of - /// - public struct DataPointers - { - /// - /// Pointer to - /// - public Block8x8F* SourceBlock; - - /// - /// Pointer to - /// - public Block8x8F* WorkspaceBlock1; - - /// - /// Pointer to - /// - public Block8x8F* WorkspaceBlock2; - - /// - /// Pointer to - /// - public Block8x8F* DequantiazationTable; - - /// - /// Pointer to as int* - /// - public int* Unzig; - - /// - /// Initializes a new instance of the struct. - /// - /// Pointer to - internal DataPointers(ComputationData* dataPtr) + else { - this.SourceBlock = &dataPtr->SourceBlock; - this.WorkspaceBlock1 = &dataPtr->WorkspaceBlock1; - this.WorkspaceBlock2 = &dataPtr->WorkspaceBlock2; - this.DequantiazationTable = &dataPtr->DequantiazationTable; - this.Unzig = dataPtr->Unzig.Data; + this.WorkspaceBlock1.NormalizeColorsInplace(); + this.WorkspaceBlock1.RoundInplace(); } + + this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs index 693afc6f2..fef6d6438 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -123,12 +123,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder if (Vector.Count == 4) { - // TODO: Find a way to properly run & test this path on modern AVX2 PC-s! (Have I already mentioned that Vector is terrible?) + // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) r.RoundAndDownscaleBasic(); g.RoundAndDownscaleBasic(); b.RoundAndDownscaleBasic(); } - else if (Vector.Count == 8) + else if (SimdUtils.IsAvx2CompatibleArchitecture) { r.RoundAndDownscaleAvx2(); g.RoundAndDownscaleAvx2(); diff --git a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs index bdea14793..3ee6e72c5 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/FastFloatingPointDCT.cs @@ -50,7 +50,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Temporary block provided by the caller public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp) { + // TODO: Transpose is a bottleneck now. We need full AVX support to optimize it: + // https://github.com/dotnet/corefx/issues/22940 src.TransposeInto(ref temp); + IDCT8x4_LeftPart(ref temp, ref dest); IDCT8x4_RightPart(ref temp, ref dest); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 66c9bb3e1..1398b2f89 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -297,6 +297,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } + [Theory] + [InlineData(1)] + [InlineData(2)] + public void NormalizeColorsAndRoundAvx2(int seed) + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("AVX2 not supported, skipping!"); + return; + } + + Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); + + Block8x8F expected = source; + expected.NormalizeColorsInplace(); + expected.RoundInplace(); + + Block8x8F actual = source; + actual.NormalizeColorsAndRoundInplaceAvx2(); + + this.Output.WriteLine(expected.ToString()); + this.Output.WriteLine(actual.ToString()); + this.CompareBlocks(expected, actual, 0); + } [Theory] [InlineData(1)] @@ -352,7 +376,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(1)] [InlineData(2)] [InlineData(3)] - public void RoundInplace(int seed) + public void RoundInplaceSlow(int seed) { Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); From 8691efabf7a31e9e5466c55fee6879e84dce57f9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 14:02:53 +0200 Subject: [PATCH 25/29] deleting unused tuples for now cuz they are neither tested nor DRY --- src/ImageSharp/Common/Tuples/Tuple8.cs | 156 ------------------------- 1 file changed, 156 deletions(-) diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs index c0b9a9002..590a3f5c9 100644 --- a/src/ImageSharp/Common/Tuples/Tuple8.cs +++ b/src/ImageSharp/Common/Tuples/Tuple8.cs @@ -8,78 +8,6 @@ namespace SixLabors.ImageSharp.Common.Tuples /// internal static class Tuple8 { - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(float))] - public struct OfSingle - { - [FieldOffset(0 * sizeof(float))] - public float V0; - - [FieldOffset(1 * sizeof(float))] - public float V1; - - [FieldOffset(2 * sizeof(float))] - public float V2; - - [FieldOffset(3 * sizeof(float))] - public float V3; - - [FieldOffset(4 * sizeof(float))] - public float V4; - - [FieldOffset(5 * sizeof(float))] - public float V5; - - [FieldOffset(6 * sizeof(float))] - public float V6; - - [FieldOffset(7 * sizeof(float))] - public float V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } - - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(int))] - public struct OfInt32 - { - [FieldOffset(0 * sizeof(int))] - public int V0; - - [FieldOffset(1 * sizeof(int))] - public int V1; - - [FieldOffset(2 * sizeof(int))] - public int V2; - - [FieldOffset(3 * sizeof(int))] - public int V3; - - [FieldOffset(4 * sizeof(int))] - public int V4; - - [FieldOffset(5 * sizeof(int))] - public int V5; - - [FieldOffset(6 * sizeof(int))] - public int V6; - - [FieldOffset(7 * sizeof(int))] - public int V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } - /// /// Value type tuple of 8 -s /// @@ -114,90 +42,6 @@ namespace SixLabors.ImageSharp.Common.Tuples { return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; } - - public void LoadFrom(ref OfUInt16 i) - { - this.V0 = i.V0; - this.V1 = i.V1; - this.V2 = i.V2; - this.V3 = i.V3; - this.V4 = i.V4; - this.V5 = i.V5; - this.V6 = i.V6; - this.V7 = i.V7; - } - } - - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(ushort))] - public struct OfUInt16 - { - [FieldOffset(0 * sizeof(ushort))] - public ushort V0; - - [FieldOffset(1 * sizeof(ushort))] - public ushort V1; - - [FieldOffset(2 * sizeof(ushort))] - public ushort V2; - - [FieldOffset(3 * sizeof(ushort))] - public ushort V3; - - [FieldOffset(4 * sizeof(ushort))] - public ushort V4; - - [FieldOffset(5 * sizeof(ushort))] - public ushort V5; - - [FieldOffset(6 * sizeof(ushort))] - public ushort V6; - - [FieldOffset(7 * sizeof(ushort))] - public ushort V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } - } - - /// - /// Value type tuple of 8 -s - /// - [StructLayout(LayoutKind.Explicit, Size = 8 * sizeof(short))] - public struct OfInt16 - { - [FieldOffset(0 * sizeof(short))] - public short V0; - - [FieldOffset(1 * sizeof(short))] - public short V1; - - [FieldOffset(2 * sizeof(short))] - public short V2; - - [FieldOffset(3 * sizeof(short))] - public short V3; - - [FieldOffset(4 * sizeof(short))] - public short V4; - - [FieldOffset(5 * sizeof(short))] - public short V5; - - [FieldOffset(6 * sizeof(short))] - public short V6; - - [FieldOffset(7 * sizeof(short))] - public short V7; - - public override string ToString() - { - return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; - } } /// From 8b9a369504c082ce62fb89222400c7216e25355e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 16:26:18 +0200 Subject: [PATCH 26/29] cleanup and docs --- src/ImageSharp/Common/Tuples/Tuple8.cs | 5 +- src/ImageSharp/Common/Tuples/Vector4Pair.cs | 8 +- .../Jpeg/Common/Block8x8F.Generated.cs | 89 +++++++++--------- .../Jpeg/Common/Block8x8F.Generated.tt | 23 ++++- .../Formats/Jpeg/Common/Block8x8F.cs | 91 +++++-------------- .../Common/Decoder/JpegBlockPostProcessor.cs | 22 ++++- .../Decoder/JpegComponentPostProcessor.cs | 5 +- .../Formats/Jpg/Block8x8FTests.cs | 47 +--------- 8 files changed, 122 insertions(+), 168 deletions(-) diff --git a/src/ImageSharp/Common/Tuples/Tuple8.cs b/src/ImageSharp/Common/Tuples/Tuple8.cs index 590a3f5c9..3335e6e37 100644 --- a/src/ImageSharp/Common/Tuples/Tuple8.cs +++ b/src/ImageSharp/Common/Tuples/Tuple8.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Common.Tuples { /// /// Contains value type tuples of 8 elements. - /// TODO: Should T4 this stuff to be DRY + /// TODO: We should T4 this stuff to be DRY /// internal static class Tuple8 { @@ -79,6 +79,9 @@ namespace SixLabors.ImageSharp.Common.Tuples return $"[{this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7}]"; } + /// + /// Sets the values of this tuple by casting all elements of the given tuple to . + /// public void LoadFrom(ref OfUInt32 i) { this.V0 = (byte)i.V0; diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs index 1be936b30..4f43c9811 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -39,10 +39,11 @@ namespace SixLabors.ImageSharp.Common.Tuples } /// - /// Color-conversion specific downscale method. + /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! + /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleBasic() + internal void RoundAndDownscalePreAvx2() { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -56,7 +57,8 @@ namespace SixLabors.ImageSharp.Common.Tuples } /// - /// AVX2-only color-conversion specific downscale method. + /// AVX2-only Downscale method, specific to Jpeg color conversion. + /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void RoundAndDownscaleAvx2() diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index 4c1b4f4d1..e8614205c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -96,51 +96,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInto(ref Block8x8F d) + internal void NormalizeColorsInplace() { - d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); - d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); - d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); - d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); - d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); - d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); - d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); - d.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); - d.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); - d.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); - d.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); - d.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); - d.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); - d.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); - d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); - d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); + this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Vector4.Clamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Vector4.Clamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Vector4.Clamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Vector4.Clamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Vector4.Clamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Vector4.Clamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Vector4.Clamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Vector4.Clamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Vector4.Clamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Vector4.Clamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Vector4.Clamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Vector4.Clamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Vector4.Clamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4); } - - /// - /// Level shift by +128, clip to [0, 255] - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInplace() + public void NormalizeColorsAndRoundInplaceAvx2() { - this.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); - this.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); - this.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); - this.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); - this.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); - this.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); - this.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); - this.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); - this.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); - this.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); - this.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); - this.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); - this.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); - this.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); - this.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); - this.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); - } - } + Vector off = new Vector(128f); + Vector max = new Vector(255F); + + + ref Vector row0 = ref Unsafe.As>(ref this.V0L); + row0 = NormalizeAndRound(row0, off, max); + + ref Vector row1 = ref Unsafe.As>(ref this.V1L); + row1 = NormalizeAndRound(row1, off, max); + + ref Vector row2 = ref Unsafe.As>(ref this.V2L); + row2 = NormalizeAndRound(row2, off, max); + + ref Vector row3 = ref Unsafe.As>(ref this.V3L); + row3 = NormalizeAndRound(row3, off, max); + + ref Vector row4 = ref Unsafe.As>(ref this.V4L); + row4 = NormalizeAndRound(row4, off, max); + + ref Vector row5 = ref Unsafe.As>(ref this.V5L); + row5 = NormalizeAndRound(row5, off, max); + + ref Vector row6 = ref Unsafe.As>(ref this.V6L); + row6 = NormalizeAndRound(row6, off, max); + + ref Vector row7 = ref Unsafe.As>(ref this.V7L); + row7 = NormalizeAndRound(row7, off, max); + } + } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt index bef3e4914..9ab3ee12c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt @@ -61,9 +61,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) + internal void NormalizeColorsInplace() { <# @@ -74,11 +73,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common for (int j = 0; j < 2; j++) { char side = j == 0 ? 'L' : 'R'; - Write($"d.V{i}{side} = Vector4.Clamp(V{i}{side} + COff4, CMin4, CMax4);\r\n"); + Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); } } PopIndent(); #> } + + public void NormalizeColorsAndRoundInplaceAvx2() + { + Vector off = new Vector(128f); + Vector max = new Vector(255F); + + <# + + for (int i = 0; i < 8; i++) + { + #> + + ref Vector row<#=i#> = ref Unsafe.As>(ref this.V<#=i#>L); + row<#=i#> = NormalizeAndRound(row<#=i#>, off, max); + <# + } + #> + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index 6ba69bec4..ff9db6ab9 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -315,28 +315,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Multiply all elements of the block. /// - /// Vector to multiply by + /// The value to multiply by [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyInplace(float scaleVec) - { - this.V0L *= scaleVec; - this.V0R *= scaleVec; - this.V1L *= scaleVec; - this.V1R *= scaleVec; - this.V2L *= scaleVec; - this.V2R *= scaleVec; - this.V3L *= scaleVec; - this.V3R *= scaleVec; - this.V4L *= scaleVec; - this.V4R *= scaleVec; - this.V5L *= scaleVec; - this.V5R *= scaleVec; - this.V6L *= scaleVec; - this.V6R *= scaleVec; - this.V7L *= scaleVec; - this.V7R *= scaleVec; + public void MultiplyInplace(float value) + { + this.V0L *= value; + this.V0R *= value; + this.V1L *= value; + this.V1R *= value; + this.V2L *= value; + this.V2R *= value; + this.V3L *= value; + this.V3R *= value; + this.V4L *= value; + this.V4R *= value; + this.V5L *= value; + this.V5R *= value; + this.V6L *= value; + this.V6R *= value; + this.V7L *= value; + this.V7R *= value; } + /// + /// Multiply all elements of the block by the corresponding elements of 'other' + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void MultiplyInplace(ref Block8x8F other) { @@ -405,33 +408,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } } - /// - /// Level shift by +128, clip to [0, 255], and write to buffer. - /// - /// Color buffer - /// Stride offset - /// Temp Block pointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void CopyColorsTo(Span destinationBuffer, int stride, Block8x8F* tempBlockPtr) - { - this.NormalizeColorsInto(ref *tempBlockPtr); - ref byte d = ref destinationBuffer.DangerousGetPinnableReference(); - float* src = (float*)tempBlockPtr; - for (int i = 0; i < 8; i++) - { - ref byte dRow = ref Unsafe.Add(ref d, i * stride); - Unsafe.Add(ref dRow, 0) = (byte)src[0]; - Unsafe.Add(ref dRow, 1) = (byte)src[1]; - Unsafe.Add(ref dRow, 2) = (byte)src[2]; - Unsafe.Add(ref dRow, 3) = (byte)src[3]; - Unsafe.Add(ref dRow, 4) = (byte)src[4]; - Unsafe.Add(ref dRow, 5) = (byte)src[5]; - Unsafe.Add(ref dRow, 6) = (byte)src[6]; - Unsafe.Add(ref dRow, 7) = (byte)src[7]; - src += 8; - } - } - /// /// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values. /// To finish the rounding it's enough to (int)-cast these values. @@ -529,29 +505,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - public void NormalizeColorsAndRoundInplaceAvx2() - { - Vector off = new Vector(128f); - Vector max = new Vector(255F); - - ref Vector row0 = ref Unsafe.As>(ref this.V0L); - row0 = NormalizeAndRound(row0, off, max); - ref Vector row1 = ref Unsafe.As>(ref this.V1L); - row1 = NormalizeAndRound(row1, off, max); - ref Vector row2 = ref Unsafe.As>(ref this.V2L); - row2 = NormalizeAndRound(row2, off, max); - ref Vector row3 = ref Unsafe.As>(ref this.V3L); - row3 = NormalizeAndRound(row3, off, max); - ref Vector row4 = ref Unsafe.As>(ref this.V4L); - row4 = NormalizeAndRound(row4, off, max); - ref Vector row5 = ref Unsafe.As>(ref this.V5L); - row5 = NormalizeAndRound(row5, off, max); - ref Vector row6 = ref Unsafe.As>(ref this.V6L); - row6 = NormalizeAndRound(row6, off, max); - ref Vector row7 = ref Unsafe.As>(ref this.V7L); - row7 = NormalizeAndRound(row7, off, max); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index 0459a5df0..d0f2b8817 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -33,18 +33,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// public Block8x8F DequantiazationTable; + /// + /// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block. + /// private Size subSamplingDivisors; /// - /// Initialize the instance on the stack. + /// Initializes a new instance of the struct. /// - public static void Init(JpegBlockPostProcessor* postProcessor, IRawJpegData decoder, IJpegComponent component) + public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) { int qtIndex = component.QuantizationTableIndex; - postProcessor->DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); - postProcessor->subSamplingDivisors = component.SubSamplingDivisors; + this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); + this.subSamplingDivisors = component.SubSamplingDivisors; + + this.SourceBlock = default(Block8x8F); + this.WorkspaceBlock1 = default(Block8x8F); + this.WorkspaceBlock2 = default(Block8x8F); } + /// + /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: + /// - Dequantize + /// - Applying IDCT + /// - Level shift by +128, clip to [0, 255] + /// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in + /// public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, BufferArea destArea) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs index 53aafed01..87c1431e0 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegComponentPostProcessor.cs @@ -66,10 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// /// Invoke for block rows, copy the result into . /// - public unsafe void CopyBlocksToColorBuffer() + public void CopyBlocksToColorBuffer() { - var blockPp = default(JpegBlockPostProcessor); - JpegBlockPostProcessor.Init(&blockPp, this.ImagePostProcessor.RawJpeg, this.Component); + var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); for (int y = 0; y < this.BlockRowsPerStep; y++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 1398b2f89..8d1e585db 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -222,33 +222,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"TranposeInto_PinningImpl_Benchmark finished in {sw.ElapsedMilliseconds} ms"); } - [Fact] - public unsafe void CopyColorsTo() - { - float[] data = Create8x8FloatData(); - var block = new Block8x8F(); - block.LoadFrom(data); - block.MultiplyInplace(5); - - int stride = 256; - int height = 42; - int offset = height * 10 + 20; - - byte[] colorsExpected = new byte[stride * height]; - byte[] colorsActual = new byte[stride * height]; - - var temp = new Block8x8F(); - - ReferenceImplementations.CopyColorsTo(ref block, new Span(colorsExpected, offset), stride); - - block.CopyColorsTo(new Span(colorsActual, offset), stride, &temp); - - // Output.WriteLine("******* EXPECTED: *********"); - // PrintLinearData(colorsExpected); - // Output.WriteLine("******** ACTUAL: **********"); - Assert.Equal(colorsExpected, colorsActual); - } - private static float[] Create8x8ColorCropTestData() { float[] result = new float[64]; @@ -263,30 +236,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return result; } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void NormalizeColors(bool inplace) + [Fact] + public void NormalizeColors() { var block = default(Block8x8F); float[] input = Create8x8ColorCropTestData(); block.LoadFrom(input); this.Output.WriteLine("Input:"); this.PrintLinearData(input); - - var dest = default(Block8x8F); - if (inplace) - { - dest = block; - dest.NormalizeColorsInplace(); - } - else - { - block.NormalizeColorsInto(ref dest); - } + Block8x8F dest = block; + dest.NormalizeColorsInplace(); - float[] array = new float[64]; dest.CopyTo(array); this.Output.WriteLine("Result:"); From 63e574cdb2fcb14f71a9d271dbf344ba289cc7f3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 17:24:47 +0200 Subject: [PATCH 27/29] more cleanup + more tests --- .../Jpeg/Common/Block8x8F.Generated.cs | 3 +- .../Jpeg/Common/Block8x8F.Generated.tt | 1 + .../Formats/Jpeg/Common/Block8x8F.cs | 2 +- .../Common/Decoder/JpegBlockPostProcessor.cs | 2 +- .../Decoder/JpegColorConverter.FromYCbCr.cs | 68 ++----------------- .../Formats/Jpg/Block8x8FTests.cs | 28 +++++++- 6 files changed, 35 insertions(+), 69 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index e8614205c..246df9d91 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -146,6 +146,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common ref Vector row7 = ref Unsafe.As>(ref this.V7L); row7 = NormalizeAndRound(row7, off, max); - } + + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt index 9ab3ee12c..7b5b6823a 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt @@ -96,6 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common <# } #> + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index ff9db6ab9..fcf618e84 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Destination [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe void CopyTo(Span dest) + public void CopyTo(Span dest) { ref byte d = ref Unsafe.As(ref dest.DangerousGetPinnableReference()); ref byte s = ref Unsafe.As(ref this); diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index d0f2b8817..a081c8415 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder /// Encapsulates the implementation of processing "raw" -s into Jpeg image channels. /// [StructLayout(LayoutKind.Sequential)] - internal unsafe struct JpegBlockPostProcessor + internal struct JpegBlockPostProcessor { /// /// Source block diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs index fef6d6438..b988695e1 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegColorConverter.FromYCbCr.cs @@ -1,6 +1,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Common.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder { @@ -124,9 +125,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder if (Vector.Count == 4) { // TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector is terrible?) - r.RoundAndDownscaleBasic(); - g.RoundAndDownscaleBasic(); - b.RoundAndDownscaleBasic(); + r.RoundAndDownscalePreAvx2(); + g.RoundAndDownscalePreAvx2(); + b.RoundAndDownscalePreAvx2(); } else if (SimdUtils.IsAvx2CompatibleArchitecture) { @@ -147,67 +148,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder } } - /// - /// Its faster to process multiple Vector4-s together - /// - private struct Vector4Pair - { - public Vector4 A; - - public Vector4 B; - - private static readonly Vector4 Scale = new Vector4(1 / 255f); - - private static readonly Vector4 Half = new Vector4(0.5f); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RoundAndDownscaleBasic() - { - ref Vector a = ref Unsafe.As>(ref this.A); - a = a.FastRound(); - - ref Vector b = ref Unsafe.As>(ref this.B); - b = b.FastRound(); - - // Downscale by 1/255 - this.A *= Scale; - this.B *= Scale; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RoundAndDownscaleAvx2() - { - ref Vector self = ref Unsafe.As>(ref this); - Vector v = self; - v = v.FastRound(); - - // Downscale by 1/255 - v *= new Vector(1 / 255f); - self = v; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyInplace(float value) - { - this.A *= value; - this.B *= value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(Vector4 value) - { - this.A += value; - this.B += value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(ref Vector4Pair other) - { - this.A += other.A; - this.B += other.B; - } - } - private struct Vector4Octet { #pragma warning disable SA1132 // Do not combine fields diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 8d1e585db..94f8d2ead 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -32,6 +32,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { } + private bool SkipOnNonAvx2Runner() + { + if (!SimdUtils.IsAvx2CompatibleArchitecture) + { + this.Output.WriteLine("AVX2 not supported, skipping!"); + return true; + } + return false; + } + [Fact] public void Indexer() { @@ -263,9 +273,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public void NormalizeColorsAndRoundAvx2(int seed) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (this.SkipOnNonAvx2Runner()) { - this.Output.WriteLine("AVX2 not supported, skipping!"); return; } @@ -283,6 +292,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0); } + [Theory] [InlineData(1)] [InlineData(2)] @@ -413,5 +423,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 0); } + + [Fact] + public void MultiplyInplace_ByScalar() + { + Block8x8F original = CreateRandomFloatBlock(-500, 500); + + Block8x8F actual = original; + actual.MultiplyInplace(42f); + + for (int i = 0; i < 64; i++) + { + Assert.Equal(original[i]*42f, actual[i]); + } + } } } \ No newline at end of file From 97dc26c01a65df5d7f2e13b65b9538a89087fc0e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 17 Sep 2017 21:45:44 +0200 Subject: [PATCH 28/29] additional Block8x8F cleanup --- src/ImageSharp/Common/Extensions/SimdUtils.cs | 4 +- .../Formats/Jpeg/Common/Block8x8.cs | 82 +---------------- .../Jpeg/Common/Block8x8F.Generated.cs | 88 ++++++++++++++++++- .../Jpeg/Common/Block8x8F.Generated.tt | 40 ++++++++- .../Formats/Jpeg/Common/Block8x8F.cs | 74 ++++++---------- .../Common/Decoder/JpegBlockPostProcessor.cs | 12 +-- .../Jpeg/GolangPort/JpegEncoderCore.cs | 2 +- .../Formats/Jpg/Block8x8FTests.cs | 32 +------ .../Formats/Jpg/JpegProfilingBenchmarks.cs | 4 +- .../Jpg/Utils/ReferenceImplementations.cs | 4 +- 10 files changed, 163 insertions(+), 179 deletions(-) diff --git a/src/ImageSharp/Common/Extensions/SimdUtils.cs b/src/ImageSharp/Common/Extensions/SimdUtils.cs index d6b2fad09..0188bc03c 100644 --- a/src/ImageSharp/Common/Extensions/SimdUtils.cs +++ b/src/ImageSharp/Common/Extensions/SimdUtils.cs @@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp internal static class SimdUtils { /// - /// Indicates AVX2 architecture where both float and integer registers are of size 256 byte. + /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte. /// - public static readonly bool IsAvx2CompatibleArchitecture = Vector.Count == 8 && Vector.Count == 8; + public static bool IsAvx2CompatibleArchitecture => Vector.Count == 8 && Vector.Count == 8; internal static void GuardAvx2(string operation) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs index 1291f160a..96ed0a8c9 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8.cs @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common public Block8x8F AsFloatBlock() { var result = default(Block8x8F); - this.CopyToFloatBlock(ref result); + result.LoadFrom(ref this); return result; } @@ -297,85 +297,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - - /// - /// Convert values into as -s - /// - public void CopyToFloatBlock(ref Block8x8F dest) - { - ref short selfRef = ref Unsafe.As(ref this); - - dest.V0L.X = Unsafe.Add(ref selfRef, 0); - dest.V0L.Y = Unsafe.Add(ref selfRef, 1); - dest.V0L.Z = Unsafe.Add(ref selfRef, 2); - dest.V0L.W = Unsafe.Add(ref selfRef, 3); - dest.V0R.X = Unsafe.Add(ref selfRef, 4); - dest.V0R.Y = Unsafe.Add(ref selfRef, 5); - dest.V0R.Z = Unsafe.Add(ref selfRef, 6); - dest.V0R.W = Unsafe.Add(ref selfRef, 7); - - dest.V1L.X = Unsafe.Add(ref selfRef, 8); - dest.V1L.Y = Unsafe.Add(ref selfRef, 9); - dest.V1L.Z = Unsafe.Add(ref selfRef, 10); - dest.V1L.W = Unsafe.Add(ref selfRef, 11); - dest.V1R.X = Unsafe.Add(ref selfRef, 12); - dest.V1R.Y = Unsafe.Add(ref selfRef, 13); - dest.V1R.Z = Unsafe.Add(ref selfRef, 14); - dest.V1R.W = Unsafe.Add(ref selfRef, 15); - - dest.V2L.X = Unsafe.Add(ref selfRef, 16); - dest.V2L.Y = Unsafe.Add(ref selfRef, 17); - dest.V2L.Z = Unsafe.Add(ref selfRef, 18); - dest.V2L.W = Unsafe.Add(ref selfRef, 19); - dest.V2R.X = Unsafe.Add(ref selfRef, 20); - dest.V2R.Y = Unsafe.Add(ref selfRef, 21); - dest.V2R.Z = Unsafe.Add(ref selfRef, 22); - dest.V2R.W = Unsafe.Add(ref selfRef, 23); - - dest.V3L.X = Unsafe.Add(ref selfRef, 24); - dest.V3L.Y = Unsafe.Add(ref selfRef, 25); - dest.V3L.Z = Unsafe.Add(ref selfRef, 26); - dest.V3L.W = Unsafe.Add(ref selfRef, 27); - dest.V3R.X = Unsafe.Add(ref selfRef, 28); - dest.V3R.Y = Unsafe.Add(ref selfRef, 29); - dest.V3R.Z = Unsafe.Add(ref selfRef, 30); - dest.V3R.W = Unsafe.Add(ref selfRef, 31); - - dest.V4L.X = Unsafe.Add(ref selfRef, 32); - dest.V4L.Y = Unsafe.Add(ref selfRef, 33); - dest.V4L.Z = Unsafe.Add(ref selfRef, 34); - dest.V4L.W = Unsafe.Add(ref selfRef, 35); - dest.V4R.X = Unsafe.Add(ref selfRef, 36); - dest.V4R.Y = Unsafe.Add(ref selfRef, 37); - dest.V4R.Z = Unsafe.Add(ref selfRef, 38); - dest.V4R.W = Unsafe.Add(ref selfRef, 39); - - dest.V5L.X = Unsafe.Add(ref selfRef, 40); - dest.V5L.Y = Unsafe.Add(ref selfRef, 41); - dest.V5L.Z = Unsafe.Add(ref selfRef, 42); - dest.V5L.W = Unsafe.Add(ref selfRef, 43); - dest.V5R.X = Unsafe.Add(ref selfRef, 44); - dest.V5R.Y = Unsafe.Add(ref selfRef, 45); - dest.V5R.Z = Unsafe.Add(ref selfRef, 46); - dest.V5R.W = Unsafe.Add(ref selfRef, 47); - - dest.V6L.X = Unsafe.Add(ref selfRef, 48); - dest.V6L.Y = Unsafe.Add(ref selfRef, 49); - dest.V6L.Z = Unsafe.Add(ref selfRef, 50); - dest.V6L.W = Unsafe.Add(ref selfRef, 51); - dest.V6R.X = Unsafe.Add(ref selfRef, 52); - dest.V6R.Y = Unsafe.Add(ref selfRef, 53); - dest.V6R.Z = Unsafe.Add(ref selfRef, 54); - dest.V6R.W = Unsafe.Add(ref selfRef, 55); - - dest.V7L.X = Unsafe.Add(ref selfRef, 56); - dest.V7L.Y = Unsafe.Add(ref selfRef, 57); - dest.V7L.Z = Unsafe.Add(ref selfRef, 58); - dest.V7L.W = Unsafe.Add(ref selfRef, 59); - dest.V7R.X = Unsafe.Add(ref selfRef, 60); - dest.V7R.Y = Unsafe.Add(ref selfRef, 61); - dest.V7R.Z = Unsafe.Add(ref selfRef, 62); - dest.V7R.W = Unsafe.Add(ref selfRef, 63); - } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs index 246df9d91..93e9e0388 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.cs @@ -96,8 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInplace() + public void NormalizeColorsInplace() { this.V0L = Vector4.Clamp(this.V0L + COff4, CMin4, CMax4); this.V0R = Vector4.Clamp(this.V0R + COff4, CMin4, CMax4); @@ -117,11 +116,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common this.V7R = Vector4.Clamp(this.V7R + COff4, CMin4, CMax4); } + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void NormalizeColorsAndRoundInplaceAvx2() { Vector off = new Vector(128f); Vector max = new Vector(255F); - ref Vector row0 = ref Unsafe.As>(ref this.V0L); row0 = NormalizeAndRound(row0, off, max); @@ -148,5 +150,85 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common row7 = NormalizeAndRound(row7, off, max); } + + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFrom(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); + + this.V0L.X = Unsafe.Add(ref selfRef, 0); + this.V0L.Y = Unsafe.Add(ref selfRef, 1); + this.V0L.Z = Unsafe.Add(ref selfRef, 2); + this.V0L.W = Unsafe.Add(ref selfRef, 3); + this.V0R.X = Unsafe.Add(ref selfRef, 4); + this.V0R.Y = Unsafe.Add(ref selfRef, 5); + this.V0R.Z = Unsafe.Add(ref selfRef, 6); + this.V0R.W = Unsafe.Add(ref selfRef, 7); + + this.V1L.X = Unsafe.Add(ref selfRef, 8); + this.V1L.Y = Unsafe.Add(ref selfRef, 9); + this.V1L.Z = Unsafe.Add(ref selfRef, 10); + this.V1L.W = Unsafe.Add(ref selfRef, 11); + this.V1R.X = Unsafe.Add(ref selfRef, 12); + this.V1R.Y = Unsafe.Add(ref selfRef, 13); + this.V1R.Z = Unsafe.Add(ref selfRef, 14); + this.V1R.W = Unsafe.Add(ref selfRef, 15); + + this.V2L.X = Unsafe.Add(ref selfRef, 16); + this.V2L.Y = Unsafe.Add(ref selfRef, 17); + this.V2L.Z = Unsafe.Add(ref selfRef, 18); + this.V2L.W = Unsafe.Add(ref selfRef, 19); + this.V2R.X = Unsafe.Add(ref selfRef, 20); + this.V2R.Y = Unsafe.Add(ref selfRef, 21); + this.V2R.Z = Unsafe.Add(ref selfRef, 22); + this.V2R.W = Unsafe.Add(ref selfRef, 23); + + this.V3L.X = Unsafe.Add(ref selfRef, 24); + this.V3L.Y = Unsafe.Add(ref selfRef, 25); + this.V3L.Z = Unsafe.Add(ref selfRef, 26); + this.V3L.W = Unsafe.Add(ref selfRef, 27); + this.V3R.X = Unsafe.Add(ref selfRef, 28); + this.V3R.Y = Unsafe.Add(ref selfRef, 29); + this.V3R.Z = Unsafe.Add(ref selfRef, 30); + this.V3R.W = Unsafe.Add(ref selfRef, 31); + + this.V4L.X = Unsafe.Add(ref selfRef, 32); + this.V4L.Y = Unsafe.Add(ref selfRef, 33); + this.V4L.Z = Unsafe.Add(ref selfRef, 34); + this.V4L.W = Unsafe.Add(ref selfRef, 35); + this.V4R.X = Unsafe.Add(ref selfRef, 36); + this.V4R.Y = Unsafe.Add(ref selfRef, 37); + this.V4R.Z = Unsafe.Add(ref selfRef, 38); + this.V4R.W = Unsafe.Add(ref selfRef, 39); + + this.V5L.X = Unsafe.Add(ref selfRef, 40); + this.V5L.Y = Unsafe.Add(ref selfRef, 41); + this.V5L.Z = Unsafe.Add(ref selfRef, 42); + this.V5L.W = Unsafe.Add(ref selfRef, 43); + this.V5R.X = Unsafe.Add(ref selfRef, 44); + this.V5R.Y = Unsafe.Add(ref selfRef, 45); + this.V5R.Z = Unsafe.Add(ref selfRef, 46); + this.V5R.W = Unsafe.Add(ref selfRef, 47); + + this.V6L.X = Unsafe.Add(ref selfRef, 48); + this.V6L.Y = Unsafe.Add(ref selfRef, 49); + this.V6L.Z = Unsafe.Add(ref selfRef, 50); + this.V6L.W = Unsafe.Add(ref selfRef, 51); + this.V6R.X = Unsafe.Add(ref selfRef, 52); + this.V6R.Y = Unsafe.Add(ref selfRef, 53); + this.V6R.Z = Unsafe.Add(ref selfRef, 54); + this.V6R.W = Unsafe.Add(ref selfRef, 55); + + this.V7L.X = Unsafe.Add(ref selfRef, 56); + this.V7L.Y = Unsafe.Add(ref selfRef, 57); + this.V7L.Z = Unsafe.Add(ref selfRef, 58); + this.V7L.W = Unsafe.Add(ref selfRef, 59); + this.V7R.X = Unsafe.Add(ref selfRef, 60); + this.V7R.Y = Unsafe.Add(ref selfRef, 61); + this.V7R.Z = Unsafe.Add(ref selfRef, 62); + this.V7R.W = Unsafe.Add(ref selfRef, 63); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt index 7b5b6823a..dc0996b65 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.Generated.tt @@ -61,8 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// /// Level shift by +128, clip to [0, 255] /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void NormalizeColorsInplace() + public void NormalizeColorsInplace() { <# @@ -80,11 +79,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common #> } + /// + /// AVX2-only variant for executing and in one step. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void NormalizeColorsAndRoundInplaceAvx2() { Vector off = new Vector(128f); Vector max = new Vector(255F); - <# for (int i = 0; i < 8; i++) @@ -98,5 +100,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common #> } + + /// + /// Fill the block from 'source' doing short -> float conversion. + /// + public void LoadFrom(ref Block8x8 source) + { + ref short selfRef = ref Unsafe.As(ref source); + + <# + PushIndent(" "); + for (int j = 0; j < 8; j++) + { + for (int i = 0; i < 8; i++) + { + char destCoord = coordz[i % 4]; + char destSide = (i / 4) % 2 == 0 ? 'L' : 'R'; + + if(j > 0 && i == 0){ + WriteLine(""); + } + + char srcCoord = coordz[j % 4]; + char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; + + string expression = $"this.V{j}{destSide}.{destCoord} = Unsafe.Add(ref selfRef, {j*8+i});\r\n"; + Write(expression); + + } + } + PopIndent(); + #> + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs index fcf618e84..2dd42288c 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Block8x8F.cs @@ -16,16 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// internal partial struct Block8x8F { - // Most of the static methods of this struct are instance methods by actual semantics: they use Block8x8F* as their first parameter. - // Example: GetScalarAt() and SetScalarAt() are really just other (optimized) versions of the indexer. - // It's much cleaner, easier and safer to work with the code, if the methods with same semantics are next to each other. -#pragma warning disable SA1204 // StaticElementsMustAppearBeforeInstanceElements - - /// - /// Vector count - /// - public const int VectorCount = 16; - /// /// A number of scalar coefficients in a /// @@ -156,36 +146,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - /// - /// Pointer-based "Indexer" (getter part) - /// - /// Block pointer - /// Index - /// The scaleVec value at the specified index - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe float GetScalarAt(Block8x8F* blockPtr, int idx) - { - GuardBlockIndex(idx); - - float* fp = (float*)blockPtr; - return fp[idx]; - } - - /// - /// Pointer-based "Indexer" (setter part) - /// - /// Block pointer - /// Index - /// Value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void SetScalarAt(Block8x8F* blockPtr, int idx, float value) - { - GuardBlockIndex(idx); - - float* fp = (float*)blockPtr; - fp[idx] = value; - } - /// /// Fill the block with defaults (zeroes) /// @@ -409,6 +369,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common } /// + /// Quantize 'block' into 'dest' using the 'qt' quantization table: /// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values. /// To finish the rounding it's enough to (int)-cast these values. /// @@ -416,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common /// Destination block /// The quantization table /// Pointer to elements of - public static unsafe void UnzigDivRound( + public static unsafe void Quantize( Block8x8F* block, Block8x8F* dest, Block8x8F* qt, @@ -505,15 +466,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) + /// + /// Level shift by +128, clip to [0..255], and round all the values in the block. + /// + public void NormalizeColorsAndRoundInplace() { - row += off; - row = Vector.Max(row, Vector.Zero); - row = Vector.Min(row, max); - return row.FastRound(); + if (SimdUtils.IsAvx2CompatibleArchitecture) + { + this.NormalizeColorsAndRoundInplaceAvx2(); + } + else + { + this.NormalizeColorsInplace(); + this.RoundInplace(); + } } + /// + /// Rounds all values in the block. + /// public void RoundInplace() { for (int i = 0; i < Size; i++) @@ -540,6 +511,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common return bld.ToString(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector NormalizeAndRound(Vector row, Vector off, Vector max) + { + row += off; + row = Vector.Max(row, Vector.Zero); + row = Vector.Min(row, max); + return row.FastRound(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { diff --git a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs index a081c8415..574967b6b 100644 --- a/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Common/Decoder/JpegBlockPostProcessor.cs @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder BufferArea destArea) { ref Block8x8F b = ref this.SourceBlock; - sourceBlock.CopyToFloatBlock(ref b); + b.LoadFrom(ref sourceBlock); // Dequantize: b.MultiplyInplace(ref this.DequantiazationTable); @@ -74,15 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Common.Decoder // To conform better to libjpeg we actually NEED TO loose precision here. // This is because they store blocks as Int16 between all the operations. // To be "more accurate", we need to emulate this by rounding! - if (SimdUtils.IsAvx2CompatibleArchitecture) - { - this.WorkspaceBlock1.NormalizeColorsAndRoundInplaceAvx2(); - } - else - { - this.WorkspaceBlock1.NormalizeColorsInplace(); - this.WorkspaceBlock1.RoundInplace(); - } + this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(); this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); } diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 3a24bded3..2deb3f62d 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -564,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort { FastFloatingPointDCT.TransformFDCT(ref *src, ref *tempDest1, ref *tempDest2); - Block8x8F.UnzigDivRound(tempDest1, tempDest2, quant, unzigPtr); + Block8x8F.Quantize(tempDest1, tempDest2, quant, unzigPtr); float* unziggedDestPtr = (float*)tempDest2; int dc = (int)unziggedDestPtr[0]; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 94f8d2ead..bf5507676 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -65,31 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }); Assert.Equal(sum, 64f * 63f * 0.5f); } - - [Fact] - public unsafe void Indexer_GetScalarAt_SetScalarAt() - { - float sum = 0; - this.Measure( - Times, - () => - { - var block = new Block8x8F(); - - for (int i = 0; i < Block8x8F.Size; i++) - { - Block8x8F.SetScalarAt(&block, i, i); - } - - sum = 0; - for (int i = 0; i < Block8x8F.Size; i++) - { - sum += Block8x8F.GetScalarAt(&block, i); - } - }); - Assert.Equal(sum, 64f * 63f * 0.5f); - } - + [Fact] public void Indexer_ReferenceBenchmarkWithArray() { @@ -296,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [InlineData(1)] [InlineData(2)] - public unsafe void UnzigDivRound(int seed) + public unsafe void Quantize(int seed) { var block = new Block8x8F(); block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); @@ -307,11 +283,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var unzig = ZigZag.CreateUnzigTable(); int* expectedResults = stackalloc int[Block8x8F.Size]; - ReferenceImplementations.UnZigDivRoundRational(&block, expectedResults, &qt, unzig.Data); + ReferenceImplementations.QuantizeRational(&block, expectedResults, &qt, unzig.Data); var actualResults = default(Block8x8F); - Block8x8F.UnzigDivRound(&block, &actualResults, &qt, unzig.Data); + Block8x8F.Quantize(&block, &actualResults, &qt, unzig.Data); for (int i = 0; i < Block8x8F.Size; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index a228bc236..baa31f674 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -34,8 +34,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg444, }; - // [Theory] // Benchmark, enable manually - // [MemberData(nameof(DecodeJpegData))] + //[Theory] // Benchmark, enable manually + //[MemberData(nameof(DecodeJpegData))] public void DecodeJpeg_Original(string fileName) { this.DecodeJpegBenchmarkImpl(fileName, new OrigJpegDecoder()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index f3722b5d6..92ead8164 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -108,14 +108,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } /// - /// Reference implementation to test . + /// Reference implementation to test . /// Rounding is done used an integer-based algorithm defined in . /// /// The input block /// The destination block of integers /// The quantization table /// Pointer to - public static unsafe void UnZigDivRoundRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) + public static unsafe void QuantizeRational(Block8x8F* src, int* dest, Block8x8F* qt, int* unzigPtr) { float* s = (float*)src; float* q = (float*)qt; From c6fe389c7f9f4154f41f709eecbac17e43d67e25 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Mon, 18 Sep 2017 20:32:40 +0200 Subject: [PATCH 29/29] Added extra check for when the number of bytes read is not the same as the count (fixes #338) --- .../Common/Extensions/StreamExtensions.cs | 11 +- .../Common/StreamExtensionsTests.cs | 108 ++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index d8fd45440..b717abab1 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -32,7 +32,16 @@ namespace SixLabors.ImageSharp byte[] foo = ArrayPool.Shared.Rent(count); try { - stream.Read(foo, 0, count); + while (count > 0) + { + int bytesRead = stream.Read(foo, 0, count); + if (bytesRead == 0) + { + break; + } + + count -= bytesRead; + } } finally { diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs new file mode 100644 index 000000000..8b2c65b07 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Common +{ + public class StreamExtensionsTests + { + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Skip_CountZeroOrLower_PositionNotChanged(int count) + { + using (var memStream = new MemoryStream(5)) + { + memStream.Position = 4; + memStream.Skip(count); + + Assert.Equal(4, memStream.Position); + } + } + + [Fact] + public void Skip_SeekableStream_SeekIsCalled() + { + using (var seekableStream = new SeekableStream(4)) + { + seekableStream.Skip(4); + + Assert.Equal(4, seekableStream.Offset); + Assert.Equal(SeekOrigin.Current, seekableStream.Loc); + } + } + + [Fact] + public void Skip_NonSeekableStream_BytesAreRead() + { + using (var nonSeekableStream = new NonSeekableStream()) + { + nonSeekableStream.Skip(5); + + Assert.Equal(3, nonSeekableStream.Counts.Count); + + Assert.Equal(5, nonSeekableStream.Counts[0]); + Assert.Equal(3, nonSeekableStream.Counts[1]); + Assert.Equal(1, nonSeekableStream.Counts[2]); + } + } + + [Fact] + public void Skip_EofStream_NoExceptionIsThrown() + { + using (var eofStream = new EofStream(7)) + { + eofStream.Skip(7); + + Assert.Equal(0, eofStream.Position); + } + } + + private class SeekableStream : MemoryStream + { + public long Offset; + public SeekOrigin Loc; + + public SeekableStream(int capacity) : base(capacity) { } + + public override long Seek(long offset, SeekOrigin loc) + { + this.Offset = offset; + this.Loc = loc; + return base.Seek(offset, loc); + } + } + + private class NonSeekableStream : MemoryStream + { + public override bool CanSeek => false; + + public List Counts = new List(); + + public NonSeekableStream() : base(4) { } + + public override int Read(byte[] buffer, int offset, int count) + { + this.Counts.Add(count); + + return Math.Min(2, count); + } + } + + private class EofStream : MemoryStream + { + public override bool CanSeek => false; + + public EofStream(int capacity) : base(capacity) { } + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + } + } +}