From c76897104771e577750361df28ab7f8ff30025bc 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 f4043b5ade..a517ea5b3a 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 ed7f9b020b96ea133cca14261f3b77594218d5a9 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 a517ea5b3a..fa299f8111 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 0afea0f9d66a2e85b3173d9d66010aea0f671cc5 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 fa299f8111..63a66de832 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 2fa2f1d459b4ce17f67eb55c2a06829e1f9c72e5 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 63a66de832..19e37689de 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 c1526747557597930b055b5cdc2a98675445fa33 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 19e37689de..aaff683123 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 2945aaef9afe8310d44c150fd1fd2a203ac26e4a 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 aaff683123..8ab245aee7 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 fef1366b501e0397c41ec8f1d785506cd1e7ecb5 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 0000000000..3bd6bf19ba --- /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 e4670d002b723f9a16bbaf304bc54d877061a5c2 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 829b5d8504..6045a9c6a4 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 df58be487e..480d2d4d88 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -34,7 +34,7 @@ - + All From 5dc06612b2cf0d231a97d7e124f3bf12bf12e0cc 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 8ab245aee7..511e66c646 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 c5b3d6f31a..c4de1c2988 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 0000000000..4291e775d4 --- /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 3bd6bf19ba..0000000000 --- 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 36d3b3c056..5b672059c2 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 b3dd763c7f..b367ecbd93 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 63fa1fdbe1c44fb72a96ceb3bde316637da97528 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 cb80a672aa..d6b2fad095 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 6f4f93d878..552ac0a018 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 44762a243a..e5f2fd5e7c 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 a1480f36f8c1bb41787e2994cccbe78418466d57 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 31ca5cef3b..13161220bc 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 7ba69a431f4fbca636802e473e3128031b1a0277 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 13161220bc..9ae2fb5355 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 2d0365bc4efc34acb6122957de59aea4e0b59d2c 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 9ae2fb5355..b1907cb39c 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 c7ef5cd773b0a7633d3951f8a2aacfe0c64d859e 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 b1907cb39c..9206e5b96c 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 001fefaa63f702c444e31816ed8b0358fe58bcdd 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 9206e5b96c..03ca0653f9 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 deb6a8bbf1bc44e512ff93d65951fef0240a98ef 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 063f42c00b..a228bc2362 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 1c84a3c3128680229687e2df591815ebf83044f0 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 191fa50ca5..3ef498e105 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 4c47017530..e679fddcf1 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 059b2e89ae..693afc6f2f 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 feb5164d73..53aafed01a 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 8a4f56e3db..bdea147937 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 e243938e33..aaefbb3af9 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 52c38bee83..59fa19be42 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 c8240bf083..38339e58f9 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 6b3f6ccd21..70d4df273e 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 78e390bbd2..9501a6c88b 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 8ff532b2f4..0a5eb8f61b 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 6a4ed5caed80f87431330e67c9013d9a217dca86 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 706fa1e202..0197dd917c 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 d8d24224b3..e26ab2cc9f 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 0a5eb8f61b..92332eba08 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 e6ad98c5ec9fa8c4f4abc21a484b2c42aa43f898 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 5867eaf85d..7fd7a59a9b 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 fa91eb6239408ede54b935f11c4ff28ff56b943a 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 e679fddcf1..7139260ec7 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 aaefbb3af9..18270f5bad 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 6252d82095..d426ed4f1d 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 984fb828cc..3a24bded33 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 59fa19be42..66c9bb3e14 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 063f42c00b..a1ff3550a2 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 38339e58f9..f3722b5d65 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 adafde4002e40ede8290d8a86f748f86fc9e14ee 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 0000000000..c1c5cfcded --- /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 3ef498e105..aecd5c59e7 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 d4d7d302ab741b1f7fb68b0f2d62d51dab6857eb 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 0000000000..c0b9a9002d --- /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 0000000000..1be936b302 --- /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 3f4c69c3ed..1291f160a9 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 aecd5c59e7..4ffb63480a 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 7139260ec7..2db869de70 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 e5f2fd5e7c..15a7941444 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 1e60b2a73c36bd71ab1d1509d0cb6a26d69e6287 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 4ffb63480a..6ba69bec4c 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 2db869de70..0459a5df00 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 693afc6f2f..fef6d64388 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 bdea147937..3ee6e72c5d 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 66c9bb3e14..1398b2f895 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 a9204f111528043960a766087cbe4d9efa9048dd 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 c0b9a9002d..590a3f5c93 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 347dd15ea261d640e2834f9b8e31aa92dd4d18e5 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 590a3f5c93..3335e6e377 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 1be936b302..4f43c98116 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 4c1b4f4d1e..e8614205c8 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 bef3e49149..9ab3ee12c6 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 6ba69bec4c..ff9db6ab9c 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 0459a5df00..d0f2b8817c 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 53aafed01a..87c1431e02 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 1398b2f895..8d1e585dbb 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 6e51489fe0c0732d586102202ab6a682db3dd8ca 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 e8614205c8..246df9d916 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 9ab3ee12c6..7b5b6823a3 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 ff9db6ab9c..fcf618e842 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 d0f2b8817c..a081c8415d 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 fef6d64388..b988695e1e 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 8d1e585dbb..94f8d2eadc 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 218ca1d33c44c782cfff68896f1b2aa38841d1a7 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 d6b2fad095..0188bc03cf 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 1291f160a9..96ed0a8c9e 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 246df9d916..93e9e03885 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 7b5b6823a3..dc0996b65d 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 fcf618e842..2dd42288cb 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 a081c8415d..574967b6bf 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 3a24bded33..2deb3f62d8 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 94f8d2eadc..bf5507676c 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 a228bc2362..baa31f674b 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 f3722b5d65..92ead8164f 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 f52845d97adacad34f6a1dc668173670e44fcd54 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 d8fd45440a..b717abab1c 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 0000000000..8b2c65b07b --- /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; + } + } + } +}