diff --git a/.editorconfig b/.editorconfig index b0d0662bf..0e4883082 100644 --- a/.editorconfig +++ b/.editorconfig @@ -339,6 +339,7 @@ csharp_space_between_square_brackets = false # warn when using var for built-in types, # warn when using var when the type is not apparent, and # warn when not using var when the type is apparent +# warn when using simplified "using" declaration ############################################################################### [*.cs] csharp_prefer_braces = true:silent @@ -367,6 +368,6 @@ csharp_style_throw_expression = true:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_var_for_built_in_types = false:warning -csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = never csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = false:warning diff --git a/.gitattributes b/.gitattributes index dd6625081..66aaca373 100644 --- a/.gitattributes +++ b/.gitattributes @@ -96,22 +96,22 @@ *.gif binary *.jpg binary *.ktx binary +*.otf binary *.pbm binary *.pdf binary *.png binary *.ppt binary *.pptx binary *.pvr binary -*.ttf binary *.snk binary *.tga binary +*.ttc binary *.ttf binary *.woff binary *.woff2 binary *.xls binary *.xlsx binary - ############################################################################### # Set explicit file behavior to: # diff as plain text diff --git a/Directory.Build.props b/Directory.Build.props index def231a7a..2f9863561 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -95,16 +95,16 @@ https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json; - 002400000c8000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97 + 00240000048000009400000006020000002400005253413100040000010001000147e6fe6766715eec6cfed61f1e7dcdbf69748a3e355c67e9d8dfd953acab1d5e012ba34b23308166fdc61ee1d0390d5f36d814a6091dd4b5ed9eda5a26afced924c683b4bfb4b3d64b0586a57eff9f02b1f84e3cb0ddd518bd1697f2c84dcbb97eb8bb5c7801be12112ed0ec86db934b0e9a5171e6bb1384b6d2f7d54dfa97 true + true - - + diff --git a/ImageSharp.sln b/ImageSharp.sln index 40878c575..f1d4afef4 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -334,6 +334,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests.ProfilingS EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution + shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2aa31a1f-142c-43f4-8687-09abca4b3a26}*SharedItemsImports = 5 shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/codecov.yml b/codecov.yml index 3941f7ff9..833fc0a51 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,3 +5,7 @@ codecov: # https://github.com/codecov/support/issues/363 # https://docs.codecov.io/docs/comparing-commits allow_coverage_offsets: true + + # Avoid Report Expired + # https://docs.codecov.io/docs/codecov-yaml#section-expired-reports + max_report_age: off diff --git a/shared-infrastructure b/shared-infrastructure index 36b2d55f5..ea561c249 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 36b2d55f5bb0d91024955bd26ba220ee41cc96e5 +Subproject commit ea561c249ba86352fe3b69e612b8072f3652eacb diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 5e3f9b061..a78a75d42 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -27,14 +27,13 @@ - - - + - - - - + + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 68d4f8949..d7171aa0f 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -52,4 +52,24 @@ + + + + + + + + + + + + + diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index d810296d6..0273f02f5 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -40,7 +41,7 @@ namespace SixLabors.ImageSharp.Advanced => GetConfiguration((IConfigurationProvider)source); /// - /// Gets the configuration . + /// Gets the configuration. /// /// The source image /// Returns the bounds of the image @@ -48,15 +49,58 @@ namespace SixLabors.ImageSharp.Advanced => source?.Configuration ?? Configuration.Default; /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. + /// Gets the representation of the pixels as a containing the backing pixel data of the image + /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. /// + /// The source image. /// The type of the pixel. - /// The source. + /// The . + /// + /// Certain Image Processors may invalidate the returned and all it's buffers, + /// therefore it's not recommended to mutate the image while holding a reference to it's . + /// + public static IMemoryGroup GetPixelMemoryGroup(this ImageFrame source) + where TPixel : unmanaged, IPixel + => source?.PixelBuffer.FastMemoryGroup.View ?? throw new ArgumentNullException(nameof(source)); + + /// + /// Gets the representation of the pixels as a containing the backing pixel data of the image + /// stored in row major order, as a list of contiguous blocks in the source image's pixel format. + /// + /// The source image. + /// The type of the pixel. + /// The . + /// + /// Certain Image Processors may invalidate the returned and all it's buffers, + /// therefore it's not recommended to mutate the image while holding a reference to it's . + /// + public static IMemoryGroup GetPixelMemoryGroup(this Image source) + where TPixel : unmanaged, IPixel + => source?.Frames.RootFrame.GetPixelMemoryGroup() ?? throw new ArgumentNullException(nameof(source)); + + /// + /// Gets the representation of the pixels as a in the source image's pixel format + /// stored in row major order, if the backing buffer is contiguous. + /// + /// The type of the pixel. + /// The source image. /// The + /// Thrown when the backing buffer is discontiguous. + [Obsolete( + @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this ImageFrame source) - where TPixel : struct, IPixel - => source.GetPixelMemory().Span; + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + + IMemoryGroup mg = source.GetPixelMemoryGroup(); + if (mg.Count > 1) + { + throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!"); + } + + return mg.Single().Span; + } /// /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format @@ -65,9 +109,16 @@ namespace SixLabors.ImageSharp.Advanced /// The type of the pixel. /// The source. /// The + /// Thrown when the backing buffer is discontiguous. + [Obsolete( + @"GetPixelSpan might fail, because the backing buffer could be discontiguous for large images. Use GetPixelMemoryGroup or GetPixelRowSpan instead!")] public static Span GetPixelSpan(this Image source) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelSpan(); + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + + return source.Frames.RootFrame.GetPixelSpan(); + } /// /// Gets the representation of the pixels as a of contiguous memory @@ -78,8 +129,14 @@ namespace SixLabors.ImageSharp.Advanced /// The row. /// The public static Span GetPixelRowSpan(this ImageFrame source, int rowIndex) - where TPixel : struct, IPixel - => source.PixelBuffer.GetRowSpan(rowIndex); + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.PixelBuffer.GetRowSpan(rowIndex); + } /// /// Gets the representation of the pixels as of of contiguous memory @@ -90,59 +147,13 @@ namespace SixLabors.ImageSharp.Advanced /// The row. /// The public static Span GetPixelRowSpan(this Image source, int rowIndex) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowSpan(rowIndex); - - /// - /// 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 contiguous memory area containing Width*Height TPixel elements laid out in row-major order. - /// - /// The Pixel format. - /// The source image frame - /// A pinnable reference the first root of the pixel buffer. - [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] - public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this ImageFrame source) - where TPixel : struct, IPixel - => ref DangerousGetPinnableReferenceToPixelBuffer((IPixelSource)source); - - /// - /// 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 contiguous memory area containing Width*Height TPixel elements laid out in row-major order. - /// - /// The Pixel format. - /// The source image - /// A pinnable reference the first root of the pixel buffer. - [Obsolete("This method will be removed in our next release! Please use MemoryMarshal.GetReference(source.GetPixelSpan())!")] - public static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(this Image source) - where TPixel : struct, IPixel - => ref source.Frames.RootFrame.DangerousGetPinnableReferenceToPixelBuffer(); - - /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. - /// - /// The Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this ImageFrame source) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - return source.PixelBuffer.MemorySource.Memory; - } + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); - /// - /// Gets the representation of the pixels as a of contiguous memory in the source image's pixel format - /// stored in row major order. - /// - /// The Pixel format. - /// The source - /// The - internal static Memory GetPixelMemory(this Image source) - where TPixel : struct, IPixel - { - return source.Frames.RootFrame.GetPixelMemory(); + return source.Frames.RootFrame.PixelBuffer.GetRowSpan(rowIndex); } /// @@ -153,9 +164,15 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) - where TPixel : struct, IPixel - => source.PixelBuffer.GetRowMemory(rowIndex); + public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.PixelBuffer.GetSafeRowMemory(rowIndex); + } /// /// Gets the representation of the pixels as of of contiguous memory @@ -165,9 +182,15 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - internal static Memory GetPixelRowMemory(this Image source, int rowIndex) - where TPixel : struct, IPixel - => source.Frames.RootFrame.GetPixelRowMemory(rowIndex); + public static Memory GetPixelRowMemory(this Image source, int rowIndex) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); + Guard.MustBeLessThan(rowIndex, source.Height, nameof(rowIndex)); + + return source.Frames.RootFrame.PixelBuffer.GetSafeRowMemory(rowIndex); + } /// /// Gets the assigned to 'source'. @@ -176,15 +199,5 @@ namespace SixLabors.ImageSharp.Advanced /// Returns the configuration. internal static MemoryAllocator GetMemoryAllocator(this IConfigurationProvider source) => GetConfiguration(source).MemoryAllocator; - - /// - /// 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 - /// A reference to the element. - private static ref TPixel DangerousGetPinnableReferenceToPixelBuffer(IPixelSource source) - where TPixel : struct, IPixel - => ref MemoryMarshal.GetReference(source.PixelBuffer.GetSpan()); } } diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 142ea3f3e..23ae62c7a 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -77,11 +79,12 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void Seed() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // This is we actually call all the individual methods you need to seed. AotCompileOctreeQuantizer(); AotCompileWuQuantizer(); + AotCompilePaletteQuantizer(); AotCompileDithering(); AotCompilePixelOperations(); @@ -107,11 +110,12 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileOctreeQuantizer() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer(false))) + using (var test = new OctreeFrameQuantizer(Configuration.Default, new OctreeQuantizer().Options)) { - test.AotGetPalette(); + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); } } @@ -120,12 +124,26 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileWuQuantizer() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer(false))) + using (var test = new WuFrameQuantizer(Configuration.Default, new WuQuantizer().Options)) { - test.QuantizeFrame(new ImageFrame(Configuration.Default, 1, 1)); - test.AotGetPalette(); + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); + } + } + + /// + /// This method pre-seeds the PaletteQuantizer in the AoT compiler for iOS. + /// + /// The pixel format. + private static void AotCompilePaletteQuantizer() + where TPixel : unmanaged, IPixel + { + using (var test = (PaletteFrameQuantizer)new PaletteQuantizer(Array.Empty()).CreateFrameQuantizer(Configuration.Default)) + { + var frame = new ImageFrame(Configuration.Default, 1, 1); + test.QuantizeFrame(frame, frame.Bounds()); } } @@ -134,13 +152,15 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompileDithering() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var test = new FloydSteinbergDiffuser(); + ErrorDither errorDither = ErrorDither.FloydSteinberg; + OrderedDither orderedDither = OrderedDither.Bayer2x2; TPixel pixel = default; using (var image = new ImageFrame(Configuration.Default, 1, 1)) { - test.Dither(image, pixel, pixel, 0, 0, 0, 0, 0); + errorDither.Dither(image, image.Bounds(), pixel, pixel, 0, 0, 0); + orderedDither.Dither(pixel, 0, 0, 0, 0); } } @@ -151,7 +171,7 @@ namespace SixLabors.ImageSharp.Advanced /// The image encoder to seed. /// The pixel format. private static void AotCodec(IImageDecoder decoder, IImageEncoder encoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { try { @@ -175,7 +195,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// The pixel format. private static void AotCompilePixelOperations() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var pixelOp = new PixelOperations(); pixelOp.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Clear); diff --git a/src/ImageSharp/Advanced/IImageVisitor.cs b/src/ImageSharp/Advanced/IImageVisitor.cs index ba8b13e2e..079db42c0 100644 --- a/src/ImageSharp/Advanced/IImageVisitor.cs +++ b/src/ImageSharp/Advanced/IImageVisitor.cs @@ -17,6 +17,6 @@ namespace SixLabors.ImageSharp.Advanced /// The image. /// The pixel type. void Visit(Image image) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Advanced/IPixelSource.cs b/src/ImageSharp/Advanced/IPixelSource.cs index a321e877b..d7162bc61 100644 --- a/src/ImageSharp/Advanced/IPixelSource.cs +++ b/src/ImageSharp/Advanced/IPixelSource.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Advanced /// /// The type of the pixel. internal interface IPixelSource - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Gets the pixel buffer. diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation.cs b/src/ImageSharp/Advanced/IRowIntervalOperation.cs new file mode 100644 index 000000000..980ed91a7 --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row interval. + /// + public interface IRowIntervalOperation + { + /// + /// Invokes the method passing the row interval. + /// + /// The row interval. + void Invoke(in RowInterval rows); + } +} diff --git a/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs new file mode 100644 index 000000000..47fcf253e --- /dev/null +++ b/src/ImageSharp/Advanced/IRowIntervalOperation{TBuffer}.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row interval with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowIntervalOperation + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row interval and a buffer. + /// + /// The row interval. + /// The contiguous region of memory. + void Invoke(in RowInterval rows, Span span); + } +} diff --git a/src/ImageSharp/Advanced/IRowOperation.cs b/src/ImageSharp/Advanced/IRowOperation.cs new file mode 100644 index 000000000..0a6065e4b --- /dev/null +++ b/src/ImageSharp/Advanced/IRowOperation.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row. + /// + public interface IRowOperation + { + /// + /// Invokes the method passing the row y coordinate. + /// + /// The row y coordinate. + void Invoke(int y); + } +} diff --git a/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs new file mode 100644 index 000000000..7a13930fa --- /dev/null +++ b/src/ImageSharp/Advanced/IRowOperation{TBuffer}.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Defines the contract for an action that operates on a row with a temporary buffer. + /// + /// The type of buffer elements. + public interface IRowOperation + where TBuffer : unmanaged + { + /// + /// Invokes the method passing the row and a buffer. + /// + /// The row y coordinate. + /// The contiguous region of memory. + void Invoke(int y, Span span); + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs similarity index 95% rename from src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs rename to src/ImageSharp/Advanced/ParallelExecutionSettings.cs index f17d70a2a..54ee06918 100644 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs +++ b/src/ImageSharp/Advanced/ParallelExecutionSettings.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,10 +6,10 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Advanced.ParallelUtils +namespace SixLabors.ImageSharp.Advanced { /// - /// Defines execution settings for methods in . + /// Defines execution settings for methods in . /// public readonly struct ParallelExecutionSettings { @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Advanced.ParallelUtils } /// - /// Get the default for a + /// Get the default for a /// /// The . /// The . diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs new file mode 100644 index 000000000..3f0f77ca3 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.Wrappers.cs @@ -0,0 +1,198 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing based on values defined + /// or . + /// Using this class is preferred over direct usage of utility methods. + /// + public static partial class ParallelRowIterator + { + private readonly struct RowOperationWrapper + where T : struct, IRowOperation + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperationWrapper( + int minY, + int maxY, + int stepY, + in T action) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + + for (int y = yMin; y < yMax; y++) + { + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(this.action).Invoke(y); + } + } + } + + private readonly struct RowOperationWrapper + where T : struct, IRowOperation + where TBuffer : unmanaged + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly int width; + private readonly MemoryAllocator allocator; + private readonly T action; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperationWrapper( + int minY, + int maxY, + int stepY, + int width, + MemoryAllocator allocator, + in T action) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; + this.allocator = allocator; + this.action = action; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + + using IMemoryOwner buffer = this.allocator.Allocate(this.width); + + Span span = buffer.Memory.Span; + + for (int y = yMin; y < yMax; y++) + { + Unsafe.AsRef(this.action).Invoke(y, span); + } + } + } + + private readonly struct RowIntervalOperationWrapper + where T : struct, IRowIntervalOperation + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper( + int minY, + int maxY, + int stepY, + in T operation) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.operation = operation; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + var rows = new RowInterval(yMin, yMax); + + // Skip the safety copy when invoking a potentially impure method on a readonly field + Unsafe.AsRef(in this.operation).Invoke(in rows); + } + } + + private readonly struct RowIntervalOperationWrapper + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly int minY; + private readonly int maxY; + private readonly int stepY; + private readonly int width; + private readonly MemoryAllocator allocator; + private readonly T operation; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperationWrapper( + int minY, + int maxY, + int stepY, + int width, + MemoryAllocator allocator, + in T operation) + { + this.minY = minY; + this.maxY = maxY; + this.stepY = stepY; + this.width = width; + this.allocator = allocator; + this.operation = operation; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int i) + { + int yMin = this.minY + (i * this.stepY); + + if (yMin >= this.maxY) + { + return; + } + + int yMax = Math.Min(yMin + this.stepY, this.maxY); + var rows = new RowInterval(yMin, yMax); + + using IMemoryOwner buffer = this.allocator.Allocate(this.width); + + Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span); + } + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelRowIterator.cs b/src/ImageSharp/Advanced/ParallelRowIterator.cs new file mode 100644 index 000000000..fb85de986 --- /dev/null +++ b/src/ImageSharp/Advanced/ParallelRowIterator.cs @@ -0,0 +1,288 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Advanced +{ + /// + /// Utility methods for batched processing of pixel row intervals. + /// Parallel execution is optimized for image processing based on values defined + /// or . + /// Using this class is preferred over direct usage of utility methods. + /// + public static partial class ParallelRowIterator + { + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row operation to perform. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single row. + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowOperation + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// + /// The type of row operation to perform. + /// The . + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowOperation + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y); + } + + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowOperation + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRows(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches. + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The operation defining the iteration logic on a single row. + public static void IterateRows( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowOperation + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + using IMemoryOwner buffer = allocator.Allocate(width); + Span span = buffer.Memory.Span; + + for (int y = top; y < bottom; y++) + { + Unsafe.AsRef(operation).Invoke(y, span); + } + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The type of row operation to perform. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single . + [MethodImpl(InliningOptions.ShortMethod)] + public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowIntervalOperation + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRowIntervals(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s. + /// + /// The type of row operation to perform. + /// The . + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRowIntervals( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowIntervalOperation + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + Unsafe.AsRef(in operation).Invoke(in rows); + return; + } + + int verticalStep = DivideCeil(rectangle.Height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The to get the parallel settings from. + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRowIntervals(Configuration configuration, Rectangle rectangle, in T operation) + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); + IterateRowIntervals(rectangle, in parallelSettings, in operation); + } + + /// + /// Iterate through the rows of a rectangle in optimized batches defined by -s + /// instantiating a temporary buffer for each invocation. + /// + /// The type of row operation to perform. + /// The type of buffer elements. + /// The . + /// The . + /// The operation defining the iteration logic on a single . + public static void IterateRowIntervals( + Rectangle rectangle, + in ParallelExecutionSettings parallelSettings, + in T operation) + where T : struct, IRowIntervalOperation + where TBuffer : unmanaged + { + ValidateRectangle(rectangle); + + int top = rectangle.Top; + int bottom = rectangle.Bottom; + int width = rectangle.Width; + int height = rectangle.Height; + + int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask); + int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); + MemoryAllocator allocator = parallelSettings.MemoryAllocator; + + // Avoid TPL overhead in this trivial case: + if (numOfSteps == 1) + { + var rows = new RowInterval(top, bottom); + using IMemoryOwner buffer = allocator.Allocate(width); + + Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span); + + return; + } + + int verticalStep = DivideCeil(height, numOfSteps); + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; + var wrappingOperation = new RowIntervalOperationWrapper(top, bottom, verticalStep, width, allocator, in operation); + + Parallel.For( + 0, + numOfSteps, + parallelOptions, + wrappingOperation.Invoke); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); + + private static void ValidateRectangle(Rectangle rectangle) + { + Guard.MustBeGreaterThan( + rectangle.Width, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); + + Guard.MustBeGreaterThan( + rectangle.Height, + 0, + $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); + } + } +} diff --git a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs b/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs deleted file mode 100644 index 4833dbafd..000000000 --- a/src/ImageSharp/Advanced/ParallelUtils/ParallelHelper.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; - -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Advanced.ParallelUtils -{ - /// - /// Utility methods for batched processing of pixel row intervals. - /// Parallel execution is optimized for image processing based on values defined - /// or . - /// Using this class is preferred over direct usage of utility methods. - /// - public static class ParallelHelper - { - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The . - /// The to get the parallel settings from. - /// The method body defining the iteration logic on a single . - public static void IterateRows(Rectangle rectangle, Configuration configuration, Action body) - { - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration); - - IterateRows(rectangle, parallelSettings, body); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s. - /// - /// The . - /// The . - /// The method body defining the iteration logic on a single . - public static void IterateRows( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action body) - { - ValidateRectangle(rectangle); - - int maxSteps = DivideCeil( - rectangle.Width * rectangle.Height, - parallelSettings.MinimumPixelsProcessedPerTask); - - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); - body(rows); - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - i => - { - int yMin = rectangle.Top + (i * verticalStep); - int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); - - var rows = new RowInterval(yMin, yMax); - body(rows); - }); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRowsWithTempBuffer( - Rectangle rectangle, - in ParallelExecutionSettings parallelSettings, - Action> body) - where T : unmanaged - { - ValidateRectangle(rectangle); - - int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); - - int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); - - MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator; - - // Avoid TPL overhead in this trivial case: - if (numOfSteps == 1) - { - var rows = new RowInterval(rectangle.Top, rectangle.Bottom); - using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) - { - body(rows, buffer.Memory); - } - - return; - } - - int verticalStep = DivideCeil(rectangle.Height, numOfSteps); - - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps }; - - Parallel.For( - 0, - numOfSteps, - parallelOptions, - i => - { - int yMin = rectangle.Top + (i * verticalStep); - int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); - - var rows = new RowInterval(yMin, yMax); - - using (IMemoryOwner buffer = memoryAllocator.Allocate(rectangle.Width)) - { - body(rows, buffer.Memory); - } - }); - } - - /// - /// Iterate through the rows of a rectangle in optimized batches defined by -s - /// instantiating a temporary buffer for each invocation. - /// - internal static void IterateRowsWithTempBuffer( - Rectangle rectangle, - Configuration configuration, - Action> body) - where T : unmanaged - { - IterateRowsWithTempBuffer(rectangle, ParallelExecutionSettings.FromConfiguration(configuration), body); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); - - private static void ValidateRectangle(Rectangle rectangle) - { - Guard.MustBeGreaterThan( - rectangle.Width, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Width)}"); - - Guard.MustBeGreaterThan( - rectangle.Height, - 0, - $"{nameof(rectangle)}.{nameof(rectangle.Height)}"); - } - } -} diff --git a/src/ImageSharp/Color/Color.NamedColors.cs b/src/ImageSharp/Color/Color.NamedColors.cs index 0575a3e99..240ce304d 100644 --- a/src/ImageSharp/Color/Color.NamedColors.cs +++ b/src/ImageSharp/Color/Color.NamedColors.cs @@ -1,13 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; + namespace SixLabors.ImageSharp { /// /// Contains static named color values. + /// /// public readonly partial struct Color { + private static readonly Lazy> NamedColorsLookupLazy = new Lazy>(CreateNamedColorsLookup, true); + /// /// Represents a matching the W3C definition that has an hex value of #F0F8FF. /// @@ -111,7 +117,7 @@ namespace SixLabors.ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #00FFFF. /// - public static readonly Color Cyan = FromRgba(0, 255, 255, 255); + public static readonly Color Cyan = Aqua; /// /// Represents a matching the W3C definition that has an hex value of #00008B. @@ -138,6 +144,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DarkGreen = FromRgba(0, 100, 0, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #A9A9A9. + /// + public static readonly Color DarkGrey = DarkGray; + /// /// Represents a matching the W3C definition that has an hex value of #BDB76B. /// @@ -188,6 +199,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DarkSlateGray = FromRgba(47, 79, 79, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #2F4F4F. + /// + public static readonly Color DarkSlateGrey = DarkSlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #00CED1. /// @@ -213,6 +229,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color DimGray = FromRgba(105, 105, 105, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #696969. + /// + public static readonly Color DimGrey = DimGray; + /// /// Represents a matching the W3C definition that has an hex value of #1E90FF. /// @@ -273,6 +294,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color GreenYellow = FromRgba(173, 255, 47, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #808080. + /// + public static readonly Color Grey = Gray; + /// /// Represents a matching the W3C definition that has an hex value of #F0FFF0. /// @@ -353,6 +379,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color LightGreen = FromRgba(144, 238, 144, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #D3D3D3. + /// + public static readonly Color LightGrey = LightGray; + /// /// Represents a matching the W3C definition that has an hex value of #FFB6C1. /// @@ -378,6 +409,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color LightSlateGray = FromRgba(119, 136, 153, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #778899. + /// + public static readonly Color LightSlateGrey = LightSlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #B0C4DE. /// @@ -406,7 +442,7 @@ namespace SixLabors.ImageSharp /// /// Represents a matching the W3C definition that has an hex value of #FF00FF. /// - public static readonly Color Magenta = FromRgba(255, 0, 255, 255); + public static readonly Color Magenta = Fuchsia; /// /// Represents a matching the W3C definition that has an hex value of #800000. @@ -643,6 +679,11 @@ namespace SixLabors.ImageSharp /// public static readonly Color SlateGray = FromRgba(112, 128, 144, 255); + /// + /// Represents a matching the W3C definition that has an hex value of #708090. + /// + public static readonly Color SlateGrey = SlateGray; + /// /// Represents a matching the W3C definition that has an hex value of #FFFAFA. /// @@ -679,9 +720,9 @@ namespace SixLabors.ImageSharp public static readonly Color Tomato = FromRgba(255, 99, 71, 255); /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. + /// Represents a matching the W3C definition that has an hex value of #00000000. /// - public static readonly Color Transparent = FromRgba(255, 255, 255, 0); + public static readonly Color Transparent = FromRgba(0, 0, 0, 0); /// /// Represents a matching the W3C definition that has an hex value of #40E0D0. @@ -717,5 +758,161 @@ namespace SixLabors.ImageSharp /// Represents a matching the W3C definition that has an hex value of #9ACD32. /// public static readonly Color YellowGreen = FromRgba(154, 205, 50, 255); + + private static Dictionary CreateNamedColorsLookup() + { + return new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(AliceBlue), AliceBlue }, + { nameof(AntiqueWhite), AntiqueWhite }, + { nameof(Aqua), Aqua }, + { nameof(Aquamarine), Aquamarine }, + { nameof(Azure), Azure }, + { nameof(Beige), Beige }, + { nameof(Bisque), Bisque }, + { nameof(Black), Black }, + { nameof(BlanchedAlmond), BlanchedAlmond }, + { nameof(Blue), Blue }, + { nameof(BlueViolet), BlueViolet }, + { nameof(Brown), Brown }, + { nameof(BurlyWood), BurlyWood }, + { nameof(CadetBlue), CadetBlue }, + { nameof(Chartreuse), Chartreuse }, + { nameof(Chocolate), Chocolate }, + { nameof(Coral), Coral }, + { nameof(CornflowerBlue), CornflowerBlue }, + { nameof(Cornsilk), Cornsilk }, + { nameof(Crimson), Crimson }, + { nameof(Cyan), Cyan }, + { nameof(DarkBlue), DarkBlue }, + { nameof(DarkCyan), DarkCyan }, + { nameof(DarkGoldenrod), DarkGoldenrod }, + { nameof(DarkGray), DarkGray }, + { nameof(DarkGreen), DarkGreen }, + { nameof(DarkGrey), DarkGrey }, + { nameof(DarkKhaki), DarkKhaki }, + { nameof(DarkMagenta), DarkMagenta }, + { nameof(DarkOliveGreen), DarkOliveGreen }, + { nameof(DarkOrange), DarkOrange }, + { nameof(DarkOrchid), DarkOrchid }, + { nameof(DarkRed), DarkRed }, + { nameof(DarkSalmon), DarkSalmon }, + { nameof(DarkSeaGreen), DarkSeaGreen }, + { nameof(DarkSlateBlue), DarkSlateBlue }, + { nameof(DarkSlateGray), DarkSlateGray }, + { nameof(DarkSlateGrey), DarkSlateGrey }, + { nameof(DarkTurquoise), DarkTurquoise }, + { nameof(DarkViolet), DarkViolet }, + { nameof(DeepPink), DeepPink }, + { nameof(DeepSkyBlue), DeepSkyBlue }, + { nameof(DimGray), DimGray }, + { nameof(DimGrey), DimGrey }, + { nameof(DodgerBlue), DodgerBlue }, + { nameof(Firebrick), Firebrick }, + { nameof(FloralWhite), FloralWhite }, + { nameof(ForestGreen), ForestGreen }, + { nameof(Fuchsia), Fuchsia }, + { nameof(Gainsboro), Gainsboro }, + { nameof(GhostWhite), GhostWhite }, + { nameof(Gold), Gold }, + { nameof(Goldenrod), Goldenrod }, + { nameof(Gray), Gray }, + { nameof(Green), Green }, + { nameof(GreenYellow), GreenYellow }, + { nameof(Grey), Grey }, + { nameof(Honeydew), Honeydew }, + { nameof(HotPink), HotPink }, + { nameof(IndianRed), IndianRed }, + { nameof(Indigo), Indigo }, + { nameof(Ivory), Ivory }, + { nameof(Khaki), Khaki }, + { nameof(Lavender), Lavender }, + { nameof(LavenderBlush), LavenderBlush }, + { nameof(LawnGreen), LawnGreen }, + { nameof(LemonChiffon), LemonChiffon }, + { nameof(LightBlue), LightBlue }, + { nameof(LightCoral), LightCoral }, + { nameof(LightCyan), LightCyan }, + { nameof(LightGoldenrodYellow), LightGoldenrodYellow }, + { nameof(LightGray), LightGray }, + { nameof(LightGreen), LightGreen }, + { nameof(LightGrey), LightGrey }, + { nameof(LightPink), LightPink }, + { nameof(LightSalmon), LightSalmon }, + { nameof(LightSeaGreen), LightSeaGreen }, + { nameof(LightSkyBlue), LightSkyBlue }, + { nameof(LightSlateGray), LightSlateGray }, + { nameof(LightSlateGrey), LightSlateGrey }, + { nameof(LightSteelBlue), LightSteelBlue }, + { nameof(LightYellow), LightYellow }, + { nameof(Lime), Lime }, + { nameof(LimeGreen), LimeGreen }, + { nameof(Linen), Linen }, + { nameof(Magenta), Magenta }, + { nameof(Maroon), Maroon }, + { nameof(MediumAquamarine), MediumAquamarine }, + { nameof(MediumBlue), MediumBlue }, + { nameof(MediumOrchid), MediumOrchid }, + { nameof(MediumPurple), MediumPurple }, + { nameof(MediumSeaGreen), MediumSeaGreen }, + { nameof(MediumSlateBlue), MediumSlateBlue }, + { nameof(MediumSpringGreen), MediumSpringGreen }, + { nameof(MediumTurquoise), MediumTurquoise }, + { nameof(MediumVioletRed), MediumVioletRed }, + { nameof(MidnightBlue), MidnightBlue }, + { nameof(MintCream), MintCream }, + { nameof(MistyRose), MistyRose }, + { nameof(Moccasin), Moccasin }, + { nameof(NavajoWhite), NavajoWhite }, + { nameof(Navy), Navy }, + { nameof(OldLace), OldLace }, + { nameof(Olive), Olive }, + { nameof(OliveDrab), OliveDrab }, + { nameof(Orange), Orange }, + { nameof(OrangeRed), OrangeRed }, + { nameof(Orchid), Orchid }, + { nameof(PaleGoldenrod), PaleGoldenrod }, + { nameof(PaleGreen), PaleGreen }, + { nameof(PaleTurquoise), PaleTurquoise }, + { nameof(PaleVioletRed), PaleVioletRed }, + { nameof(PapayaWhip), PapayaWhip }, + { nameof(PeachPuff), PeachPuff }, + { nameof(Peru), Peru }, + { nameof(Pink), Pink }, + { nameof(Plum), Plum }, + { nameof(PowderBlue), PowderBlue }, + { nameof(Purple), Purple }, + { nameof(RebeccaPurple), RebeccaPurple }, + { nameof(Red), Red }, + { nameof(RosyBrown), RosyBrown }, + { nameof(RoyalBlue), RoyalBlue }, + { nameof(SaddleBrown), SaddleBrown }, + { nameof(Salmon), Salmon }, + { nameof(SandyBrown), SandyBrown }, + { nameof(SeaGreen), SeaGreen }, + { nameof(SeaShell), SeaShell }, + { nameof(Sienna), Sienna }, + { nameof(Silver), Silver }, + { nameof(SkyBlue), SkyBlue }, + { nameof(SlateBlue), SlateBlue }, + { nameof(SlateGray), SlateGray }, + { nameof(SlateGrey), SlateGrey }, + { nameof(Snow), Snow }, + { nameof(SpringGreen), SpringGreen }, + { nameof(SteelBlue), SteelBlue }, + { nameof(Tan), Tan }, + { nameof(Teal), Teal }, + { nameof(Thistle), Thistle }, + { nameof(Tomato), Tomato }, + { nameof(Transparent), Transparent }, + { nameof(Turquoise), Turquoise }, + { nameof(Violet), Violet }, + { nameof(Wheat), Wheat }, + { nameof(White), White }, + { nameof(WhiteSmoke), WhiteSmoke }, + { nameof(Yellow), Yellow }, + { nameof(YellowGreen), YellowGreen } + }; + } } } diff --git a/src/ImageSharp/Color/Color.WernerPalette.cs b/src/ImageSharp/Color/Color.WernerPalette.cs index 768fe065c..2948b4c52 100644 --- a/src/ImageSharp/Color/Color.WernerPalette.cs +++ b/src/ImageSharp/Color/Color.WernerPalette.cs @@ -20,116 +20,116 @@ namespace SixLabors.ImageSharp private static Color[] CreateWernerPalette() => new[] { - FromHex("#f1e9cd"), - FromHex("#f2e7cf"), - FromHex("#ece6d0"), - FromHex("#f2eacc"), - FromHex("#f3e9ca"), - FromHex("#f2ebcd"), - FromHex("#e6e1c9"), - FromHex("#e2ddc6"), - FromHex("#cbc8b7"), - FromHex("#bfbbb0"), - FromHex("#bebeb3"), - FromHex("#b7b5ac"), - FromHex("#bab191"), - FromHex("#9c9d9a"), - FromHex("#8a8d84"), - FromHex("#5b5c61"), - FromHex("#555152"), - FromHex("#413f44"), - FromHex("#454445"), - FromHex("#423937"), - FromHex("#433635"), - FromHex("#252024"), - FromHex("#241f20"), - FromHex("#281f3f"), - FromHex("#1c1949"), - FromHex("#4f638d"), - FromHex("#383867"), - FromHex("#5c6b8f"), - FromHex("#657abb"), - FromHex("#6f88af"), - FromHex("#7994b5"), - FromHex("#6fb5a8"), - FromHex("#719ba2"), - FromHex("#8aa1a6"), - FromHex("#d0d5d3"), - FromHex("#8590ae"), - FromHex("#3a2f52"), - FromHex("#39334a"), - FromHex("#6c6d94"), - FromHex("#584c77"), - FromHex("#533552"), - FromHex("#463759"), - FromHex("#bfbac0"), - FromHex("#77747f"), - FromHex("#4a475c"), - FromHex("#b8bfaf"), - FromHex("#b2b599"), - FromHex("#979c84"), - FromHex("#5d6161"), - FromHex("#61ac86"), - FromHex("#a4b6a7"), - FromHex("#adba98"), - FromHex("#93b778"), - FromHex("#7d8c55"), - FromHex("#33431e"), - FromHex("#7c8635"), - FromHex("#8e9849"), - FromHex("#c2c190"), - FromHex("#67765b"), - FromHex("#ab924b"), - FromHex("#c8c76f"), - FromHex("#ccc050"), - FromHex("#ebdd99"), - FromHex("#ab9649"), - FromHex("#dbc364"), - FromHex("#e6d058"), - FromHex("#ead665"), - FromHex("#d09b2c"), - FromHex("#a36629"), - FromHex("#a77d35"), - FromHex("#f0d696"), - FromHex("#d7c485"), - FromHex("#f1d28c"), - FromHex("#efcc83"), - FromHex("#f3daa7"), - FromHex("#dfa837"), - FromHex("#ebbc71"), - FromHex("#d17c3f"), - FromHex("#92462f"), - FromHex("#be7249"), - FromHex("#bb603c"), - FromHex("#c76b4a"), - FromHex("#a75536"), - FromHex("#b63e36"), - FromHex("#b5493a"), - FromHex("#cd6d57"), - FromHex("#711518"), - FromHex("#e9c49d"), - FromHex("#eedac3"), - FromHex("#eecfbf"), - FromHex("#ce536b"), - FromHex("#b74a70"), - FromHex("#b7757c"), - FromHex("#612741"), - FromHex("#7a4848"), - FromHex("#3f3033"), - FromHex("#8d746f"), - FromHex("#4d3635"), - FromHex("#6e3b31"), - FromHex("#864735"), - FromHex("#553d3a"), - FromHex("#613936"), - FromHex("#7a4b3a"), - FromHex("#946943"), - FromHex("#c39e6d"), - FromHex("#513e32"), - FromHex("#8b7859"), - FromHex("#9b856b"), - FromHex("#766051"), - FromHex("#453b32") + ParseHex("#f1e9cd"), + ParseHex("#f2e7cf"), + ParseHex("#ece6d0"), + ParseHex("#f2eacc"), + ParseHex("#f3e9ca"), + ParseHex("#f2ebcd"), + ParseHex("#e6e1c9"), + ParseHex("#e2ddc6"), + ParseHex("#cbc8b7"), + ParseHex("#bfbbb0"), + ParseHex("#bebeb3"), + ParseHex("#b7b5ac"), + ParseHex("#bab191"), + ParseHex("#9c9d9a"), + ParseHex("#8a8d84"), + ParseHex("#5b5c61"), + ParseHex("#555152"), + ParseHex("#413f44"), + ParseHex("#454445"), + ParseHex("#423937"), + ParseHex("#433635"), + ParseHex("#252024"), + ParseHex("#241f20"), + ParseHex("#281f3f"), + ParseHex("#1c1949"), + ParseHex("#4f638d"), + ParseHex("#383867"), + ParseHex("#5c6b8f"), + ParseHex("#657abb"), + ParseHex("#6f88af"), + ParseHex("#7994b5"), + ParseHex("#6fb5a8"), + ParseHex("#719ba2"), + ParseHex("#8aa1a6"), + ParseHex("#d0d5d3"), + ParseHex("#8590ae"), + ParseHex("#3a2f52"), + ParseHex("#39334a"), + ParseHex("#6c6d94"), + ParseHex("#584c77"), + ParseHex("#533552"), + ParseHex("#463759"), + ParseHex("#bfbac0"), + ParseHex("#77747f"), + ParseHex("#4a475c"), + ParseHex("#b8bfaf"), + ParseHex("#b2b599"), + ParseHex("#979c84"), + ParseHex("#5d6161"), + ParseHex("#61ac86"), + ParseHex("#a4b6a7"), + ParseHex("#adba98"), + ParseHex("#93b778"), + ParseHex("#7d8c55"), + ParseHex("#33431e"), + ParseHex("#7c8635"), + ParseHex("#8e9849"), + ParseHex("#c2c190"), + ParseHex("#67765b"), + ParseHex("#ab924b"), + ParseHex("#c8c76f"), + ParseHex("#ccc050"), + ParseHex("#ebdd99"), + ParseHex("#ab9649"), + ParseHex("#dbc364"), + ParseHex("#e6d058"), + ParseHex("#ead665"), + ParseHex("#d09b2c"), + ParseHex("#a36629"), + ParseHex("#a77d35"), + ParseHex("#f0d696"), + ParseHex("#d7c485"), + ParseHex("#f1d28c"), + ParseHex("#efcc83"), + ParseHex("#f3daa7"), + ParseHex("#dfa837"), + ParseHex("#ebbc71"), + ParseHex("#d17c3f"), + ParseHex("#92462f"), + ParseHex("#be7249"), + ParseHex("#bb603c"), + ParseHex("#c76b4a"), + ParseHex("#a75536"), + ParseHex("#b63e36"), + ParseHex("#b5493a"), + ParseHex("#cd6d57"), + ParseHex("#711518"), + ParseHex("#e9c49d"), + ParseHex("#eedac3"), + ParseHex("#eecfbf"), + ParseHex("#ce536b"), + ParseHex("#b74a70"), + ParseHex("#b7757c"), + ParseHex("#612741"), + ParseHex("#7a4848"), + ParseHex("#3f3033"), + ParseHex("#8d746f"), + ParseHex("#4d3635"), + ParseHex("#6e3b31"), + ParseHex("#864735"), + ParseHex("#553d3a"), + ParseHex("#613936"), + ParseHex("#7a4b3a"), + ParseHex("#946943"), + ParseHex("#c39e6d"), + ParseHex("#513e32"), + ParseHex("#8b7859"), + ParseHex("#9b856b"), + ParseHex("#766051"), + ParseHex("#453b32") }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 5fad7a8e3..e0f9d1e8d 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -95,21 +95,102 @@ namespace SixLabors.ImageSharp public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); /// - /// Creates a new instance from the string representing a color in hexadecimal form. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. /// - /// Returns a that represents the color defined by the provided RGBA hex string. + /// + /// The . + /// [MethodImpl(InliningOptions.ShortMethod)] - public static Color FromHex(string hex) + public static Color ParseHex(string hex) { - var rgba = Rgba32.FromHex(hex); + var rgba = Rgba32.ParseHex(hex); return new Color(rgba); } + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Color result) + { + result = default; + + if (Rgba32.TryParseHex(hex, out Rgba32 rgba)) + { + result = new Color(rgba); + return true; + } + + return false; + } + + /// + /// Creates a new instance of the struct + /// from the given input string. + /// + /// + /// The name of the color or the hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// + /// The . + /// + public static Color Parse(string input) + { + Guard.NotNull(input, nameof(input)); + + if (!TryParse(input, out Color color)) + { + throw new ArgumentException("Input string is not in the correct format.", nameof(input)); + } + + return color; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given input string. + /// + /// + /// The name of the color or the hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + public static bool TryParse(string input, out Color result) + { + result = default; + + if (string.IsNullOrWhiteSpace(input)) + { + return false; + } + + if (NamedColorsLookupLazy.Value.TryGetValue(input, out result)) + { + return true; + } + + return TryParseHex(input, out result); + } + /// /// Alters the alpha channel of the color, returning a new instance. /// @@ -117,7 +198,7 @@ namespace SixLabors.ImageSharp /// The color having it's alpha channel altered. public Color WithAlpha(float alpha) { - Vector4 v = (Vector4)this; + var v = (Vector4)this; v.W = alpha; return new Color(v); } @@ -139,7 +220,7 @@ namespace SixLabors.ImageSharp /// The pixel value. [MethodImpl(InliningOptions.ShortMethod)] public TPixel ToPixel() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; pixel.FromRgba64(this.data); @@ -158,7 +239,7 @@ namespace SixLabors.ImageSharp Configuration configuration, ReadOnlySpan source, Span destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source); PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination); diff --git a/src/ImageSharp/ColorSpaces/Cmyk.cs b/src/ImageSharp/ColorSpaces/Cmyk.cs index c2331c379..5229cf14f 100644 --- a/src/ImageSharp/ColorSpaces/Cmyk.cs +++ b/src/ImageSharp/ColorSpaces/Cmyk.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.ColorSpaces [MethodImpl(InliningOptions.ShortMethod)] public Cmyk(Vector4 vector) { - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); this.C = vector.X; this.M = vector.Y; this.Y = vector.Z; diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs index 4be3f0079..5b312f4f8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.LinearRgb.cs @@ -248,7 +248,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion } /// - /// Performs the bulk conversion from into . + /// Performs the bulk conversion from into . /// /// The span to the source colors /// The span to the destination colors @@ -435,4 +435,4 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion return this.ToLinearRgb(rgb); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs index 8b9dbe1b8..4028b70b0 100644 --- a/src/ImageSharp/Common/Exceptions/ImageFormatException.cs +++ b/src/ImageSharp/Common/Exceptions/ImageFormatException.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp { /// /// The exception that is thrown when the library tries to load - /// an image, which has an invalid format. + /// an image, which has format or content that is invalid or unsupported by ImageSharp. /// public class ImageFormatException : Exception { diff --git a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs index cff6e3b60..983a1eb8b 100644 --- a/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs +++ b/src/ImageSharp/Common/Extensions/EnumerableExtensions.cs @@ -1,10 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Collections.Generic; -namespace SixLabors.ImageSharp.Common +namespace SixLabors.ImageSharp { /// /// Encapsulates a series of time saving extension methods to the interface. @@ -34,15 +34,11 @@ namespace SixLabors.ImageSharp.Common /// /// Generates a sequence of integral numbers within a specified range. /// - /// - /// The start index, inclusive. - /// + /// The start index, inclusive. /// /// A method that has one parameter and returns a calculating the end index. /// - /// - /// The incremental step. - /// + /// The incremental step. /// /// The that contains a range of sequential integral numbers. /// @@ -56,4 +52,4 @@ namespace SixLabors.ImageSharp.Common } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Extensions/StreamExtensions.cs b/src/ImageSharp/Common/Extensions/StreamExtensions.cs index 971bff322..5d8668257 100644 --- a/src/ImageSharp/Common/Extensions/StreamExtensions.cs +++ b/src/ImageSharp/Common/Extensions/StreamExtensions.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; using System.IO; using SixLabors.ImageSharp.Memory; +#if !SUPPORTS_SPAN_STREAM +using System.Buffers; +#endif namespace SixLabors.ImageSharp { diff --git a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs index f82774601..312ab388d 100644 --- a/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs +++ b/src/ImageSharp/Common/Helpers/Buffer2DUtils.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ComplexVector4 vector = default; int kernelLength = kernel.Length; diff --git a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs index ff6e3a4ec..462eeb302 100644 --- a/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs +++ b/src/ImageSharp/Common/Helpers/DenseMatrixUtils.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Convolve2DImpl( in matrixY, @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Convolve2DImpl( in matrixY, @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp out Vector4 vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp int minColumn, int maxColumn, out Vector4 vector) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Vector4 vectorY = default; Vector4 vectorX = default; @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp { int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); - Vector4Utils.Premultiply(ref currentColor); + Vector4Utilities.Premultiply(ref currentColor); vectorX += matrixX[y, x] * currentColor; vectorY += matrixY[y, x] * currentColor; @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Vector4 vector = default; @@ -193,7 +193,7 @@ namespace SixLabors.ImageSharp ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); vector.W = target.W; - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp int maxRow, int minColumn, int maxColumn) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Vector4 vector = default; @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp ref vector); ref Vector4 target = ref Unsafe.Add(ref targetRowRef, column); - Vector4Utils.UnPremultiply(ref vector); + Vector4Utilities.UnPremultiply(ref vector); target = vector; } @@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp int minColumn, int maxColumn, ref Vector4 vector) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int matrixHeight = matrix.Rows; int matrixWidth = matrix.Columns; @@ -270,7 +270,7 @@ namespace SixLabors.ImageSharp { int offsetX = (sourceOffsetColumnBase + x - radiusX).Clamp(minColumn, maxColumn); var currentColor = sourceRowSpan[offsetX].ToVector4(); - Vector4Utils.Premultiply(ref currentColor); + Vector4Utilities.Premultiply(ref currentColor); vector += matrix[y, x] * currentColor; } } diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs new file mode 100644 index 000000000..3ab1b199a --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp; + +namespace SixLabors +{ + internal static partial class Guard + { + /// + /// Ensures that the value is a value type. + /// + /// The target object, which cannot be null. + /// The name of the parameter that is to be checked. + /// The type of the value. + /// is not a value type. + [MethodImpl(InliningOptions.ShortMethod)] + public static void MustBeValueType(TValue value, string parameterName) + { + if (!value.GetType().GetTypeInfo().IsValueType) + { + ThrowHelper.ThrowArgumentException("Type must be a struct.", parameterName); + } + } + } +} diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index e7b14be42..92430c915 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -242,40 +242,6 @@ namespace SixLabors.ImageSharp return 1F; } - /// - /// Returns the result of a B-C filter against the given value. - /// - /// - /// The value to process. - /// The B-Spline curve variable. - /// The Cardinal curve variable. - /// - /// The . - /// - [MethodImpl(InliningOptions.ShortMethod)] - public static float GetBcValue(float x, float b, float c) - { - if (x < 0F) - { - x = -x; - } - - float temp = x * x; - if (x < 1F) - { - x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); - return x / 6F; - } - - if (x < 2F) - { - x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); - return x / 6F; - } - - return 0F; - } - /// /// Gets the bounding from the given points. /// @@ -303,7 +269,7 @@ namespace SixLabors.ImageSharp /// The . /// public static Rectangle GetFilteredBoundingRectangle(ImageFrame bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int width = bitmap.Width; int height = bitmap.Height; diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs new file mode 100644 index 000000000..ea1ffba05 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Avx2Intrinsics.cs @@ -0,0 +1,103 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +#if SUPPORTS_RUNTIME_INTRINSICS + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp +{ + internal static partial class SimdUtils + { + public static class Avx2Intrinsics + { + private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 }; + + /// + /// as many elements as possible, slicing them down (keeping the remainder). + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal static void NormalizedFloatToByteSaturateReduce( + ref ReadOnlySpan source, + ref Span dest) + { + DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); + + if (Avx2.IsSupported) + { + int remainder = ImageMaths.ModuloP2(source.Length, Vector.Count); + int adjustedCount = source.Length - remainder; + + if (adjustedCount > 0) + { + NormalizedFloatToByteSaturate( + source.Slice(0, adjustedCount), + dest.Slice(0, adjustedCount)); + + source = source.Slice(adjustedCount); + dest = dest.Slice(adjustedCount); + } + } + } + + /// + /// Implementation of , which is faster on new .NET runtime. + /// + /// + /// Implementation is based on MagicScaler code: + /// https://github.com/saucecontrol/PhotoSauce/blob/a9bd6e5162d2160419f0cf743fd4f536c079170b/src/MagicScaler/Magic/Processors/ConvertersFloat.cs#L453-L477 + /// + internal static void NormalizedFloatToByteSaturate( + ReadOnlySpan source, + Span dest) + { + VerifySpanInput(source, dest, Vector256.Count); + + int n = dest.Length / Vector256.Count; + + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + var maxBytes = Vector256.Create(255f); + ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); + Vector256 mask = Unsafe.As>(ref maskBase); + + for (int i = 0; i < n; i++) + { + ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); + + Vector256 f0 = s; + Vector256 f1 = Unsafe.Add(ref s, 1); + Vector256 f2 = Unsafe.Add(ref s, 2); + Vector256 f3 = Unsafe.Add(ref s, 3); + + Vector256 w0 = ConvertToInt32(f0, maxBytes); + Vector256 w1 = ConvertToInt32(f1, maxBytes); + Vector256 w2 = ConvertToInt32(f2, maxBytes); + Vector256 w3 = ConvertToInt32(f3, maxBytes); + + Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); + Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); + Vector256 b = Avx2.PackUnsignedSaturate(u0, u1); + b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte(); + + Unsafe.Add(ref destBase, i) = b; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(vf, scale); + return Avx.ConvertToVector256Int32(vf); + } + } + } +} +#endif diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs index bc07fbf31..1099678f7 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.BasicIntrinsics256.cs @@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp /// public static class BasicIntrinsics256 { - public static bool IsAvailable { get; } = IsAvx2CompatibleArchitecture; + public static bool IsAvailable { get; } = HasVector8; #if !SUPPORTS_EXTENDED_INTRINSICS /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat( + ByteToNormalizedFloat( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + NormalizedFloatToByteSaturate(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); source = source.Slice(adjustedCount); dest = dest.Slice(adjustedCount); @@ -78,15 +78,15 @@ namespace SixLabors.ImageSharp #endif /// - /// SIMD optimized implementation for . + /// SIMD optimized implementation for . /// Works only with span Length divisible by 8. /// Implementation adapted from: /// http://lolengine.net/blog/2011/3/20/understanding-fast-float-integer-conversions /// http://stackoverflow.com/a/536278 /// - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertByteToNormalizedFloat)); + VerifyHasVector8(nameof(ByteToNormalizedFloat)); VerifySpanInput(source, dest, 8); var bVec = new Vector(256.0f / 255.0f); @@ -94,17 +94,17 @@ namespace SixLabors.ImageSharp var magicInt = new Vector(1191182336); // reinterpreted value of 32768.0f var mask = new Vector(255); - ref Octet.OfByte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref Octet.OfUInt32 destBaseAsWideOctet = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Octet destBaseAsWideOctet = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); - ref Vector destBaseAsFloat = ref Unsafe.As>(ref destBaseAsWideOctet); + ref Vector destBaseAsFloat = ref Unsafe.As, Vector>(ref destBaseAsWideOctet); int n = dest.Length / 8; for (int i = 0; i < n; i++) { - ref Octet.OfByte s = ref Unsafe.Add(ref sourceBase, i); - ref Octet.OfUInt32 d = ref Unsafe.Add(ref destBaseAsWideOctet, i); + ref Octet s = ref Unsafe.Add(ref sourceBase, i); + ref Octet d = ref Unsafe.Add(ref destBaseAsWideOctet, i); d.LoadFrom(ref s); } @@ -124,11 +124,11 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of which is faster on older runtimes. + /// Implementation of which is faster on older runtimes. /// - internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByteClampOverflows)); + VerifyHasVector8(nameof(NormalizedFloatToByteSaturate)); VerifySpanInput(source, dest, 8); if (source.Length == 0) @@ -137,17 +137,17 @@ namespace SixLabors.ImageSharp } ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); int n = source.Length / 8; var magick = new Vector(32768.0f); var scale = new Vector(255f) / new Vector(256f); // need to copy to a temporary struct, because - // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) + // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report - var temp = default(Octet.OfUInt32); - ref Vector tempRef = ref Unsafe.As>(ref temp); + var temp = default(Octet); + ref Vector tempRef = ref Unsafe.As, Vector>(ref temp); for (int i = 0; i < n; i++) { @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp x = (x * scale) + magick; tempRef = x; - ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + ref Octet d = ref Unsafe.Add(ref destBase, i); d.LoadFrom(ref temp); } } @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp /// internal static void BulkConvertNormalizedFloatToByte(ReadOnlySpan source, Span dest) { - VerifyIsAvx2Compatible(nameof(BulkConvertNormalizedFloatToByte)); + VerifyHasVector8(nameof(BulkConvertNormalizedFloatToByte)); VerifySpanInput(source, dest, 8); if (source.Length == 0) @@ -186,17 +186,17 @@ namespace SixLabors.ImageSharp } ref Vector srcBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Octet.OfByte destBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + ref Octet destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); int n = source.Length / 8; var magick = new Vector(32768.0f); var scale = new Vector(255f) / new Vector(256f); // need to copy to a temporary struct, because - // SimdUtils.Octet.OfUInt32 temp = Unsafe.As, SimdUtils.Octet.OfUInt32>(ref x) + // SimdUtils.Octet temp = Unsafe.As, SimdUtils.Octet>(ref x) // does not work. TODO: This might be a CoreClr bug, need to ask/report - var temp = default(Octet.OfUInt32); - ref Vector tempRef = ref Unsafe.As>(ref temp); + var temp = default(Octet); + ref Vector tempRef = ref Unsafe.As, Vector>(ref temp); for (int i = 0; i < n; i++) { @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp x = (x * scale) + magick; tempRef = x; - ref Octet.OfByte d = ref Unsafe.Add(ref destBase, i); + ref Octet d = ref Unsafe.Add(ref destBase, i); d.LoadFrom(ref temp); } } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs index 7baa788e4..69d5dfa73 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.ExtendedIntrinsics.cs @@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); + ByteToNormalizedFloat(source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); source = source.Slice(adjustedCount); dest = dest.Slice(adjustedCount); @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows( + NormalizedFloatToByteSaturate( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -99,9 +99,9 @@ namespace SixLabors.ImageSharp } /// - /// Implementation , which is faster on new RyuJIT runtime. + /// Implementation , which is faster on new RyuJIT runtime. /// - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { VerifySpanInput(source, dest, Vector.Count); @@ -132,9 +132,9 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of , which is faster on new .NET runtime. + /// Implementation of , which is faster on new .NET runtime. /// - internal static void BulkConvertNormalizedFloatToByteClampOverflows( + internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span dest) { diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs index 565ea08f5..f16c91b40 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.FallbackIntrinsics128.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,10 +19,10 @@ namespace SixLabors.ImageSharp public static class FallbackIntrinsics128 { /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloatReduce( + internal static void ByteToNormalizedFloatReduce( ref ReadOnlySpan source, ref Span dest) { @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertByteToNormalizedFloat( + ByteToNormalizedFloat( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -43,10 +43,10 @@ namespace SixLabors.ImageSharp } /// - /// as many elements as possible, slicing them down (keeping the remainder). + /// as many elements as possible, slicing them down (keeping the remainder). /// [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflowsReduce( + internal static void NormalizedFloatToByteSaturateReduce( ref ReadOnlySpan source, ref Span dest) { @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp if (adjustedCount > 0) { - BulkConvertNormalizedFloatToByteClampOverflows( + NormalizedFloatToByteSaturate( source.Slice(0, adjustedCount), dest.Slice(0, adjustedCount)); @@ -67,10 +67,10 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of using . + /// Implementation of using . /// [MethodImpl(InliningOptions.ColdPath)] - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { VerifySpanInput(source, dest, 4); @@ -99,10 +99,10 @@ namespace SixLabors.ImageSharp } /// - /// Implementation of using . + /// Implementation of using . /// [MethodImpl(InliningOptions.ColdPath)] - internal static void BulkConvertNormalizedFloatToByteClampOverflows( + internal static void NormalizedFloatToByteSaturate( ReadOnlySpan source, Span dest) { @@ -125,10 +125,7 @@ namespace SixLabors.ImageSharp Vector4 s = Unsafe.Add(ref sBase, i); s *= maxBytes; s += half; - - // I'm not sure if Vector4.Clamp() is properly implemented with intrinsics. - s = Vector4.Max(Vector4.Zero, s); - s = Vector4.Min(maxBytes, s); + s = Vector4Utilities.FastClamp(s, Vector4.Zero, maxBytes); ref ByteVector4 d = ref Unsafe.Add(ref dBase, i); d.X = (byte)s.X; @@ -148,4 +145,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 4c34e28bc..0dc45d887 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -15,9 +15,10 @@ namespace SixLabors.ImageSharp internal static partial class SimdUtils { /// - /// Gets a value indicating whether the code is being executed on AVX2 CPU where both float and integer registers are of size 256 byte. + /// Gets a value indicating whether code is being JIT-ed to AVX2 instructions + /// where both float and integer registers are of size 256 byte. /// - public static bool IsAvx2CompatibleArchitecture { get; } = + public static bool HasVector8 { get; } = Vector.IsHardwareAccelerated && Vector.Count == 8 && Vector.Count == 8; /// @@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Vector4 PseudoRound(this Vector4 v) { - var sign = Vector4.Clamp(v, new Vector4(-1), new Vector4(1)); + var sign = Vector4Utilities.FastClamp(v, new Vector4(-1), new Vector4(1)); return v + (sign * 0.5f); } @@ -60,16 +61,18 @@ namespace SixLabors.ImageSharp /// The source span of bytes /// The destination span of floats [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertByteToNormalizedFloat(ReadOnlySpan source, Span dest) + internal static void ByteToNormalizedFloat(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); #if SUPPORTS_EXTENDED_INTRINSICS - ExtendedIntrinsics.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + ExtendedIntrinsics.ByteToNormalizedFloatReduce(ref source, ref dest); #else - BasicIntrinsics256.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + BasicIntrinsics256.ByteToNormalizedFloatReduce(ref source, ref dest); #endif - FallbackIntrinsics128.BulkConvertByteToNormalizedFloatReduce(ref source, ref dest); + + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.ByteToNormalizedFloatReduce(ref source, ref dest); // Deal with the remainder: if (source.Length > 0) @@ -87,16 +90,20 @@ namespace SixLabors.ImageSharp /// The source span of floats /// The destination span of bytes [MethodImpl(InliningOptions.ShortMethod)] - internal static void BulkConvertNormalizedFloatToByteClampOverflows(ReadOnlySpan source, Span dest) + internal static void NormalizedFloatToByteSaturate(ReadOnlySpan source, Span dest) { DebugGuard.IsTrue(source.Length == dest.Length, nameof(source), "Input spans must be of same length!"); -#if SUPPORTS_EXTENDED_INTRINSICS - ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); +#if SUPPORTS_RUNTIME_INTRINSICS + Avx2Intrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); +#elif SUPPORTS_EXTENDED_INTRINSICS + ExtendedIntrinsics.NormalizedFloatToByteSaturateReduce(ref source, ref dest); #else - BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + BasicIntrinsics256.NormalizedFloatToByteSaturateReduce(ref source, ref dest); #endif - FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflowsReduce(ref source, ref dest); + + // Also deals with the remainder from previous conversions: + FallbackIntrinsics128.NormalizedFloatToByteSaturateReduce(ref source, ref dest); // Deal with the remainder: if (source.Length > 0) @@ -151,9 +158,9 @@ namespace SixLabors.ImageSharp private static byte ConvertToByte(float f) => (byte)ComparableExtensions.Clamp((f * 255f) + 0.5f, 0, 255f); [Conditional("DEBUG")] - private static void VerifyIsAvx2Compatible(string operation) + private static void VerifyHasVector8(string operation) { - if (!IsAvx2CompatibleArchitecture) + if (!HasVector8) { throw new NotSupportedException($"{operation} is supported only on AVX2 CPU!"); } diff --git a/src/ImageSharp/Common/Helpers/Vector4Utils.cs b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs similarity index 85% rename from src/ImageSharp/Common/Helpers/Vector4Utils.cs rename to src/ImageSharp/Common/Helpers/Vector4Utilities.cs index 594a5ff10..9fb4eb790 100644 --- a/src/ImageSharp/Common/Helpers/Vector4Utils.cs +++ b/src/ImageSharp/Common/Helpers/Vector4Utilities.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,8 +11,20 @@ namespace SixLabors.ImageSharp /// /// Utility methods for the struct. /// - internal static class Vector4Utils + internal static class Vector4Utilities { + /// + /// Restricts a vector between a minimum and a maximum value. + /// 5x Faster then . + /// + /// The vector to restrict. + /// The minimum value. + /// The maximum value. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static Vector4 FastClamp(Vector4 x, Vector4 min, Vector4 max) + => Vector4.Min(Vector4.Max(x, min), max); + /// /// Pre-multiplies the "x", "y", "z" components of a vector by its "w" component leaving the "w" component intact. /// @@ -107,4 +119,4 @@ namespace SixLabors.ImageSharp } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Common/Tuples/Octet.cs b/src/ImageSharp/Common/Tuples/Octet.cs deleted file mode 100644 index 7ece2ed56..000000000 --- a/src/ImageSharp/Common/Tuples/Octet.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tuples -{ - /// - /// Contains 8 element value tuples of various types. - /// - internal static class Octet - { - /// - /// Value tuple of -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 $"{nameof(Octet)}.{nameof(OfUInt32)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(ref OfByte src) - { - this.V0 = src.V0; - this.V1 = src.V1; - this.V2 = src.V2; - this.V3 = src.V3; - this.V4 = src.V4; - this.V5 = src.V5; - this.V6 = src.V6; - this.V7 = src.V7; - } - } - - /// - /// Value tuple of -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 $"{nameof(Octet)}.{nameof(OfByte)}({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void LoadFrom(ref OfUInt32 src) - { - this.V0 = (byte)src.V0; - this.V1 = (byte)src.V1; - this.V2 = (byte)src.V2; - this.V3 = (byte)src.V3; - this.V4 = (byte)src.V4; - this.V5 = (byte)src.V5; - this.V6 = (byte)src.V6; - this.V7 = (byte)src.V7; - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs new file mode 100644 index 000000000..71e7da801 --- /dev/null +++ b/src/ImageSharp/Common/Tuples/Octet{T}.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Tuples +{ + /// + /// Contains 8 element value tuples of various types. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct Octet + where T : unmanaged + { + public T V0; + public T V1; + public T V2; + public T V3; + public T V4; + public T V5; + public T V6; + public T V7; + + /// + public override readonly string ToString() + { + return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})"; + } + } + + /// + /// Extension methods for the type. + /// + internal static class OctetExtensions + { + /// + /// Loads the fields in a target of from one of type. + /// + /// The target of instance. + /// The source of instance. + [MethodImpl(InliningOptions.ShortMethod)] + public static void LoadFrom(ref this Octet destination, ref Octet source) + { + destination.V0 = source.V0; + destination.V1 = source.V1; + destination.V2 = source.V2; + destination.V3 = source.V3; + destination.V4 = source.V4; + destination.V5 = source.V5; + destination.V6 = source.V6; + destination.V7 = source.V7; + } + + /// + /// Loads the fields in a target of from one of type. + /// + /// The target of instance. + /// The source of instance. + [MethodImpl(InliningOptions.ShortMethod)] + public static void LoadFrom(ref this Octet destination, ref Octet source) + { + destination.V0 = (byte)source.V0; + destination.V1 = (byte)source.V1; + destination.V2 = (byte)source.V2; + destination.V3 = (byte)source.V3; + destination.V4 = (byte)source.V4; + destination.V5 = (byte)source.V5; + destination.V6 = (byte)source.V6; + destination.V7 = (byte)source.V7; + } + } +} diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs index b3a32deee..1fdae0d5d 100644 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ b/src/ImageSharp/Common/Tuples/Vector4Pair.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tuples /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscalePreAvx2(float downscaleFactor) + internal void RoundAndDownscalePreVector8(float downscaleFactor) { ref Vector a = ref Unsafe.As>(ref this.A); a = a.FastRound(); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tuples /// TODO: Move it somewhere else. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleAvx2(float downscaleFactor) + internal void RoundAndDownscaleVector8(float downscaleFactor) { ref Vector self = ref Unsafe.As>(ref this); Vector v = self; @@ -79,4 +79,4 @@ namespace SixLabors.ImageSharp.Tuples return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 619be880a..47c7c54ea 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp /// The default value is 1MB. /// /// - /// Currently only used by Resize. + /// Currently only used by Resize. If the working buffer is expected to be discontiguous, + /// min(WorkingBufferSizeHintInBytes, BufferCapacityInBytes) should be used. /// internal int WorkingBufferSizeHintInBytes { get; set; } = 1 * 1024 * 1024; diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index a404ab418..a956f19c7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -28,11 +29,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Decode(stream); + var decoder = new BmpDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}. This error can happen for very large RLE bitmaps, which are not supported.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 8d82d28fb..dfdbb22fa 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -114,6 +114,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.options = options; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); + /// /// Decodes the image from the specified this._stream and sets /// the data to image. @@ -126,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { try { @@ -251,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The output pixel buffer containing the decoded image. /// Whether the bitmap is inverted. private void ReadBitFields(Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.infoHeader.BitsPerPixel == 16) { @@ -291,27 +296,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRle(BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; - using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) - using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; - if (compression == BmpCompression.RLE8) + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; + Span bufferSpan = buffer.Memory.Span; + if (compression is BmpCompression.RLE8) { - this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle8(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } else { - this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + this.UncompressRle4(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); } for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span bufferRow = buffer.GetRowSpan(y); + int rowStartIdx = y * width; + Span bufferRow = bufferSpan.Slice(rowStartIdx, width); Span pixelRow = pixels.GetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; @@ -321,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int x = 0; x < width; x++) { byte colorIdx = bufferRow[x]; - if (undefinedPixels[x, y]) + if (undefinedPixelsSpan[rowStartIdx + x]) { switch (this.options.RleSkippedPixelHandling) { @@ -368,16 +376,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRle24(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * 3, AllocationOptions.Clean)) - using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean)) + using (IMemoryOwner undefinedPixels = this.memoryAllocator.Allocate(width * height, AllocationOptions.Clean)) using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean)) { Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span; + Span undefinedPixelsSpan = undefinedPixels.Memory.Span; Span bufferSpan = buffer.GetSpan(); - this.UncompressRle24(width, bufferSpan, undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan); + + this.UncompressRle24(width, bufferSpan, undefinedPixelsSpan, rowsWithUndefinedPixelsSpan); for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); @@ -386,11 +396,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp if (rowHasUndefinedPixels) { // Slow path with undefined pixels. - int rowStartIdx = y * width * 3; + var yMulWidth = y * width; + int rowStartIdx = yMulWidth * 3; for (int x = 0; x < width; x++) { int idx = rowStartIdx + (x * 3); - if (undefinedPixels[x, y]) + if (undefinedPixelsSpan[yMulWidth + x]) { switch (this.options.RleSkippedPixelHandling) { @@ -803,7 +814,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// the bytes per color palette entry's can be 3 bytes instead of 4. /// Whether the bitmap is inverted. private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Pixels per byte (bits per pixel). int ppb = 8 / bitsPerPixel; @@ -861,7 +872,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The bitmask for the green channel. /// The bitmask for the blue channel. private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 2); int stride = (width * 2) + padding; @@ -928,7 +939,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb24(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 3); @@ -957,7 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); @@ -987,7 +998,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int padding = CalculatePadding(width, 4); @@ -1088,7 +1099,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The bitmask for the blue channel. /// The bitmask for the alpha channel. private void ReadRgb32BitFields(Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; int padding = CalculatePadding(width, 4); diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 612675c33..9c05ae2d5 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new BmpEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 1c7c606ca..7d2799503 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Bmp @@ -87,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; this.writeV4Header = options.SupportTransparency; - this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256); + this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; } /// @@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -202,7 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// private void WriteImage(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Buffer2D pixels = image.PixelBuffer; switch (this.bitsPerPixel) @@ -234,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write32Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { @@ -258,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write24Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { @@ -282,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write16Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) { @@ -308,7 +309,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The to write to. /// The containing pixel data. private void Write8Bit(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { bool isL8 = typeof(TPixel) == typeof(L8); using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) @@ -333,38 +334,38 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// A byte span of size 1024 for the color palette. private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(image, image.Bounds()); + + ReadOnlySpan quantizedColors = quantized.Palette.Span; + var color = default(Rgba32); + + // TODO: Use bulk conversion here for better perf + int idx = 0; + foreach (TPixel quantizedColor in quantizedColors) { - ReadOnlySpan quantizedColors = quantized.Palette.Span; - var color = default(Rgba32); + quantizedColor.ToRgba32(ref color); + colorPalette[idx] = color.B; + colorPalette[idx + 1] = color.G; + colorPalette[idx + 2] = color.R; - // TODO: Use bulk conversion here for better perf - int idx = 0; - foreach (TPixel quantizedColor in quantizedColors) - { - quantizedColor.ToRgba32(ref color); - colorPalette[idx] = color.B; - colorPalette[idx + 1] = color.G; - colorPalette[idx + 2] = color.R; - - // Padding byte, always 0. - colorPalette[idx + 3] = 0; - idx += 4; - } + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + idx += 4; + } + + stream.Write(colorPalette); - stream.Write(colorPalette); + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); + stream.Write(pixelSpan); - for (int y = image.Height - 1; y >= 0; y--) + for (int i = 0; i < this.padding; i++) { - ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); - stream.Write(pixelSpan); - - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } + stream.WriteByte(0); } } } @@ -377,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The containing pixel data. /// A byte span of size 1024 for the color palette. private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Create a color palette with 256 different gray values. for (int i = 0; i <= 255; i++) diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index c9d631da0..06c4b3fc6 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; @@ -21,11 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const string FileVersion = "89a"; - /// - /// The ASCII encoded bytes used to identify the GIF file. - /// - internal static readonly byte[] MagicNumber = Encoding.ASCII.GetBytes(FileType + FileVersion); - /// /// The extension block introducer !. /// @@ -51,11 +47,6 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public const string NetscapeApplicationIdentification = "NETSCAPE2.0"; - /// - /// The ASCII encoded application identification bytes. - /// - internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.ASCII.GetBytes(NetscapeApplicationIdentification); - /// /// The Netscape looping application sub block size. /// @@ -110,5 +101,25 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The collection of file extensions that equate to a Gif. /// public static readonly IEnumerable FileExtensions = new[] { "gif" }; + + /// + /// Gets the ASCII encoded bytes used to identify the GIF file (combining and ). + /// + internal static ReadOnlySpan MagicNumber => new[] + { + (byte)'G', (byte)'I', (byte)'F', + (byte)'8', (byte)'9', (byte)'a' + }; + + /// + /// Gets the ASCII encoded application identification bytes (representing ). + /// + internal static ReadOnlySpan NetscapeApplicationIdentificationBytes => new[] + { + (byte)'N', (byte)'E', (byte)'T', + (byte)'S', (byte)'C', (byte)'A', + (byte)'P', (byte)'E', + (byte)'2', (byte)'.', (byte)'0' + }; } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index 7691ec1aa..caa076553 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -24,10 +26,22 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new GifDecoderCore(configuration, this); - return decoder.Decode(stream); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 722c9c899..02267de1a 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -86,10 +86,15 @@ namespace SixLabors.ImageSharp.Formats.Gif public bool IgnoreMetadata { get; internal set; } /// - /// Gets the decoding mode for multi-frame images + /// Gets the decoding mode for multi-frame images. /// public FrameDecodingMode DecodingMode { get; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.imageDescriptor.Width, this.imageDescriptor.Height); + private MemoryAllocator MemoryAllocator => this.configuration.MemoryAllocator; /// @@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The stream containing image data. /// The decoded image public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image image = null; ImageFrame previousFrame = null; @@ -274,9 +279,8 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Could be XMP or something else not supported yet. - // Back up and skip. - this.stream.Position -= appLength + 1; - this.SkipBlock(appLength); + // Skip the subblock and terminator. + this.SkipBlock(subBlockSize); return; } @@ -344,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The image to decode the information to. /// The previous frame. private void ReadFrame(ref Image image, ref ImageFrame previousFrame) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.ReadImageDescriptor(); @@ -401,7 +405,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The color table containing the available colors. /// The private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Span indices, ReadOnlySpan colorTable, in GifImageDescriptor descriptor) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref byte indicesRef = ref MemoryMarshal.GetReference(indices); int imageWidth = this.logicalScreenDescriptor.Width; @@ -531,7 +535,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The frame. private void RestoreToBackground(ImageFrame frame) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.restoreArea is null) { diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs index 248915cb7..53c4c6f3f 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoder.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Gif @@ -17,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Gets or sets the quantizer for reducing the color count. /// Defaults to the /// - public IQuantizer Quantizer { get; set; } = new OctreeQuantizer(); + public IQuantizer Quantizer { get; set; } = KnownQuantizers.Octree; /// /// Gets or sets the color table mode: Global or local. @@ -26,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new GifEncoderCore(image.GetConfiguration(), this); encoder.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index a691e527e..887540930 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -6,8 +6,6 @@ using System.Buffers; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Configuration bound to the encoding operation. /// - private Configuration configuration; + private readonly Configuration configuration; /// /// A reusable buffer used to reduce allocations. @@ -70,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -81,14 +79,14 @@ namespace SixLabors.ImageSharp.Formats.Gif bool useGlobalTable = this.colorTableMode == GifColorTableMode.Global; // Quantize the image returning a palette. - IQuantizedFrame quantized; + IndexedImageFrame quantized; using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) { - quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + quantized = frameQuantizer.QuantizeFrame(image.Frames.RootFrame, image.Bounds()); } // Get the number of bits. - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); // Write the header. this.WriteHeader(stream); @@ -121,15 +119,20 @@ namespace SixLabors.ImageSharp.Formats.Gif } // Clean up. - quantized?.Dispose(); + quantized.Dispose(); // TODO: Write extension etc stream.WriteByte(GifConstants.EndIntroducer); } - private void EncodeGlobal(Image image, IQuantizedFrame quantized, int transparencyIndex, Stream stream) - where TPixel : struct, IPixel + private void EncodeGlobal(Image image, IndexedImageFrame quantized, int transparencyIndex, Stream stream) + where TPixel : unmanaged, IPixel { + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + EuclideanPixelMap pixelMap = default; + bool pixelMapSet = false; for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; @@ -144,25 +147,27 @@ namespace SixLabors.ImageSharp.Formats.Gif } else { - using (IFrameQuantizer paletteFrameQuantizer = - new PaletteFrameQuantizer(this.configuration, this.quantizer.Diffuser, quantized.Palette)) + if (!pixelMapSet) { - using (IQuantizedFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame)) - { - this.WriteImageData(paletteQuantized, stream); - } + pixelMapSet = true; + pixelMap = new EuclideanPixelMap(this.configuration, quantized.Palette); } + + using var paletteFrameQuantizer = new PaletteFrameQuantizer(this.configuration, this.quantizer.Options, pixelMap); + using IndexedImageFrame paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); + this.WriteImageData(paletteQuantized, stream); } } } - private void EncodeLocal(Image image, IQuantizedFrame quantized, Stream stream) - where TPixel : struct, IPixel + private void EncodeLocal(Image image, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel { ImageFrame previousFrame = null; GifFrameMetadata previousMeta = null; - foreach (ImageFrame frame in image.Frames) + for (int i = 0; i < image.Frames.Count; i++) { + ImageFrame frame = image.Frames[i]; ImageFrameMetadata metadata = frame.Metadata; GifFrameMetadata frameMetadata = metadata.GetGifMetadata(); if (quantized is null) @@ -171,27 +176,30 @@ namespace SixLabors.ImageSharp.Formats.Gif if (previousFrame != null && previousMeta.ColorTableLength != frameMetadata.ColorTableLength && frameMetadata.ColorTableLength > 0) { - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, frameMetadata.ColorTableLength)) + var options = new QuantizerOptions { - quantized = frameQuantizer.QuantizeFrame(frame); - } + Dither = this.quantizer.Options.Dither, + DitherScale = this.quantizer.Options.DitherScale, + MaxColors = frameMetadata.ColorTableLength + }; + + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration, options); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } else { - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration)) - { - quantized = frameQuantizer.QuantizeFrame(frame); - } + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(this.configuration); + quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } - this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8); + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length); this.WriteGraphicalControlExtension(frameMetadata, this.GetTransparentIndex(quantized), stream); this.WriteImageDescriptor(frame, true, stream); this.WriteColorTable(quantized, stream); this.WriteImageData(quantized, stream); - quantized?.Dispose(); + quantized.Dispose(); quantized = null; // So next frame can regenerate it previousFrame = frame; previousMeta = frameMetadata; @@ -206,25 +214,23 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The . /// - private int GetTransparentIndex(IQuantizedFrame quantized) - where TPixel : struct, IPixel + private int GetTransparentIndex(IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { - // Transparent pixels are much more likely to be found at the end of a palette + // Transparent pixels are much more likely to be found at the end of a palette. int index = -1; - int length = quantized.Palette.Length; + ReadOnlySpan paletteSpan = quantized.Palette.Span; - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(length)) - { - Span rgbaSpan = rgbaBuffer.GetSpan(); - ref Rgba32 paletteRef = ref MemoryMarshal.GetReference(rgbaSpan); - PixelOperations.Instance.ToRgba32(this.configuration, quantized.Palette.Span, rgbaSpan); + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteSpan.Length); + Span rgbaSpan = rgbaOwner.GetSpan(); + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, rgbaSpan); + ref Rgba32 rgbaSpanRef = ref MemoryMarshal.GetReference(rgbaSpan); - for (int i = quantized.Palette.Length - 1; i >= 0; i--) + for (int i = rgbaSpan.Length - 1; i >= 0; i--) + { + if (Unsafe.Add(ref rgbaSpanRef, i).Equals(default)) { - if (Unsafe.Add(ref paletteRef, i).Equals(default)) - { - index = i; - } + index = i; } } @@ -236,7 +242,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length); + private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber); /// /// Writes the logical screen descriptor to the stream. @@ -324,8 +330,9 @@ namespace SixLabors.ImageSharp.Formats.Gif return; } - foreach (string comment in metadata.Comments) + for (var i = 0; i < metadata.Comments.Count; i++) { + string comment = metadata.Comments[i]; this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = GifConstants.CommentLabel; stream.Write(this.buffer, 0, 2); @@ -333,7 +340,9 @@ namespace SixLabors.ImageSharp.Formats.Gif // Comment will be stored in chunks of 255 bytes, if it exceeds this size. ReadOnlySpan commentSpan = comment.AsSpan(); int idx = 0; - for (; idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; idx += GifConstants.MaxCommentSubBlockLength) + for (; + idx <= comment.Length - GifConstants.MaxCommentSubBlockLength; + idx += GifConstants.MaxCommentSubBlockLength) { WriteCommentSubBlock(stream, commentSpan, idx, GifConstants.MaxCommentSubBlockLength); } @@ -389,7 +398,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The extension to write to the stream. /// The stream to write to. - public void WriteExtension(IGifExtension extension, Stream stream) + private void WriteExtension(TGifExtension extension, Stream stream) + where TGifExtension : struct, IGifExtension { this.buffer[0] = GifConstants.ExtensionIntroducer; this.buffer[1] = extension.Label; @@ -409,7 +419,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Whether to use the global color table. /// The stream to write to. private void WriteImageDescriptor(ImageFrame image, bool hasColorTable, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { byte packedValue = GifImageDescriptor.GetPackedValue( localColorTableFlag: hasColorTable, @@ -435,37 +445,33 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The pixel format. /// The to encode. /// The stream to write to. - private void WriteColorTable(IQuantizedFrame image, Stream stream) - where TPixel : struct, IPixel + private void WriteColorTable(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel { // The maximum number of colors for the bit depth - int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3; - int pixelCount = image.Palette.Length; + int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf(); - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - { - PixelOperations.Instance.ToRgb24Bytes( - this.configuration, - image.Palette.Span, - colorTable.GetSpan(), - pixelCount); - stream.Write(colorTable.Array, 0, colorTableLength); - } + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean); + PixelOperations.Instance.ToRgb24Bytes( + this.configuration, + image.Palette.Span, + colorTable.GetSpan(), + image.Palette.Length); + + stream.Write(colorTable.Array, 0, colorTableLength); } /// /// Writes the image pixel data to the stream. /// /// The pixel format. - /// The containing indexed pixels. + /// The containing indexed pixels. /// The stream to write to. - private void WriteImageData(IQuantizedFrame image, Stream stream) - where TPixel : struct, IPixel + private void WriteImageData(IndexedImageFrame image, Stream stream) + where TPixel : unmanaged, IPixel { - using (var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth)) - { - encoder.Encode(image.GetPixelSpan(), stream); - } + using var encoder = new LzwEncoder(this.memoryAllocator, (byte)this.bitDepth); + encoder.Encode(image.GetPixelBufferSpan(), stream); } } } diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs index b00db6752..5fe86c4dd 100644 --- a/src/ImageSharp/Formats/Gif/GifMetadata.cs +++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs @@ -36,10 +36,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Gets or sets the number of times any animation is repeated. /// - /// 0 means to repeat indefinitely, count is set as play n + 1 times + /// 0 means to repeat indefinitely, count is set as repeat n-1 times. Defaults to 1. /// /// - public ushort RepeatCount { get; set; } + public ushort RepeatCount { get; set; } = 1; /// /// Gets or sets the color table mode. diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index eda0c5fb8..056076bf0 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Gif ent = this.NextPixel(indexedPixels); - // TODO: PERF: It looks likt hshift could be calculated once statically. + // TODO: PERF: It looks like hshift could be calculated once statically. hshift = 0; for (fcode = this.hsize; fcode < 65536; fcode *= 2) { diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index 8af5b81c2..5e26370ba 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Gif buffer[0] = GifConstants.ApplicationBlockSize; // Write NETSCAPE2.0 - GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11)); + GifConstants.NetscapeApplicationIdentificationBytes.CopyTo(buffer.Slice(1, 11)); // Application Data ---- buffer[12] = 3; // Application block length (always 3) diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index e8e84de7d..7a7fc4b26 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -18,8 +18,9 @@ namespace SixLabors.ImageSharp.Formats /// The configuration for the image. /// The containing image data. /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; /// /// Decodes the image from the specified stream to an . @@ -27,6 +28,7 @@ namespace SixLabors.ImageSharp.Formats /// The configuration for the image. /// The containing image data. /// The . + // TODO: Document ImageFormatExceptions (https://github.com/SixLabors/ImageSharp/issues/1110) Image Decode(Configuration configuration, Stream stream); } } diff --git a/src/ImageSharp/Formats/IImageEncoder.cs b/src/ImageSharp/Formats/IImageEncoder.cs index 76d831d5a..d5ff4b93c 100644 --- a/src/ImageSharp/Formats/IImageEncoder.cs +++ b/src/ImageSharp/Formats/IImageEncoder.cs @@ -18,6 +18,6 @@ namespace SixLabors.ImageSharp.Formats /// The to encode from. /// The to encode the image data to. void Encode(Image image, Stream stream) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 0b69e3f8b..acde84c91 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public Block8x8(Span coefficients) { ref byte selfRef = ref Unsafe.As(ref this); - ref byte sourceRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(coefficients)); + ref byte sourceRef = ref Unsafe.As(ref MemoryMarshal.GetReference(coefficients)); Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 23b51f092..8e14ed2c3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -99,29 +99,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components var CMax4 = new Vector4(maximum); var COff4 = new Vector4(MathF.Ceiling(maximum / 2)); - 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); + this.V0L = Vector4Utilities.FastClamp(this.V0L + COff4, CMin4, CMax4); + this.V0R = Vector4Utilities.FastClamp(this.V0R + COff4, CMin4, CMax4); + this.V1L = Vector4Utilities.FastClamp(this.V1L + COff4, CMin4, CMax4); + this.V1R = Vector4Utilities.FastClamp(this.V1R + COff4, CMin4, CMax4); + this.V2L = Vector4Utilities.FastClamp(this.V2L + COff4, CMin4, CMax4); + this.V2R = Vector4Utilities.FastClamp(this.V2R + COff4, CMin4, CMax4); + this.V3L = Vector4Utilities.FastClamp(this.V3L + COff4, CMin4, CMax4); + this.V3R = Vector4Utilities.FastClamp(this.V3R + COff4, CMin4, CMax4); + this.V4L = Vector4Utilities.FastClamp(this.V4L + COff4, CMin4, CMax4); + this.V4R = Vector4Utilities.FastClamp(this.V4R + COff4, CMin4, CMax4); + this.V5L = Vector4Utilities.FastClamp(this.V5L + COff4, CMin4, CMax4); + this.V5R = Vector4Utilities.FastClamp(this.V5R + COff4, CMin4, CMax4); + this.V6L = Vector4Utilities.FastClamp(this.V6L + COff4, CMin4, CMax4); + this.V6R = Vector4Utilities.FastClamp(this.V6R + COff4, CMin4, CMax4); + this.V7L = Vector4Utilities.FastClamp(this.V7L + COff4, CMin4, CMax4); + this.V7R = Vector4Utilities.FastClamp(this.V7R + COff4, CMin4, CMax4); } /// /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + public void NormalizeColorsAndRoundInplaceVector8(float maximum) { var off = new Vector(MathF.Ceiling(maximum / 2)); var max = new Vector(maximum); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index 176591972..a1a6b0172 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components for (int j = 0; j < 2; j++) { char side = j == 0 ? 'L' : 'R'; - Write($"this.V{i}{side} = Vector4.Clamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); + Write($"this.V{i}{side} = Vector4Utilities.FastClamp(this.V{i}{side} + COff4, CMin4, CMax4);\r\n"); } } PopIndent(); @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// AVX2-only variant for executing and in one step. /// [MethodImpl(InliningOptions.ShortMethod)] - public void NormalizeColorsAndRoundInplaceAvx2(float maximum) + public void NormalizeColorsAndRoundInplaceVector8(float maximum) { var off = new Vector(MathF.Ceiling(maximum / 2)); var max = new Vector(maximum); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs similarity index 78% rename from src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs rename to src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs index 6bf9c8483..064ca7553 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs @@ -15,29 +15,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical scale factors. /// [MethodImpl(InliningOptions.ShortMethod)] - public void CopyTo(in BufferArea area, int horizontalScale, int verticalScale) + public void ScaledCopyTo(in BufferArea area, int horizontalScale, int verticalScale) + { + ref float areaOrigin = ref area.GetReferenceToOrigin(); + this.ScaledCopyTo(ref areaOrigin, area.Stride, horizontalScale, verticalScale); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void ScaledCopyTo(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) { if (horizontalScale == 1 && verticalScale == 1) { - this.Copy1x1Scale(area); + this.Copy1x1Scale(ref areaOrigin, areaStride); return; } if (horizontalScale == 2 && verticalScale == 2) { - this.Copy2x2Scale(area); + this.Copy2x2Scale(ref areaOrigin, areaStride); return; } // TODO: Optimize: implement all cases with scale-specific, loopless code! - this.CopyArbitraryScale(area, horizontalScale, verticalScale); + this.CopyArbitraryScale(ref areaOrigin, areaStride, horizontalScale, verticalScale); } - public void Copy1x1Scale(in BufferArea destination) + public void Copy1x1Scale(ref float areaOrigin, int areaStride) { ref byte selfBase = ref Unsafe.As(ref this); - ref byte destBase = ref Unsafe.As(ref destination.GetReferenceToOrigin()); - int destStride = destination.Stride * sizeof(float); + ref byte destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride * sizeof(float); CopyRowImpl(ref selfBase, ref destBase, destStride, 0); CopyRowImpl(ref selfBase, ref destBase, destStride, 1); @@ -57,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); } - private void Copy2x2Scale(in BufferArea area) + private void Copy2x2Scale(ref float areaOrigin, int areaStride) { - ref Vector2 destBase = ref Unsafe.As(ref area.GetReferenceToOrigin()); - int destStride = area.Stride / 2; + ref Vector2 destBase = ref Unsafe.As(ref areaOrigin); + int destStride = areaStride / 2; this.WidenCopyRowImpl2x2(ref destBase, 0, destStride); this.WidenCopyRowImpl2x2(ref destBase, 1, destStride); @@ -110,10 +117,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } [MethodImpl(InliningOptions.ColdPath)] - private void CopyArbitraryScale(BufferArea area, int horizontalScale, int verticalScale) + private void CopyArbitraryScale(ref float areaOrigin, int areaStride, int horizontalScale, int verticalScale) { - ref float destBase = ref area.GetReferenceToOrigin(); - for (int y = 0; y < 8; y++) { int yy = y * verticalScale; @@ -127,16 +132,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components for (int i = 0; i < verticalScale; i++) { - int baseIdx = ((yy + i) * area.Stride) + xx; + int baseIdx = ((yy + i) * areaStride) + xx; for (int j = 0; j < horizontalScale; j++) { // area[xx + j, yy + i] = value; - Unsafe.Add(ref destBase, baseIdx + j) = value; + Unsafe.Add(ref areaOrigin, baseIdx + j) = value; } } } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index f11b0f8fa..70a34ddcf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -201,7 +201,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public void CopyTo(Span dest) + public void ScaledCopyTo(Span dest) { ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); ref byte s = ref Unsafe.As(ref this); @@ -215,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Pointer to block /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) { float* fPtr = (float*)blockPtr; for (int i = 0; i < Size; i++) @@ -231,9 +231,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// The block pointer. /// The destination. [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void CopyTo(Block8x8F* blockPtr, Span dest) + public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) { - blockPtr->CopyTo(dest); + blockPtr->ScaledCopyTo(dest); } /// @@ -241,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Destination [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void CopyTo(float[] dest) + public unsafe void ScaledCopyTo(float[] dest) { fixed (void* ptr = &this.V0L) { @@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Copy raw 32bit floating point data to dest /// /// Destination - public unsafe void CopyTo(Span dest) + public unsafe void ScaledCopyTo(Span dest) { fixed (Vector4* ptr = &this.V0L) { @@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public float[] ToArray() { var result = new float[Size]; - this.CopyTo(result); + this.ScaledCopyTo(result); return result; } @@ -471,9 +471,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// public void NormalizeColorsAndRoundInplace(float maximum) { - if (SimdUtils.IsAvx2CompatibleArchitecture) + if (SimdUtils.HasVector8) { - this.NormalizeColorsAndRoundInplaceAvx2(maximum); + this.NormalizeColorsAndRoundInplaceVector8(maximum); } else { @@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public void LoadFrom(ref Block8x8 source) { #if SUPPORTS_EXTENDED_INTRINSICS - if (SimdUtils.IsAvx2CompatibleArchitecture) + if (SimdUtils.HasVector8) { this.LoadFromInt16ExtendedAvx2(ref source); return; @@ -513,7 +513,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public void LoadFromInt16ExtendedAvx2(ref Block8x8 source) { DebugGuard.IsTrue( - SimdUtils.IsAvx2CompatibleArchitecture, + SimdUtils.HasVector8, "LoadFromUInt16ExtendedAvx2 only works on AVX2 compatible architecture!"); ref Vector sRef = ref Unsafe.As>(ref source); @@ -589,7 +589,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { // sign(dividend) = max(min(dividend, 1), -1) - var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One); + var sign = Vector4Utilities.FastClamp(dividend, NegativeOne, Vector4.One); // AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend) return (dividend / divisor) + (sign * Offset); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs index 1706b4c1b..09d6a4d1d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs @@ -90,15 +90,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters 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.RoundAndDownscalePreAvx2(maxValue); - g.RoundAndDownscalePreAvx2(maxValue); - b.RoundAndDownscalePreAvx2(maxValue); + r.RoundAndDownscalePreVector8(maxValue); + g.RoundAndDownscalePreVector8(maxValue); + b.RoundAndDownscalePreVector8(maxValue); } - else if (SimdUtils.IsAvx2CompatibleArchitecture) + else if (SimdUtils.HasVector8) { - r.RoundAndDownscaleAvx2(maxValue); - g.RoundAndDownscaleAvx2(maxValue); - b.RoundAndDownscaleAvx2(maxValue); + r.RoundAndDownscaleVector8(maxValue); + g.RoundAndDownscaleVector8(maxValue); + b.RoundAndDownscaleVector8(maxValue); } else { @@ -114,4 +114,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs index 093ea2f9a..8c1b427ee 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs @@ -13,14 +13,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverter { - internal sealed class FromYCbCrSimdAvx2 : JpegColorConverter + internal sealed class FromYCbCrSimdVector8 : JpegColorConverter { - public FromYCbCrSimdAvx2(int precision) + public FromYCbCrSimdVector8(int precision) : base(JpegColorSpace.YCbCr, precision) { } - public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture; + public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.HasVector8; public override void ConvertToRgba(in ComponentValues values, Span result) { @@ -107,4 +107,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 61e359869..7ada1b9da 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, float precision) + public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision) { JpegColorConverter converter = Array.Find(Converters, c => c.ColorSpace == colorSpace && c.Precision == precision); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// Returns the for the YCbCr colorspace that matches the current CPU architecture. /// private static JpegColorConverter GetYCbCrConverter(int precision) => - FromYCbCrSimdAvx2.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdAvx2(precision) : new FromYCbCrSimd(precision); + FromYCbCrSimdVector8.IsAvailable ? (JpegColorConverter)new FromYCbCrSimdVector8(precision) : new FromYCbCrSimd(precision); /// /// A stack-only struct to reference the input buffers using -s. @@ -232,4 +232,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index 44f9048a5..db4b6a532 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -68,11 +68,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . /// /// The source block. - /// The destination buffer area. + /// Reference to the origin of the destination pixel area. + /// The width of the destination pixel buffer. /// The maximum value derived from the bitdepth. public void ProcessBlockColorsInto( ref Block8x8 sourceBlock, - in BufferArea destArea, + ref float destAreaOrigin, + int destAreaStride, float maximumValue) { ref Block8x8F b = ref this.SourceBlock; @@ -88,7 +90,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // To be "more accurate", we need to emulate this by rounding! this.WorkspaceBlock1.NormalizeColorsAndRoundInplace(maximumValue); - this.WorkspaceBlock1.CopyTo(destArea, this.subSamplingDivisors.Width, this.subSamplingDivisors.Height); + this.WorkspaceBlock1.ScaledCopyTo( + ref destAreaOrigin, + destAreaStride, + this.subSamplingDivisors.Width, + this.subSamplingDivisors.Height); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 39c8be312..d9fd9ac8b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -31,12 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.Component = component; this.ImagePostProcessor = imagePostProcessor; - this.ColorBuffer = memoryAllocator.Allocate2D( + this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( imagePostProcessor.PostProcessorBufferSize.Width, - imagePostProcessor.PostProcessorBufferSize.Height); + imagePostProcessor.PostProcessorBufferSize.Height, + this.blockAreaSize.Height); this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; - this.blockAreaSize = this.Component.SubSamplingDivisors * 8; } /// @@ -78,6 +79,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1; + int destAreaStride = this.ColorBuffer.Width; + for (int y = 0; y < this.BlockRowsPerStep; y++) { int yBlock = this.currentComponentRowInBlocks + y; @@ -89,26 +92,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; + Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); Span blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); - ref Block8x8 blockRowBase = ref MemoryMarshal.GetReference(blockRow); + // see: https://github.com/SixLabors/ImageSharp/issues/824 + int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width); - for (int xBlock = 0; xBlock < this.SizeInBlocks.Width; xBlock++) + for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) { - ref Block8x8 block = ref Unsafe.Add(ref blockRowBase, xBlock); + ref Block8x8 block = ref blockRow[xBlock]; int xBuffer = xBlock * this.blockAreaSize.Width; + ref float destAreaOrigin = ref colorBufferRow[xBuffer]; - BufferArea destArea = this.ColorBuffer.GetArea( - xBuffer, - yBuffer, - this.blockAreaSize.Width, - this.blockAreaSize.Height); - - blockPp.ProcessBlockColorsInto(ref block, destArea, maximumValue); + blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); } } this.currentComponentRowInBlocks += this.BlockRowsPerStep; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs index 0400978d2..5352a0bff 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image public void PostProcess(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.PixelRowCounter = 0; @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image. public void DoPostProcessorStep(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors) { @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The pixel type /// The destination image private void ConvertColorsInto(ImageFrame destination) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index 54633a5d7..87b486ea6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; -using System.Text; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { @@ -12,24 +11,38 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder internal static class ProfileResolver { /// - /// Describes the JFIF specific markers. + /// Gets the JFIF specific markers. /// - public static readonly byte[] JFifMarker = Encoding.ASCII.GetBytes("JFIF\0"); + public static ReadOnlySpan JFifMarker => new[] + { + (byte)'J', (byte)'F', (byte)'I', (byte)'F', (byte)'\0' + }; /// - /// Describes the ICC specific markers. + /// Gets the ICC specific markers. /// - public static readonly byte[] IccMarker = Encoding.ASCII.GetBytes("ICC_PROFILE\0"); + public static ReadOnlySpan IccMarker => new[] + { + (byte)'I', (byte)'C', (byte)'C', (byte)'_', + (byte)'P', (byte)'R', (byte)'O', (byte)'F', + (byte)'I', (byte)'L', (byte)'E', (byte)'\0' + }; /// - /// Describes the EXIF specific markers. + /// Gets the EXIF specific markers. /// - public static readonly byte[] ExifMarker = Encoding.ASCII.GetBytes("Exif\0\0"); + public static ReadOnlySpan ExifMarker => new[] + { + (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' + }; /// - /// Describes Adobe specific markers . + /// Gets the Adobe specific markers . /// - public static readonly byte[] AdobeMarker = Encoding.ASCII.GetBytes("Adobe"); + public static ReadOnlySpan AdobeMarker => new[] + { + (byte)'A', (byte)'d', (byte)'o', (byte)'b', (byte)'e' + }; /// /// Returns a value indicating whether the passed bytes are a match to the profile identifier. @@ -43,4 +56,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder && bytesToCheck.Slice(0, profileIdentifier.Length).SequenceEqual(profileIdentifier); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 92482de2a..ba604e891 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The pixel type to work on internal ref struct YCbCrForwardConverter - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// The Y component @@ -55,9 +55,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) /// - public void Convert(ImageFrame frame, int x, int y) + public void Convert(ImageFrame frame, int x, int y, in RowOctet currentRows) { - this.pixelBlock.LoadAndStretchEdges(frame, x, y); + this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, currentRows); Span rgbSpan = this.rgbBlock.AsSpanUnsafe(); PixelOperations.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs index 3d1e22a99..534c66b99 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs @@ -54,24 +54,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public void LoadAndStretchEdges(IPixelSource source, int sourceX, int sourceY) - where TPixel : struct, IPixel - { - if (source.PixelBuffer is Buffer2D buffer) - { - this.LoadAndStretchEdges(buffer, sourceX, sourceY); - } - else - { - throw new InvalidOperationException("LoadAndStretchEdges() is only valid for TPixel == T !"); - } - } - /// /// Load a 8x8 region of an image into the block. /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image. /// - public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY) + public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, in RowOctet currentRows) { int width = Math.Min(8, source.Width - sourceX); int height = Math.Min(8, source.Height - sourceY); @@ -85,15 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components int remainderXCount = 8 - width; ref byte blockStart = ref Unsafe.As, byte>(ref this); - ref byte imageStart = ref Unsafe.As( - ref Unsafe.Add(ref MemoryMarshal.GetReference(source.GetRowSpan(sourceY)), sourceX)); - int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); - int imageRowSizeInBytes = source.Width * Unsafe.SizeOf(); for (int y = 0; y < height; y++) { - ref byte s = ref Unsafe.Add(ref imageStart, y * imageRowSizeInBytes); + Span row = currentRows[y]; + + ref byte s = ref Unsafe.As(ref row[sourceX]); ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); Unsafe.CopyBlock(ref d, ref s, byteWidth); @@ -127,4 +112,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// public Span AsSpanUnsafe() => new Span(Unsafe.AsPointer(ref this), Size); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs new file mode 100644 index 000000000..8c3daa4d5 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + /// + /// Cache 8 pixel rows on the stack, which may originate from different buffers of a . + /// + [StructLayout(LayoutKind.Sequential)] + internal readonly ref struct RowOctet + where T : struct + { + private readonly Span row0; + private readonly Span row1; + private readonly Span row2; + private readonly Span row3; + private readonly Span row4; + private readonly Span row5; + private readonly Span row6; + private readonly Span row7; + + public RowOctet(Buffer2D buffer, int startY) + { + int y = startY; + int height = buffer.Height; + this.row0 = y < height ? buffer.GetRowSpan(y++) : default; + this.row1 = y < height ? buffer.GetRowSpan(y++) : default; + this.row2 = y < height ? buffer.GetRowSpan(y++) : default; + this.row3 = y < height ? buffer.GetRowSpan(y++) : default; + this.row4 = y < height ? buffer.GetRowSpan(y++) : default; + this.row5 = y < height ? buffer.GetRowSpan(y++) : default; + this.row6 = y < height ? buffer.GetRowSpan(y++) : default; + this.row7 = y < height ? buffer.GetRowSpan(y) : default; + } + + public Span this[int y] + { + [MethodImpl(InliningOptions.ShortMethod)] + get + { + // No unsafe tricks, since Span can't be used as a generic argument + return y switch + { + 0 => this.row0, + 1 => this.row1, + 2 => this.row2, + 3 => this.row3, + 4 => this.row4, + 5 => this.row5, + 6 => this.row6, + 7 => this.row7, + _ => ThrowIndexOutOfRangeException() + }; + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static Span ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index 059e2052b..669abad28 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -34,12 +34,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public fixed byte Data[Size]; /// - /// 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 - /// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). + /// Gets the unzigs map, which 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 value is 16, which means first column (16%8 == 0) and third row (16/8 == 2). /// - private static readonly byte[] Unzig = - new byte[Size] + private static ReadOnlySpan Unzig => new byte[] { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, @@ -75,8 +74,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components public static ZigZag CreateUnzigTable() { ZigZag result = default; - byte* unzigPtr = result.Data; - Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, Size); + ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig); + ref byte destinationRef = ref Unsafe.AsRef(result.Data); + + Unzig.CopyTo(new Span(result.Data, Size)); + return result; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 4e1c0c1be..b1144508e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -18,14 +19,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - using (var decoder = new JpegDecoderCore(configuration, this)) + using var decoder = new JpegDecoderCore(configuration, this); + try { return decoder.Decode(stream); } + catch (InvalidMemoryOperationException ex) + { + (int w, int h) = (decoder.ImageWidth, decoder.ImageHeight); + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9b6a72cc9..951fec1d4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The stream, where the image should be. /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.ParseStream(stream); this.InitExifProfile(); @@ -958,7 +958,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The . private Image PostProcessIntoImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (this.ImageWidth == 0 || this.ImageHeight == 0) { diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index d649d3041..1c4035a98 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new JpegEncoderCore(this); encoder.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index cd3c19aa3..32f4d2287 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The image to write from. /// The stream to write to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -393,7 +394,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode444(Image pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // (Partially done with YCbCrForwardConverter) @@ -409,12 +410,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; var pixelConverter = YCbCrForwardConverter.Create(); + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 8) { + var currentRows = new RowOctet(pixelBuffer, y); + for (int x = 0; x < pixels.Width; x += 8) { - pixelConverter.Convert(pixels.Frames.RootFrame, x, y); + pixelConverter.Convert(frame, x, y, currentRows); prevDCY = this.WriteBlock( QuantIndex.Luminance, @@ -886,7 +891,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The pixel accessor providing access to the image pixels. private void WriteStartOfScan(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) // TODO: We should allow grayscale writing. @@ -913,7 +918,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The pixel format. /// The pixel accessor providing access to the image pixels. private void Encode420(Image pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Need a JpegScanEncoder class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) Block8x8F b = default; @@ -935,6 +940,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // ReSharper disable once InconsistentNaming int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; + ImageFrame frame = pixels.Frames.RootFrame; + Buffer2D pixelBuffer = frame.PixelBuffer; for (int y = 0; y < pixels.Height; y += 16) { @@ -945,7 +952,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int xOff = (i & 1) * 8; int yOff = (i & 2) * 4; - pixelConverter.Convert(pixels.Frames.RootFrame, x + xOff, y + yOff); + // TODO: Try pushing this to the outer loop! + var currentRows = new RowOctet(pixelBuffer, y + yOff); + + pixelConverter.Convert(frame, x + xOff, y + yOff, currentRows); cbPtr[i] = pixelConverter.Cb; crPtr[i] = pixelConverter.Cr; diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index 66d04f39f..1683519c2 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats public int BitsPerPixel { get; } internal static PixelTypeInfo Create() - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => new PixelTypeInfo(Unsafe.SizeOf() * 8); } } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index 632460ec4..247bb3c75 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text; @@ -36,21 +37,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// public static readonly IEnumerable FileExtensions = new[] { "png" }; - /// - /// The header bytes identifying a Png. - /// - public static readonly byte[] HeaderBytes = - { - 0x89, // Set the high bit. - 0x50, // P - 0x4E, // N - 0x47, // G - 0x0D, // Line ending CRLF - 0x0A, // Line ending CRLF - 0x1A, // EOF - 0x0A // LF - }; - /// /// The header bytes as a big-endian coded ulong. /// @@ -77,5 +63,20 @@ namespace SixLabors.ImageSharp.Formats.Png /// The minimum length of a keyword in a text chunk is 1 byte. /// public const int MinTextKeywordLength = 1; + + /// + /// Gets the header bytes identifying a Png. + /// + public static ReadOnlySpan HeaderBytes => new byte[] + { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index eea9e54c0..d605577e7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -41,10 +42,22 @@ namespace SixLabors.ImageSharp.Formats.Png /// The containing image data. /// The decoded image. public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new PngDecoderCore(configuration, this); - return decoder.Decode(stream); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 69b341c8d..4d7de4161 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Png private int currentRow = Adam7.FirstRow[0]; /// - /// The current number of bytes read in the current scanline + /// The current number of bytes read in the current scanline. /// private int currentRowBytesRead; @@ -132,20 +132,25 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.header.Width, this.header.Height); + /// /// Decodes the stream to the image. /// /// The pixel format. - /// The stream containing image data. + /// The stream containing image data. /// /// Thrown if the stream does not contain and end chunk. /// /// /// Thrown if the image is larger than the maximum allowable size. /// - /// The decoded image + /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var metadata = new ImageMetadata(); PngMetadata pngMetadata = metadata.GetPngMetadata(); @@ -373,7 +378,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The metadata information for the image /// The image that we will populate private void InitializeImage(ImageMetadata metadata, out Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); this.bytesPerPixel = this.CalculateBytesPerPixel(); @@ -466,7 +471,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel data. /// The png metadata private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) { @@ -492,7 +497,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image to decode to. /// The png metadata private void DecodePixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { while (this.currentRow < this.header.Height) { @@ -548,7 +553,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The current image. /// The png metadata. private void DecodeInterlacedPixelData(Stream compressedStream, ImageFrame image, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int pass = 0; int width = this.header.Width; @@ -637,7 +642,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image /// The png metadata. private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); @@ -721,7 +726,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. private void ProcessInterlacedDefilteredScanline(ReadOnlySpan defilteredScanline, Span rowSpan, PngMetadata pngMetadata, int pixelOffset = 0, int increment = 1) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); @@ -1096,10 +1101,7 @@ namespace SixLabors.ImageSharp.Formats.Png while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position)) { - // Not a valid chunk so we skip back all but one of the four bytes we have just read. - // That lets us read one byte at a time until we reach a known chunk. - this.currentStream.Position -= 3; - + // Not a valid chunk so try again until we reach a known chunk. if (!this.TryReadChunkLength(out length)) { chunk = default; diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs index 3e46ad29e..e654036a8 100644 --- a/src/ImageSharp/Formats/Png/PngEncoder.cs +++ b/src/ImageSharp/Formats/Png/PngEncoder.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var encoder = new PngEncoderCore(image.GetMemoryAllocator(), image.GetConfiguration(), new PngEncoderOptions(this))) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 69a80e024..45e1ffd2d 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -146,10 +146,10 @@ namespace SixLabors.ImageSharp.Formats.Png ImageMetadata metadata = image.Metadata; PngMetadata pngMetadata = metadata.GetPngMetadata(); PngEncoderOptionsHelpers.AdjustOptions(this.options, pngMetadata, out this.use16Bit, out this.bytesPerPixel); - IQuantizedFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); + IndexedImageFrame quantized = PngEncoderOptionsHelpers.CreateQuantizedFrame(this.options, image); this.bitDepth = PngEncoderOptionsHelpers.CalculateBitDepth(this.options, image, quantized); - stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length); + stream.Write(PngConstants.HeaderBytes); this.WriteHeaderChunk(stream); this.WritePaletteChunk(stream, quantized); @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The image row span. private void CollectGrayscaleBytes(ReadOnlySpan rowSpan) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); Span rawScanlineSpan = this.currentScanline.GetSpan(); @@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The row span. private void CollectTPixelBytes(ReadOnlySpan rowSpan) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span rawScanlineSpan = this.currentScanline.GetSpan(); @@ -371,8 +371,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The row span. /// The quantized pixels. Can be null. /// The row. - private void CollectPixelBytes(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row) - where TPixel : struct, IPixel + private void CollectPixelBytes(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel { switch (this.options.ColorType) { @@ -380,12 +380,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); } else { - int stride = this.currentScanline.Length(); - quantized.GetPixelSpan().Slice(row * stride, stride).CopyTo(this.currentScanline.GetSpan()); + quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; @@ -440,8 +439,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The quantized pixels. Can be null. /// The row. /// The - private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IQuantizedFrame quantized, int row) - where TPixel : struct, IPixel + private IManagedByteBuffer EncodePixelRow(ReadOnlySpan rowSpan, IndexedImageFrame quantized, int row) + where TPixel : unmanaged, IPixel { this.CollectPixelBytes(rowSpan, quantized, row); return this.FilterPixelBytes(); @@ -546,59 +545,54 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixel format. /// The containing image data. /// The quantized frame. - private void WritePaletteChunk(Stream stream, IQuantizedFrame quantized) - where TPixel : struct, IPixel + private void WritePaletteChunk(Stream stream, IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { - if (quantized == null) + if (quantized is null) { return; } // Grab the palette and write it to the stream. ReadOnlySpan palette = quantized.Palette.Span; - int paletteLength = Math.Min(palette.Length, 256); - int colorTableLength = paletteLength * 3; - bool anyAlpha = false; + int paletteLength = palette.Length; + int colorTableLength = paletteLength * Unsafe.SizeOf(); + bool hasAlpha = false; - using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength)) - using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength)) - { - ref byte colorTableRef = ref MemoryMarshal.GetReference(colorTable.GetSpan()); - ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - ReadOnlySpan quantizedSpan = quantized.GetPixelSpan(); - - Rgba32 rgba = default; - - for (int i = 0; i < paletteLength; i++) - { - if (quantizedSpan.IndexOf((byte)i) > -1) - { - int offset = i * 3; - palette[i].ToRgba32(ref rgba); + using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength); + using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength); - byte alpha = rgba.A; + ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(colorTable.GetSpan())); + ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan()); - Unsafe.Add(ref colorTableRef, offset) = rgba.R; - Unsafe.Add(ref colorTableRef, offset + 1) = rgba.G; - Unsafe.Add(ref colorTableRef, offset + 2) = rgba.B; + // Bulk convert our palette to RGBA to allow assignment to tables. + using IMemoryOwner rgbaOwner = quantized.Configuration.MemoryAllocator.Allocate(paletteLength); + Span rgbaPaletteSpan = rgbaOwner.GetSpan(); + PixelOperations.Instance.ToRgba32(quantized.Configuration, quantized.Palette.Span, rgbaPaletteSpan); + ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPaletteSpan); - if (alpha > this.options.Threshold) - { - alpha = byte.MaxValue; - } + // Loop, assign, and extract alpha values from the palette. + for (int i = 0; i < paletteLength; i++) + { + Rgba32 rgba = Unsafe.Add(ref rgbaPaletteRef, i); + byte alpha = rgba.A; - anyAlpha = anyAlpha || alpha < byte.MaxValue; - Unsafe.Add(ref alphaTableRef, i) = alpha; - } + Unsafe.Add(ref colorTableRef, i) = rgba.Rgb; + if (alpha > this.options.Threshold) + { + alpha = byte.MaxValue; } - this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + hasAlpha = hasAlpha || alpha < byte.MaxValue; + Unsafe.Add(ref alphaTableRef, i) = alpha; + } - // Write the transparency data - if (anyAlpha) - { - this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); - } + this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength); + + // Write the transparency data + if (hasAlpha) + { + this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength); } } @@ -783,8 +777,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image. /// The quantized pixel data. Can be null. /// The stream. - private void WriteDataChunks(ImageFrame pixels, IQuantizedFrame quantized, Stream stream) - where TPixel : struct, IPixel + private void WriteDataChunks(ImageFrame pixels, IndexedImageFrame quantized, Stream stream) + where TPixel : unmanaged, IPixel { byte[] buffer; int bufferLength; @@ -881,8 +875,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixels. /// The quantized pixels span. /// The deflate stream. - private void EncodePixels(ImageFrame pixels, IQuantizedFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : struct, IPixel + private void EncodePixels(ImageFrame pixels, IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel { int bytesPerScanline = this.CalculateScanlineLength(this.width); int resultLength = bytesPerScanline + 1; @@ -906,7 +900,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The pixels. /// The deflate stream. private void EncodeAdam7Pixels(ImageFrame pixels, ZlibDeflateStream deflateStream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int width = pixels.Width; int height = pixels.Height; @@ -960,8 +954,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The quantized. /// The deflate stream. - private void EncodeAdam7IndexedPixels(IQuantizedFrame quantized, ZlibDeflateStream deflateStream) - where TPixel : struct, IPixel + private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream) + where TPixel : unmanaged, IPixel { int width = quantized.Width; int height = quantized.Height; @@ -987,7 +981,7 @@ namespace SixLabors.ImageSharp.Formats.Png row += Adam7.RowIncrement[pass]) { // collect data - ReadOnlySpan srcRow = quantized.GetRowSpan(row); + ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) diff --git a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs index b494c164f..3f490ca6f 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Png PngMetadata pngMetadata, out bool use16Bit, out int bytesPerPixel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Always take the encoder options over the metadata values. options.Gamma ??= pngMetadata.Gamma; @@ -53,10 +53,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The options. /// The image. - public static IQuantizedFrame CreateQuantizedFrame( + public static IndexedImageFrame CreateQuantizedFrame( PngEncoderOptions options, Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (options.ColorType != PngColorType.Palette) { @@ -72,13 +72,15 @@ namespace SixLabors.ImageSharp.Formats.Png // Use the metadata to determine what quantization depth to use if no quantizer has been set. if (options.Quantizer is null) { - options.Quantizer = new WuQuantizer(ImageMaths.GetColorCountForBitDepth(bits)); + var maxColors = ImageMaths.GetColorCountForBitDepth(bits); + options.Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = maxColors }); } // Create quantized frame returning the palette and set the bit depth. using (IFrameQuantizer frameQuantizer = options.Quantizer.CreateFrameQuantizer(image.GetConfiguration())) { - return frameQuantizer.QuantizeFrame(image.Frames.RootFrame); + ImageFrame frame = image.Frames.RootFrame; + return frameQuantizer.QuantizeFrame(frame, frame.Bounds()); } } @@ -92,8 +94,8 @@ namespace SixLabors.ImageSharp.Formats.Png public static byte CalculateBitDepth( PngEncoderOptions options, Image image, - IQuantizedFrame quantizedFrame) - where TPixel : struct, IPixel + IndexedImageFrame quantizedFrame) + where TPixel : unmanaged, IPixel { byte bitDepth; if (options.ColorType == PngColorType.Palette) @@ -151,7 +153,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// This is not exhaustive but covers many common pixel formats. /// private static PngColorType SuggestColorType() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return typeof(TPixel) switch { @@ -177,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// This is not exhaustive but covers many common pixel formats. /// private static PngBitDepth SuggestBitDepth() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return typeof(TPixel) switch { diff --git a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs index 5f9d1de9c..cf365c8b9 100644 --- a/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs +++ b/src/ImageSharp/Formats/Png/PngScanlineProcessor.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, L16 luminance16Trans, L8 luminanceTrans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, L16 luminance16Trans, L8 luminanceTrans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -198,7 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, ReadOnlySpan palette, byte[] paletteAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -284,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, ReadOnlySpan palette, byte[] paletteAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -331,7 +331,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, Rgb48 rgb48Trans, Rgb24 rgb24Trans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -404,7 +404,7 @@ namespace SixLabors.ImageSharp.Formats.Png bool hasTrans, Rgb48 rgb48Trans, Rgb24 rgb24Trans) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); @@ -482,7 +482,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span rowSpan, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); @@ -515,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Png int increment, int bytesPerPixel, int bytesPerSample) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = default; ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs index e8dd8a520..543a1fe30 100644 --- a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs +++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs @@ -36,12 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib private const int EofSymbol = 256; - // The lengths of the bit length codes are sent in order of decreasing - // probability, to avoid transmitting the lengths for unused bit length codes. - private static readonly int[] BitLengthOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; - - private static readonly byte[] Bit4Reverse = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; - private static readonly short[] StaticLCodes; private static readonly byte[] StaticLLength; private static readonly short[] StaticDCodes; @@ -128,6 +122,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer; } + /// + /// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes. + /// + private static ReadOnlySpan BitLengthOrder => new byte[] { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + + private static ReadOnlySpan Bit4Reverse => new byte[] { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 }; + /// /// Gets the pending buffer to use. /// @@ -158,6 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5); this.Pending.WriteBits(this.distTree.NumCodes - 1, 5); this.Pending.WriteBits(blTreeCodes - 4, 4); + for (int rank = 0; rank < blTreeCodes; rank++) { this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3); @@ -250,6 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib this.blTree.BuildTree(); int blTreeCodes = 4; + for (int i = 18; i > blTreeCodes; i--) { if (this.blTree.Length[BitLengthOrder[i]] > 0) @@ -363,10 +366,30 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib [MethodImpl(InliningOptions.ShortMethod)] public static short BitReverse(int toReverse) { - return (short)(Bit4Reverse[toReverse & 0xF] << 12 - | Bit4Reverse[(toReverse >> 4) & 0xF] << 8 - | Bit4Reverse[(toReverse >> 8) & 0xF] << 4 - | Bit4Reverse[toReverse >> 12]); + /* Use unsafe offsetting and manually validate the input index to reduce the + * total number of conditional branches. There are two main cases to test here: + * 1. In the first 3, the input value (or some combination of it) is combined + * with & 0xF, which results in a maximum value of 0xF no matter what the + * input value was. That is 15, which is always in range for the target span. + * As a result, no input validation is needed at all in this case. + * 2. There are two cases where the input value might cause an invalid access: + * when it is either negative, or greater than 15 << 12. We can test both + * conditions in a single pass by casting the input value to uint and right + * shifting it by 12, which also preserves the sign. If it is a negative + * value (2-complement), the test will fail as the uint cast will result + * in a much larger value. If the value was simply too high, the test will + * fail as expected. We can't simply check whether the value is lower than + * 15 << 12, because higher values are acceptable in the first 3 accesses. + * Doing this reduces the total number of index checks from 4 down to just 1. */ + int toReverseRightShiftBy12 = toReverse >> 12; + Guard.MustBeLessThanOrEqualTo((uint)toReverseRightShiftBy12, 15, nameof(toReverse)); + + ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse); + + return (short)(Unsafe.Add(ref bit4ReverseRef, toReverse & 0xF) << 12 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 4) & 0xF) << 8 + | Unsafe.Add(ref bit4ReverseRef, (toReverse >> 8) & 0xF) << 4 + | Unsafe.Add(ref bit4ReverseRef, toReverseRightShiftBy12)); } /// diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs index b97388773..2249c86bf 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tga @@ -13,11 +15,24 @@ namespace SixLabors.ImageSharp.Formats.Tga { /// public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(stream, nameof(stream)); - return new TgaDecoderCore(configuration, this).Decode(stream); + var decoder = new TgaDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 91cc93e19..ead004003 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -61,6 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Tga this.options = options; } + /// + /// Gets the dimensions of the image. + /// + public Size Dimensions => new Size(this.fileHeader.Width, this.fileHeader.Height); + /// /// Decodes the image from the specified stream. /// @@ -71,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// /// The decoded image. public Image Decode(Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { try { @@ -217,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Color map size of one entry in bytes. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean)) { @@ -280,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// Color map size of one entry in bytes. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int bytesPerPixel = 1; using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) @@ -331,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to assign the palette to. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadMonoChrome(int width, int height, Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0)) { @@ -358,7 +363,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to assign the palette to. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadBgra16(int width, int height, Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) { @@ -393,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to assign the palette to. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadBgr24(int width, int height, Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0)) { @@ -420,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to assign the palette to. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadBgra32(int width, int height, Buffer2D pixels, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0)) { @@ -448,7 +453,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The bytes per pixel. /// Indicates, if the origin of the image is top left rather the bottom left (the default). private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, bool inverted) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel color = default; using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean)) diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs index 2fcbb822f..e938067a1 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoder.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator()); encoder.Encode(image, stream); diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index a4b141f38..d5d7ce49e 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to encode from. /// The to encode the image data to. public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Formats.Tga if (this.compression is TgaCompression.RunLength) { - this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame); + this.WriteRunLengthEncodedImage(stream, image.Frames.RootFrame); } else { @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The containing pixel data. /// private void WriteImage(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Buffer2D pixels = image.PixelBuffer; switch (this.bitsPerPixel) @@ -150,19 +150,20 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The pixel type. /// The stream to write the image to. /// The image to encode. - private void WriteRunLengthEndcodedImage(Stream stream, ImageFrame image) - where TPixel : struct, IPixel + private void WriteRunLengthEncodedImage(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel { Rgba32 color = default; Buffer2D pixels = image.PixelBuffer; - Span pixelSpan = pixels.GetSpan(); int totalPixels = image.Width * image.Height; int encodedPixels = 0; while (encodedPixels < totalPixels) { - TPixel currentPixel = pixelSpan[encodedPixels]; + int x = encodedPixels % pixels.Width; + int y = encodedPixels / pixels.Width; + TPixel currentPixel = pixels[x, y]; currentPixel.ToRgba32(ref color); - byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels)); + byte equalPixelCount = this.FindEqualPixels(pixels, x, y); // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run. stream.WriteByte((byte)(equalPixelCount | 128)); @@ -200,30 +201,40 @@ namespace SixLabors.ImageSharp.Formats.Tga } /// - /// Finds consecutive pixels, which have the same value starting from the pixel span offset 0. + /// Finds consecutive pixels which have the same value. /// /// The pixel type. - /// The pixel span to search in. + /// The pixels of the image. + /// X coordinate to start searching for the same pixels. + /// Y coordinate to start searching for the same pixels. /// The number of equal pixels. - private byte FindEqualPixels(Span pixelSpan) - where TPixel : struct, IPixel + private byte FindEqualPixels(Buffer2D pixels, int xStart, int yStart) + where TPixel : unmanaged, IPixel { - int idx = 0; byte equalPixelCount = 0; - while (equalPixelCount < 127 && idx < pixelSpan.Length - 1) + bool firstRow = true; + TPixel startPixel = pixels[xStart, yStart]; + for (int y = yStart; y < pixels.Height; y++) { - TPixel currentPixel = pixelSpan[idx]; - TPixel nextPixel = pixelSpan[idx + 1]; - if (currentPixel.Equals(nextPixel)) + for (int x = firstRow ? xStart + 1 : 0; x < pixels.Width; x++) { - equalPixelCount++; - } - else - { - return equalPixelCount; + TPixel nextPixel = pixels[x, y]; + if (startPixel.Equals(nextPixel)) + { + equalPixelCount++; + } + else + { + return equalPixelCount; + } + + if (equalPixelCount >= 127) + { + return equalPixelCount; + } } - idx++; + firstRow = false; } return equalPixelCount; @@ -238,7 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to write to. /// The containing pixel data. private void Write8Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1)) { @@ -262,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to write to. /// The containing pixel data. private void Write16Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2)) { @@ -286,7 +297,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to write to. /// The containing pixel data. private void Write24Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3)) { @@ -310,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The to write to. /// The containing pixel data. private void Write32Bit(Stream stream, Buffer2D pixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4)) { @@ -333,7 +344,7 @@ namespace SixLabors.ImageSharp.Formats.Tga /// The pixel to get the luminance from. [MethodImpl(InliningOptions.ShortMethod)] public static int GetLuminance(TPixel sourcePixel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var vector = sourcePixel.ToVector4(); return ImageMaths.GetBT709Luminance(ref vector, 256); diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index e1376b4a2..c28a21452 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -32,11 +32,11 @@ namespace SixLabors.ImageSharp int width, int height, ImageMetadata metadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Buffer2D uninitializedMemoryBuffer = configuration.MemoryAllocator.Allocate2D(width, height); - return new Image(configuration, uninitializedMemoryBuffer.MemorySource, width, height, metadata); + return new Image(configuration, uninitializedMemoryBuffer.FastMemoryGroup, width, height, metadata); } /// @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp /// private static (Image img, IImageFormat format) Decode(Stream stream, Configuration config) #pragma warning restore SA1008 // Opening parenthesis must be spaced correctly - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IImageDecoder decoder = DiscoverDecoder(stream, config, out IImageFormat format); if (decoder is null) diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs index 389fbba6d..06b05fe3c 100644 --- a/src/ImageSharp/Image.FromBytes.cs +++ b/src/ImageSharp/Image.FromBytes.cs @@ -26,14 +26,55 @@ namespace SixLabors.ImageSharp /// /// By reading the header on the provided byte array this calculates the images format. /// - /// The configuration. + /// The configuration. /// The byte array containing encoded image data to read the header from. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, byte[] data) + public static IImageFormat DetectFormat(Configuration configuration, byte[] data) { - using (var stream = new MemoryStream(data)) + Guard.NotNull(configuration, nameof(configuration)); + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) + { + return DetectFormat(configuration, stream); + } + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte array containing encoded image data to read the header from. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(byte[] data) => Identify(data, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(byte[] data, out IImageFormat format) => Identify(Configuration.Default, data, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The byte array containing encoded image data to read the header from. + /// The format type of the decoded image. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, byte[] data, out IImageFormat format) + { + Guard.NotNull(configuration, nameof(configuration)); + using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return DetectFormat(config, stream); + return Identify(configuration, stream, out format); } } @@ -51,7 +92,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(byte[] data) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data); /// @@ -62,39 +103,39 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(byte[] data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, out format); /// /// Load a new instance of from the given encoded byte array. /// - /// The configuration options. + /// The configuration options. /// The byte array containing encoded image data. /// The pixel format. /// A new . - public static Image Load(Configuration config, byte[] data) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data) + where TPixel : unmanaged, IPixel { using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream); + return Load(configuration, stream); } } /// /// Load a new instance of from the given encoded byte array. /// - /// The configuration options. + /// The configuration options. /// The byte array containing encoded image data. /// The of the decoded image. /// The pixel format. /// A new . - public static Image Load(Configuration config, byte[] data, out IImageFormat format) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) + where TPixel : unmanaged, IPixel { using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -106,7 +147,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(byte[] data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { @@ -117,17 +158,17 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte array. /// - /// The Configuration. + /// The Configuration. /// The byte array containing encoded image data. /// The decoder. /// The pixel format. /// A new . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) + where TPixel : unmanaged, IPixel { using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } @@ -144,18 +185,18 @@ namespace SixLabors.ImageSharp /// /// By reading the header on the provided byte array this calculates the images format. /// - /// The configuration. + /// The configuration. /// The byte array containing encoded image data to read the header from. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, ReadOnlySpan data) + public static IImageFormat DetectFormat(Configuration configuration, ReadOnlySpan data) { - int maxHeaderSize = config.MaxHeaderSize; + int maxHeaderSize = configuration.MaxHeaderSize; if (maxHeaderSize <= 0) { return null; } - foreach (IImageFormatDetector detector in config.ImageFormatsManager.FormatDetectors) + foreach (IImageFormatDetector detector in configuration.ImageFormatsManager.FormatDetectors) { IImageFormat f = detector.DetectFormat(data); @@ -175,7 +216,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(ReadOnlySpan data) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data); /// @@ -186,7 +227,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(ReadOnlySpan data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, out format); /// @@ -197,24 +238,24 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(ReadOnlySpan data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Load(Configuration.Default, data, decoder); /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing encoded image data. /// The pixel format. /// A new . - public static unsafe Image Load(Configuration config, ReadOnlySpan data) - where TPixel : struct, IPixel + public static unsafe Image Load(Configuration configuration, ReadOnlySpan data) + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream); + return Load(configuration, stream); } } } @@ -222,22 +263,22 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The Configuration. + /// The Configuration. /// The byte span containing image data. /// The decoder. /// The pixel format. /// A new . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } @@ -245,22 +286,22 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. /// The of the decoded image. /// The pixel format. /// A new . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { fixed (byte* ptr = &data.GetPinnableReference()) { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } } @@ -285,38 +326,38 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing encoded image data. /// The . - public static Image Load(Configuration config, byte[] data) => Load(config, data, out _); + public static Image Load(Configuration configuration, byte[] data) => Load(configuration, data, out _); /// /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing image data. /// The decoder. /// The . - public static Image Load(Configuration config, byte[] data, IImageDecoder decoder) + public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) { using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } /// /// Load a new instance of from the given encoded byte array. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The byte array containing image data. /// The mime type of the decoded image. /// The . - public static Image Load(Configuration config, byte[] data, out IImageFormat format) + public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) { using (var stream = new MemoryStream(data, 0, data.Length, false, true)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -348,20 +389,20 @@ namespace SixLabors.ImageSharp /// /// Decodes a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. /// The . - public static Image Load(Configuration config, ReadOnlySpan data) => Load(config, data, out _); + public static Image Load(Configuration configuration, ReadOnlySpan data) => Load(configuration, data, out _); /// /// Load a new instance of from the given encoded byte span. /// - /// The Configuration. + /// The Configuration. /// The byte span containing image data. /// The decoder. /// The . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, IImageDecoder decoder) { @@ -369,7 +410,7 @@ namespace SixLabors.ImageSharp { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } @@ -377,12 +418,12 @@ namespace SixLabors.ImageSharp /// /// Load a new instance of from the given encoded byte span. /// - /// The configuration options. + /// The configuration options. /// The byte span containing image data. /// The of the decoded image.> /// The . public static unsafe Image Load( - Configuration config, + Configuration configuration, ReadOnlySpan data, out IImageFormat format) { @@ -390,7 +431,7 @@ namespace SixLabors.ImageSharp { using (var stream = new UnmanagedMemoryStream(ptr, data.Length)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } } diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 95b4b9790..1a9fa1546 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -26,15 +26,55 @@ namespace SixLabors.ImageSharp /// /// By reading the header on the provided file this calculates the images mime type. /// - /// The configuration. + /// The configuration. /// The image file to open and to read the header from. /// The mime type or null if none found. - public static IImageFormat DetectFormat(Configuration config, string filePath) + public static IImageFormat DetectFormat(Configuration configuration, string filePath) { - config = config ?? Configuration.Default; - using (Stream file = config.FileSystem.OpenRead(filePath)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream file = configuration.FileSystem.OpenRead(filePath)) { - return DetectFormat(config, file); + return DetectFormat(configuration, file); + } + } + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath) => Identify(filePath, out IImageFormat _); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The image file to open and to read the header from. + /// The format type of the decoded image. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(string filePath, out IImageFormat format) => Identify(Configuration.Default, filePath, out format); + + /// + /// Reads the raw image information from the specified stream without fully decoding it. + /// + /// The configuration. + /// The image file to open and to read the header from. + /// The format type of the decoded image. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector is not found. + /// + public static IImageInfo Identify(Configuration configuration, string filePath, out IImageFormat format) + { + Guard.NotNull(configuration, nameof(configuration)); + using (Stream file = configuration.FileSystem.OpenRead(filePath)) + { + return Identify(configuration, file, out format); } } @@ -62,29 +102,30 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given file. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The file path to the image. /// /// Thrown if the stream is not readable nor seekable. /// /// The . - public static Image Load(Configuration config, string path) => Load(config, path, out _); + public static Image Load(Configuration configuration, string path) => Load(configuration, path, out _); /// /// Create a new instance of the class from the given file. /// - /// The Configuration. + /// The Configuration. /// The file path to the image. /// The decoder. /// /// Thrown if the stream is not readable nor seekable. /// /// The . - public static Image Load(Configuration config, string path, IImageDecoder decoder) + public static Image Load(Configuration configuration, string path, IImageDecoder decoder) { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } @@ -109,7 +150,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(string path) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return Load(Configuration.Default, path); } @@ -125,7 +166,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(string path, out IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return Load(Configuration.Default, path, out format); } @@ -133,26 +174,27 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. /// /// Thrown if the stream is not readable nor seekable. /// /// The pixel format. /// A new . - public static Image Load(Configuration config, string path) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream); + return Load(configuration, stream); } } /// /// Create a new instance of the class from the given file. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. /// The mime type of the decoded image. /// @@ -160,12 +202,13 @@ namespace SixLabors.ImageSharp /// /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path, out IImageFormat format) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -173,18 +216,19 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given file. /// The pixel type is selected by the decoder. /// - /// The configuration options. + /// The configuration options. /// The file path to the image. /// The mime type of the decoded image. /// /// Thrown if the stream is not readable nor seekable. /// /// A new . - public static Image Load(Configuration config, string path, out IImageFormat format) + public static Image Load(Configuration configuration, string path, out IImageFormat format) { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, out format); + return Load(configuration, stream, out format); } } @@ -199,7 +243,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image Load(string path, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return Load(Configuration.Default, path, decoder); } @@ -207,7 +251,7 @@ namespace SixLabors.ImageSharp /// /// Create a new instance of the class from the given file. /// - /// The Configuration. + /// The Configuration. /// The file path to the image. /// The decoder. /// @@ -215,12 +259,13 @@ namespace SixLabors.ImageSharp /// /// The pixel format. /// A new . - public static Image Load(Configuration config, string path, IImageDecoder decoder) - where TPixel : struct, IPixel + public static Image Load(Configuration configuration, string path, IImageDecoder decoder) + where TPixel : unmanaged, IPixel { - using (Stream stream = config.FileSystem.OpenRead(path)) + Guard.NotNull(configuration, nameof(configuration)); + using (Stream stream = configuration.FileSystem.OpenRead(path)) { - return Load(config, stream, decoder); + return Load(configuration, stream, decoder); } } } diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index db2cc2fd1..52d71409b 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -26,15 +26,15 @@ namespace SixLabors.ImageSharp /// /// By reading the header on the provided stream this calculates the images format type. /// - /// The configuration. + /// The configuration. /// The image stream to read the header from. /// Thrown if the stream is not readable. /// The format type or null if none found. - public static IImageFormat DetectFormat(Configuration config, Stream stream) - => WithSeekableStream(config, stream, s => InternalDetectFormat(s, config)); + public static IImageFormat DetectFormat(Configuration configuration, Stream stream) + => WithSeekableStream(configuration, stream, s => InternalDetectFormat(s, configuration)); /// - /// By reading the header on the provided stream this reads the raw image information. + /// Reads the raw image information from the specified stream without fully decoding it. /// /// The image stream to read the header from. /// Thrown if the stream is not readable. @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _); /// - /// By reading the header on the provided stream this reads the raw image information. + /// Reads the raw image information from the specified stream without fully decoding it. /// /// The image stream to read the header from. /// The format type of the decoded image. @@ -57,16 +57,16 @@ namespace SixLabors.ImageSharp /// /// Reads the raw image information from the specified stream without fully decoding it. /// - /// The configuration. + /// The configuration. /// The image stream to read the information from. /// The format type of the decoded image. /// Thrown if the stream is not readable. /// /// The or null if suitable info detector is not found. /// - public static IImageInfo Identify(Configuration config, Stream stream, out IImageFormat format) + public static IImageInfo Identify(Configuration configuration, Stream stream, out IImageFormat format) { - (IImageInfo info, IImageFormat format) data = WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); + (IImageInfo info, IImageFormat format) data = WithSeekableStream(configuration, stream, s => InternalIdentity(s, configuration ?? Configuration.Default)); format = data.format; return data.info; @@ -108,24 +108,24 @@ namespace SixLabors.ImageSharp /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The stream containing image information. /// The decoder. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// A new .> - public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) => - WithSeekableStream(config, stream, s => decoder.Decode(config, s)); + public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) => + WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); /// /// Decode a new instance of the class from the given stream. /// - /// The config for the decoder. + /// The configuration for the decoder. /// The stream containing image information. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// A new .> - public static Image Load(Configuration config, Stream stream) => Load(config, stream, out _); + public static Image Load(Configuration configuration, Stream stream) => Load(configuration, stream, out _); /// /// Create a new instance of the class from the given stream. @@ -136,8 +136,8 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new .> public static Image Load(Stream stream) - where TPixel : struct, IPixel - => Load(null, stream); + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, stream); /// /// Create a new instance of the class from the given stream. @@ -149,8 +149,8 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new .> public static Image Load(Stream stream, out IImageFormat format) - where TPixel : struct, IPixel - => Load(null, stream, out format); + where TPixel : unmanaged, IPixel + => Load(Configuration.Default, stream, out format); /// /// Create a new instance of the class from the given stream. @@ -162,51 +162,51 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new .> public static Image Load(Stream stream, IImageDecoder decoder) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => WithSeekableStream(Configuration.Default, stream, s => decoder.Decode(Configuration.Default, s)); /// /// Create a new instance of the class from the given stream. /// - /// The Configuration. + /// The Configuration. /// The stream containing image information. /// The decoder. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream, IImageDecoder decoder) - where TPixel : struct, IPixel - => WithSeekableStream(config, stream, s => decoder.Decode(config, s)); + public static Image Load(Configuration configuration, Stream stream, IImageDecoder decoder) + where TPixel : unmanaged, IPixel + => WithSeekableStream(configuration, stream, s => decoder.Decode(configuration, s)); /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// The pixel format. /// A new .> - public static Image Load(Configuration config, Stream stream) - where TPixel : struct, IPixel - => Load(config, stream, out IImageFormat _); + public static Image Load(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + => Load(configuration, stream, out IImageFormat _); /// /// Create a new instance of the class from the given stream. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. /// The format type of the decoded image. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// The pixel format. - /// A new .> - public static Image Load(Configuration config, Stream stream, out IImageFormat format) - where TPixel : struct, IPixel + /// A new . + public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) + where TPixel : unmanaged, IPixel { - config = config ?? Configuration.Default; - (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + Guard.NotNull(configuration, nameof(configuration)); + (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); format = data.format; @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) { sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } @@ -230,16 +230,16 @@ namespace SixLabors.ImageSharp /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// - /// The configuration options. + /// The configuration options. /// The stream containing image information. /// The format type of the decoded image. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// A new . - public static Image Load(Configuration config, Stream stream, out IImageFormat format) + public static Image Load(Configuration configuration, Stream stream, out IImageFormat format) { - config = config ?? Configuration.Default; - (Image img, IImageFormat format) data = WithSeekableStream(config, stream, s => Decode(s, config)); + Guard.NotNull(configuration, nameof(configuration)); + (Image img, IImageFormat format) data = WithSeekableStream(configuration, stream, s => Decode(s, configuration)); format = data.format; @@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp var sb = new StringBuilder(); sb.AppendLine("Image cannot be loaded. Available decoders:"); - foreach (KeyValuePair val in config.ImageFormatsManager.ImageDecoders) + foreach (KeyValuePair val in configuration.ImageFormatsManager.ImageDecoders) { sb.AppendLine($" - {val.Key.Name} : {val.Value.GetType().Name}"); } @@ -259,7 +259,7 @@ namespace SixLabors.ImageSharp throw new UnknownImageFormatException(sb.ToString()); } - private static T WithSeekableStream(Configuration config, Stream stream, Func action) + private static T WithSeekableStream(Configuration configuration, Stream stream, Func action) { if (!stream.CanRead) { @@ -268,7 +268,7 @@ namespace SixLabors.ImageSharp if (stream.CanSeek) { - if (config.ReadOrigin == ReadOrigin.Begin) + if (configuration.ReadOrigin == ReadOrigin.Begin) { stream.Position = 0; } diff --git a/src/ImageSharp/Image.LoadPixelData.cs b/src/ImageSharp/Image.LoadPixelData.cs index eb7ce261f..f8f2e8485 100644 --- a/src/ImageSharp/Image.LoadPixelData.cs +++ b/src/ImageSharp/Image.LoadPixelData.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -22,7 +23,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(TPixel[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -46,7 +47,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(byte[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -58,7 +59,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(Configuration.Default, data, width, height); /// @@ -71,7 +72,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(Configuration config, byte[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(config, MemoryMarshal.Cast(new ReadOnlySpan(data)), width, height); /// @@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(config, MemoryMarshal.Cast(data), width, height); /// @@ -97,7 +98,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(Configuration config, TPixel[] data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return LoadPixelData(config, new ReadOnlySpan(data), width, height); } @@ -112,16 +113,16 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . public static Image LoadPixelData(Configuration config, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); var image = new Image(config, width, height); - - data.Slice(0, count).CopyTo(image.Frames.RootFrame.GetPixelSpan()); + data = data.Slice(0, count); + data.CopyTo(image.Frames.RootFrame.PixelBuffer.FastMemoryGroup); return image; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.WrapMemory.cs b/src/ImageSharp/Image.WrapMemory.cs index 095991b07..e5181a0db 100644 --- a/src/ImageSharp/Image.WrapMemory.cs +++ b/src/ImageSharp/Image.WrapMemory.cs @@ -32,9 +32,9 @@ namespace SixLabors.ImageSharp int width, int height, ImageMetadata metadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var memorySource = new MemorySource(pixelMemory); + var memorySource = MemoryGroup.Wrap(pixelMemory); return new Image(config, memorySource, width, height, metadata); } @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp Memory pixelMemory, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return WrapMemory(config, pixelMemory, width, height, new ImageMetadata()); } @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp Memory pixelMemory, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return WrapMemory(Configuration.Default, pixelMemory, width, height); } @@ -97,9 +97,9 @@ namespace SixLabors.ImageSharp int width, int height, ImageMetadata metadata) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - var memorySource = new MemorySource(pixelMemoryOwner, false); + var memorySource = MemoryGroup.Wrap(pixelMemoryOwner); return new Image(config, memorySource, width, height, metadata); } @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp IMemoryOwner pixelMemoryOwner, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return WrapMemory(config, pixelMemoryOwner, width, height, new ImageMetadata()); } @@ -142,9 +142,9 @@ namespace SixLabors.ImageSharp IMemoryOwner pixelMemoryOwner, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return WrapMemory(Configuration.Default, pixelMemoryOwner, width, height); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 574178d39..b1cefdf1d 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The public Image CloneAs() - where TPixel2 : struct, IPixel => this.CloneAs(this.GetConfiguration()); + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); /// /// Returns a copy of the image in the given pixel format. @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp /// The configuration providing initialization code which allows extending the library. /// The . public abstract Image CloneAs(Configuration configuration) - where TPixel2 : struct, IPixel; + where TPixel2 : unmanaged, IPixel; /// /// Update the size of the image after mutation. @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp } public void Visit(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.encoder.Encode(image, this.stream); } diff --git a/src/ImageSharp/ImageExtensions.Internal.cs b/src/ImageSharp/ImageExtensions.Internal.cs index 5b5e56665..a1fc51043 100644 --- a/src/ImageSharp/ImageExtensions.Internal.cs +++ b/src/ImageSharp/ImageExtensions.Internal.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp /// The /// internal static Buffer2D GetRootFramePixelBuffer(this Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.Frames.RootFrame.PixelBuffer; } diff --git a/src/ImageSharp/ImageExtensions.cs b/src/ImageSharp/ImageExtensions.cs index 7f753c05a..24d2ad49b 100644 --- a/src/ImageSharp/ImageExtensions.cs +++ b/src/ImageSharp/ImageExtensions.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp /// The format. /// The public static string ToBase64String(this Image source, IImageFormat format) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var stream = new MemoryStream()) { diff --git a/src/ImageSharp/ImageFrame.LoadPixelData.cs b/src/ImageSharp/ImageFrame.LoadPixelData.cs index 9e90aeaf5..e6035a177 100644 --- a/src/ImageSharp/ImageFrame.LoadPixelData.cs +++ b/src/ImageSharp/ImageFrame.LoadPixelData.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => LoadPixelData(configuration, MemoryMarshal.Cast(data), width, height); /// @@ -36,14 +37,15 @@ namespace SixLabors.ImageSharp /// The pixel format. /// A new . internal static ImageFrame LoadPixelData(Configuration configuration, ReadOnlySpan data, int width, int height) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int count = width * height; Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); var image = new ImageFrame(configuration, width, height); - data.Slice(0, count).CopyTo(image.GetPixelSpan()); + data = data.Slice(0, count); + data.CopyTo(image.PixelBuffer.FastMemoryGroup); return image; } diff --git a/src/ImageSharp/ImageFrame.cs b/src/ImageSharp/ImageFrame.cs index 235840e77..93fa20587 100644 --- a/src/ImageSharp/ImageFrame.cs +++ b/src/ImageSharp/ImageFrame.cs @@ -3,6 +3,7 @@ using System; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -78,8 +79,8 @@ namespace SixLabors.ImageSharp /// Whether to dispose of managed and unmanaged objects. protected abstract void Dispose(bool disposing); - internal abstract void CopyPixelsTo(Span destination) - where TDestinationPixel : struct, IPixel; + internal abstract void CopyPixelsTo(MemoryGroup destination) + where TDestinationPixel : unmanaged, IPixel; /// /// Updates the size of the image frame. diff --git a/src/ImageSharp/ImageFrameCollection{TPixel}.cs b/src/ImageSharp/ImageFrameCollection{TPixel}.cs index 722a4ddea..89a1dfcc1 100644 --- a/src/ImageSharp/ImageFrameCollection{TPixel}.cs +++ b/src/ImageSharp/ImageFrameCollection{TPixel}.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp /// /// The type of the pixel. public sealed class ImageFrameCollection : ImageFrameCollection, IEnumerable> - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly IList> frames = new List>(); private readonly Image parent; @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp this.frames.Add(new ImageFrame(parent.GetConfiguration(), width, height, backgroundColor)); } - internal ImageFrameCollection(Image parent, int width, int height, MemorySource memorySource) + internal ImageFrameCollection(Image parent, int width, int height, MemoryGroup memorySource) { this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); @@ -351,7 +351,7 @@ namespace SixLabors.ImageSharp this.parent.GetConfiguration(), source.Size(), source.Metadata.DeepClone()); - source.CopyPixelsTo(result.PixelBuffer.GetSpan()); + source.CopyPixelsTo(result.PixelBuffer.FastMemoryGroup); return result; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e1112c017..a35443ec9 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -4,9 +4,7 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -20,7 +18,7 @@ namespace SixLabors.ImageSharp /// /// The pixel format. public sealed class ImageFrame : ImageFrame, IPixelSource - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private bool isDisposed; @@ -99,7 +97,7 @@ namespace SixLabors.ImageSharp /// The width of the image in pixels. /// The height of the image in pixels. /// The memory source. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource) : this(configuration, width, height, memorySource, new ImageFrameMetadata()) { } @@ -112,7 +110,7 @@ namespace SixLabors.ImageSharp /// The height of the image in pixels. /// The memory source. /// The metadata. - internal ImageFrame(Configuration configuration, int width, int height, MemorySource memorySource, ImageFrameMetadata metadata) + internal ImageFrame(Configuration configuration, int width, int height, MemoryGroup memorySource, ImageFrameMetadata metadata) : base(configuration, width, height, metadata) { Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -133,7 +131,7 @@ namespace SixLabors.ImageSharp Guard.NotNull(source, nameof(source)); this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); - source.PixelBuffer.GetSpan().CopyTo(this.PixelBuffer.GetSpan()); + source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } /// @@ -150,13 +148,22 @@ namespace SixLabors.ImageSharp /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.PixelBuffer[x, y]; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelBuffer.GetElementUnsafe(x, y); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => this.PixelBuffer[x, y] = value; + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelBuffer.GetElementUnsafe(x, y) = value; + } } /// @@ -179,7 +186,7 @@ namespace SixLabors.ImageSharp throw new ArgumentException("ImageFrame.CopyTo(): target must be of the same size!", nameof(target)); } - this.GetPixelSpan().CopyTo(target.GetSpan()); + this.PixelBuffer.FastMemoryGroup.CopyTo(target.FastMemoryGroup); } /// @@ -211,15 +218,22 @@ namespace SixLabors.ImageSharp this.isDisposed = true; } - internal override void CopyPixelsTo(Span destination) + internal override void CopyPixelsTo(MemoryGroup destination) { if (typeof(TPixel) == typeof(TDestinationPixel)) { - Span dest1 = MemoryMarshal.Cast(destination); - this.PixelBuffer.GetSpan().CopyTo(dest1); + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + { + Span d1 = MemoryMarshal.Cast(d); + s.CopyTo(d1); + }); + return; } - PixelOperations.Instance.To(this.GetConfiguration(), this.PixelBuffer.GetSpan(), destination); + this.PixelBuffer.FastMemoryGroup.TransformTo(destination, (s, d) => + { + PixelOperations.Instance.To(this.GetConfiguration(), s, d); + }); } /// @@ -244,7 +258,7 @@ namespace SixLabors.ImageSharp /// The pixel format. /// The internal ImageFrame CloneAs() - where TPixel2 : struct, IPixel => this.CloneAs(this.GetConfiguration()); + where TPixel2 : unmanaged, IPixel => this.CloneAs(this.GetConfiguration()); /// /// Returns a copy of the image frame in the given pixel format. @@ -253,7 +267,7 @@ namespace SixLabors.ImageSharp /// The configuration providing initialization code which allows extending the library. /// The internal ImageFrame CloneAs(Configuration configuration) - where TPixel2 : struct, IPixel + where TPixel2 : unmanaged, IPixel { if (typeof(TPixel2) == typeof(TPixel)) { @@ -261,19 +275,12 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); + var operation = new RowIntervalOperation(this, target, configuration); - ParallelHelper.IterateRows( - this.Bounds(), + ParallelRowIterator.IterateRowIntervals( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = this.GetPixelRowSpan(y); - Span targetRow = target.GetPixelRowSpan(y); - PixelOperations.Instance.To(configuration, sourceRow, targetRow); - } - }); + this.Bounds(), + in operation); return target; } @@ -284,15 +291,69 @@ namespace SixLabors.ImageSharp /// The value to initialize the bitmap with. internal void Clear(TPixel value) { - Span span = this.GetPixelSpan(); + MemoryGroup group = this.PixelBuffer.FastMemoryGroup; if (value.Equals(default)) { - span.Clear(); + group.Clear(); } else { - span.Fill(value); + group.Fill(value); + } + } + + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TPixel2 : unmanaged, IPixel + { + private readonly ImageFrame source; + private readonly ImageFrame target; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ImageFrame source, + ImageFrame target, + Configuration configuration) + { + this.source = source; + this.target = target; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.target.GetPixelRowSpan(y); + PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); + } } } } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 0fd449d90..baf4a2ce1 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -24,9 +24,9 @@ - - - + + + @@ -36,7 +36,7 @@ - + True True @@ -209,5 +209,4 @@ - diff --git a/src/ImageSharp/ImageSharp.csproj.DotSettings b/src/ImageSharp/ImageSharp.csproj.DotSettings index 018ca75cd..6896e069c 100644 --- a/src/ImageSharp/ImageSharp.csproj.DotSettings +++ b/src/ImageSharp/ImageSharp.csproj.DotSettings @@ -2,6 +2,8 @@ True True True + True + True True True True diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 87bdf90a1..56f1f6b7b 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; @@ -18,7 +19,7 @@ namespace SixLabors.ImageSharp /// /// The pixel format. public sealed class Image : Image - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private bool isDisposed; @@ -74,22 +75,22 @@ namespace SixLabors.ImageSharp /// /// Initializes a new instance of the class - /// wrapping an external . + /// wrapping an external . /// /// The configuration providing initialization code which allows extending the library. - /// The memory source. + /// The memory source. /// The width of the image in pixels. /// The height of the image in pixels. /// The images metadata. internal Image( Configuration configuration, - MemorySource memorySource, + MemoryGroup memoryGroup, int width, int height, ImageMetadata metadata) : base(configuration, PixelTypeInfo.Create(), metadata, width, height) { - this.Frames = new ImageFrameCollection(this, width, height, memorySource); + this.Frames = new ImageFrameCollection(this, width, height, memoryGroup); } /// @@ -144,10 +145,22 @@ namespace SixLabors.ImageSharp /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. /// The at the specified position. + /// Thrown when the provided (x,y) coordinates are outside the image boundary. public TPixel this[int x, int y] { - get => this.PixelSource.PixelBuffer[x, y]; - set => this.PixelSource.PixelBuffer[x, y] = value; + [MethodImpl(InliningOptions.ShortMethod)] + get + { + this.VerifyCoords(x, y); + return this.PixelSource.PixelBuffer.GetElementUnsafe(x, y); + } + + [MethodImpl(InliningOptions.ShortMethod)] + set + { + this.VerifyCoords(x, y); + this.PixelSource.PixelBuffer.GetElementUnsafe(x, y) = value; + } } /// @@ -265,5 +278,25 @@ namespace SixLabors.ImageSharp return rootSize; } + + [MethodImpl(InliningOptions.ShortMethod)] + private void VerifyCoords(int x, int y) + { + if (x < 0 || x >= this.Width) + { + ThrowArgumentOutOfRangeException(nameof(x)); + } + + if (y < 0 || y >= this.Height) + { + ThrowArgumentOutOfRangeException(nameof(y)); + } + } + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowArgumentOutOfRangeException(string paramName) + { + throw new ArgumentOutOfRangeException(paramName); + } } } diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs index 0d7e0b784..f94359830 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory.Internals; @@ -46,7 +47,15 @@ namespace SixLabors.ImageSharp.Memory protected byte[] Data { get; private set; } /// - public override Span GetSpan() => MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); + public override Span GetSpan() + { + if (this.Data is null) + { + ThrowObjectDisposedException(); + } + + return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); + } /// protected override void Dispose(bool disposing) @@ -66,6 +75,12 @@ namespace SixLabors.ImageSharp.Memory } protected override object GetPinnableObject() => this.Data; + + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); + } } /// diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs index 1ce2525b8..5ef60c9ed 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs @@ -29,6 +29,9 @@ namespace SixLabors.ImageSharp.Memory /// private const int DefaultNormalPoolBucketCount = 16; + // TODO: This value should be determined by benchmarking + private const int DefaultBufferCapacityInBytes = int.MaxValue / 4; + /// /// This is the default. Should be good for most use cases. /// @@ -39,7 +42,8 @@ namespace SixLabors.ImageSharp.Memory DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes, DefaultLargePoolBucketCount, - DefaultNormalPoolBucketCount); + DefaultNormalPoolBucketCount, + DefaultBufferCapacityInBytes); } /// @@ -69,4 +73,4 @@ namespace SixLabors.ImageSharp.Memory return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index c4d92ca3c..4a04cb5d6 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -60,13 +60,41 @@ namespace SixLabors.ImageSharp.Memory /// The threshold to pool arrays in which has less buckets for memory safety. /// Max arrays per bucket for the large array pool. /// Max arrays per bucket for the normal array pool. - public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes, int maxArraysPerBucketLargePool, int maxArraysPerBucketNormalPool) + public ArrayPoolMemoryAllocator( + int maxPoolSizeInBytes, + int poolSelectorThresholdInBytes, + int maxArraysPerBucketLargePool, + int maxArraysPerBucketNormalPool) + : this( + maxPoolSizeInBytes, + poolSelectorThresholdInBytes, + maxArraysPerBucketLargePool, + maxArraysPerBucketNormalPool, + DefaultBufferCapacityInBytes) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. + /// The threshold to pool arrays in which has less buckets for memory safety. + /// Max arrays per bucket for the large array pool. + /// Max arrays per bucket for the normal array pool. + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + public ArrayPoolMemoryAllocator( + int maxPoolSizeInBytes, + int poolSelectorThresholdInBytes, + int maxArraysPerBucketLargePool, + int maxArraysPerBucketNormalPool, + int bufferCapacityInBytes) { Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); this.MaxPoolSizeInBytes = maxPoolSizeInBytes; this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; + this.BufferCapacityInBytes = bufferCapacityInBytes; this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; @@ -83,23 +111,29 @@ namespace SixLabors.ImageSharp.Memory /// public int PoolSelectorThresholdInBytes { get; } + /// + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. + /// + public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests + /// public override void ReleaseRetainedResources() { this.InitArrayPools(); } + /// + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; + /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); int itemSizeBytes = Unsafe.SizeOf(); int bufferSizeInBytes = length * itemSizeBytes; - if (bufferSizeInBytes < 0) + if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes) { - throw new ArgumentOutOfRangeException( - nameof(length), - $"{nameof(ArrayPoolMemoryAllocator)} can not allocate {length} elements of {typeof(T).Name}."); + ThrowInvalidAllocationException(length); } ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); @@ -136,6 +170,11 @@ namespace SixLabors.ImageSharp.Memory return maxPoolSizeInBytes / 4; } + [MethodImpl(InliningOptions.ColdPath)] + private static void ThrowInvalidAllocationException(int length) => + throw new InvalidMemoryOperationException( + $"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); + private ArrayPool GetArrayPool(int bufferSizeInBytes) { return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; @@ -147,4 +186,4 @@ namespace SixLabors.ImageSharp.Memory this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 20598c3e3..a4e1de197 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; namespace SixLabors.ImageSharp.Memory @@ -10,6 +11,12 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { + /// + /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. + /// + /// The length of the largest contiguous buffer that can be handled by this allocator instance. + protected internal abstract int GetBufferCapacityInBytes(); + /// /// Allocates an , holding a of length . /// @@ -17,6 +24,8 @@ namespace SixLabors.ImageSharp.Memory /// Size of the buffer to allocate. /// The allocation options. /// A buffer of values of type . + /// When length is zero or negative. + /// When length is over the capacity of the allocator. public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) where T : struct; @@ -26,6 +35,8 @@ namespace SixLabors.ImageSharp.Memory /// The requested buffer length. /// The allocation options. /// The . + /// When length is zero or negative. + /// When length is over the capacity of the allocator. public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); /// diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 54b64b131..4c62e4ded 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -7,10 +7,13 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { /// - /// Implements by newing up arrays by the GC on every allocation requests. + /// Implements by newing up managed arrays on every allocation request. /// public sealed class SimpleGcMemoryAllocator : MemoryAllocator { + /// + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + /// public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { @@ -27,4 +30,4 @@ namespace SixLabors.ImageSharp.Memory return new BasicByteBuffer(new byte[length]); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index ba4f9c925..fea44f52c 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -14,62 +15,20 @@ namespace SixLabors.ImageSharp.Memory public static class Buffer2DExtensions { /// - /// Gets a to the backing buffer of . + /// Gets the backing . /// - /// The . - /// The value type. - /// The referencing the memory area. - public static Span GetSpan(this Buffer2D buffer) + /// The buffer. + /// The element type. + /// The MemoryGroup. + public static IMemoryGroup GetMemoryGroup(this Buffer2D buffer) where T : struct { Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.GetSpan(); - } - - /// - /// Gets the holding the backing buffer of . - /// - /// The . - /// The value type. - /// The . - public static Memory GetMemory(this Buffer2D buffer) - where T : struct - { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.Memory; - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The buffer - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span GetRowSpan(this Buffer2D buffer, int y) - where T : struct - { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.GetSpan().Slice(y * buffer.Width, buffer.Width); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// - /// The buffer - /// The y (row) coordinate - /// The element type - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Memory GetRowMemory(this Buffer2D buffer, int y) - where T : struct - { - Guard.NotNull(buffer, nameof(buffer)); - return buffer.MemorySource.Memory.Slice(y * buffer.Width, buffer.Width); + return buffer.FastMemoryGroup.View; } /// + /// TODO: Does not work with multi-buffer groups, should be specific to Resize. /// Copy columns of inplace, /// from positions starting at to positions at . /// @@ -91,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory int dOffset = destIndex * elementSize; long count = columnCount * elementSize; - Span span = MemoryMarshal.AsBytes(buffer.GetMemory().Span); + Span span = MemoryMarshal.AsBytes(buffer.GetSingleMemory().Span); fixed (byte* ptr = span) { @@ -145,15 +104,6 @@ namespace SixLabors.ImageSharp.Memory where T : struct => new BufferArea(buffer); - /// - /// Gets a span for all the pixels in defined by - /// - internal static Span GetMultiRowSpan(this Buffer2D buffer, in RowInterval rows) - where T : struct - { - return buffer.GetSpan().Slice(rows.Min * buffer.Width, rows.Height * buffer.Width); - } - /// /// Returns the size of the buffer. /// diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 6b7f3bf42..bf8630931 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory { @@ -14,23 +16,27 @@ namespace SixLabors.ImageSharp.Memory /// Before RC1, this class might be target of API changes, use it on your own risk! /// /// The value type. - // TODO: Consider moving this type to the SixLabors.ImageSharp.Memory namespace (SixLabors.Core). public sealed class Buffer2D : IDisposable where T : struct { - private MemorySource memorySource; + private Memory cachedMemory = default; /// /// Initializes a new instance of the class. /// - /// The buffer to wrap - /// The number of elements in a row - /// The number of rows - internal Buffer2D(MemorySource memorySource, int width, int height) + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height) { - this.memorySource = memorySource; + this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; + + if (memoryGroup.Count == 1) + { + this.cachedMemory = memoryGroup[0]; + } } /// @@ -44,9 +50,20 @@ namespace SixLabors.ImageSharp.Memory public int Height { get; private set; } /// - /// Gets the backing + /// Gets the backing . + /// + /// The MemoryGroup. + public IMemoryGroup MemoryGroup => this.FastMemoryGroup.View; + + /// + /// Gets the backing without the view abstraction. /// - internal MemorySource MemorySource => this.memorySource; + /// + /// This property has been kept internal intentionally. + /// It's public counterpart is , + /// which only exposes the view of the MemoryGroup. + /// + internal MemoryGroup FastMemoryGroup { get; } /// /// Gets a reference to the element at the specified position. @@ -54,16 +71,18 @@ namespace SixLabors.ImageSharp.Memory /// The x coordinate (row) /// The y coordinate (position at row) /// A reference to the element. - internal ref T this[int x, int y] + /// When index is out of range of the buffer. + public ref T this[int x, int y] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get { + DebugGuard.MustBeGreaterThanOrEqualTo(x, 0, nameof(x)); + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - Span span = this.GetSpan(); - return ref span[(this.Width * y) + x]; + return ref this.GetRowSpan(y)[x]; } } @@ -72,7 +91,102 @@ namespace SixLabors.ImageSharp.Memory /// public void Dispose() { - this.MemorySource.Dispose(); + this.FastMemoryGroup.Dispose(); + this.cachedMemory = default; + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// + /// This method does not validate the y argument for performance reason, + /// is being propagated from lower levels. + /// + /// The row index. + /// The of the pixels in the row. + /// Thrown when row index is out of range. + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetRowSpan(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y).Span; + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal ref T GetElementUnsafe(int x, int y) + { + if (this.cachedMemory.Length > 0) + { + Span span = this.cachedMemory.Span; + ref T start = ref MemoryMarshal.GetReference(span); + return ref Unsafe.Add(ref start, (y * this.Width) + x); + } + + return ref this.GetElementSlow(x, y); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// This method is intended for internal use only, since it does not use the indirection provided by + /// . + /// + /// The y (row) coordinate. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetFastRowMemory(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return this.cachedMemory.Length > 0 + ? this.cachedMemory.Slice(y * this.Width, this.Width) + : this.GetRowMemorySlow(y); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. + /// + /// The y (row) coordinate. + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetSafeRowMemory(int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); + DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + return this.FastMemoryGroup.View.GetBoundedSlice(y * this.Width, this.Width); + } + + /// + /// Gets a to the backing data if the backing group consists of a single contiguous memory buffer. + /// Throws otherwise. + /// + /// The referencing the memory area. + /// + /// Thrown when the backing group is discontiguous. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal Span GetSingleSpan() + { + // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup + return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.GetSingleSpanSlow(); + } + + /// + /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. + /// Throws otherwise. + /// + /// The . + /// + /// Thrown when the backing group is discontiguous. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal Memory GetSingleMemory() + { + // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup + return this.cachedMemory.Length != 0 ? this.cachedMemory : this.GetSingleMemorySlow(); } /// @@ -81,11 +195,27 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - MemorySource.SwapOrCopyContent(ref destination.memorySource, ref source.memorySource); - SwapDimensionData(destination, source); + bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); + SwapOwnData(destination, source, swap); } - private static void SwapDimensionData(Buffer2D a, Buffer2D b) + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * this.Width, this.Width); + + [MethodImpl(InliningOptions.ColdPath)] + private Memory GetSingleMemorySlow() => this.FastMemoryGroup.Single(); + + [MethodImpl(InliningOptions.ColdPath)] + private Span GetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; + + [MethodImpl(InliningOptions.ColdPath)] + private ref T GetElementSlow(int x, int y) + { + Span span = this.GetRowMemorySlow(y).Span; + return ref span[x]; + } + + private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -95,6 +225,13 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; + + if (swapCachedMemory) + { + Memory aCached = a.cachedMemory; + a.cachedMemory = b.cachedMemory; + b.cachedMemory = aCached; + } } } } diff --git a/src/ImageSharp/Memory/BufferArea{T}.cs b/src/ImageSharp/Memory/BufferArea{T}.cs index 08731846e..076f7f37c 100644 --- a/src/ImageSharp/Memory/BufferArea{T}.cs +++ b/src/ImageSharp/Memory/BufferArea{T}.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Memory /// Represents a rectangular area inside a 2D memory buffer (). /// This type is kind-of 2D Span, but it can live on heap. /// - /// The element type + /// The element type. internal readonly struct BufferArea where T : struct { @@ -72,15 +72,19 @@ namespace SixLabors.ImageSharp.Memory /// The position inside a row /// The row index /// The reference to the value - public ref T this[int x, int y] => ref this.DestinationBuffer.GetSpan()[this.GetIndexOf(x, y)]; + public ref T this[int x, int y] => ref this.DestinationBuffer[x + this.Rectangle.X, y + this.Rectangle.Y]; /// /// Gets a reference to the [0,0] element. /// /// The reference to the [0,0] element [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T GetReferenceToOrigin() => - ref this.DestinationBuffer.GetSpan()[(this.Rectangle.Y * this.DestinationBuffer.Width) + this.Rectangle.X]; + public ref T GetReferenceToOrigin() + { + int y = this.Rectangle.Y; + int x = this.Rectangle.X; + return ref this.DestinationBuffer.GetRowSpan(y)[x]; + } /// /// Gets a span to row 'y' inside this area. @@ -90,20 +94,20 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetRowSpan(int y) { - int yy = this.GetRowIndex(y); + int yy = this.Rectangle.Y + y; int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.DestinationBuffer.GetSpan().Slice(yy + xx, width); + return this.DestinationBuffer.GetRowSpan(yy).Slice(xx, width); } /// /// Returns a sub-area as . (Similar to .) /// - /// The x index at the subarea origo - /// The y index at the subarea origo - /// The desired width of the subarea - /// The desired height of the subarea + /// The x index at the subarea origin. + /// The y index at the subarea origin. + /// The desired width of the subarea. + /// The desired height of the subarea. /// The subarea [MethodImpl(MethodImplOptions.AggressiveInlining)] public BufferArea GetSubArea(int x, int y, int width, int height) @@ -129,26 +133,12 @@ namespace SixLabors.ImageSharp.Memory return new BufferArea(this.DestinationBuffer, rectangle); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int GetIndexOf(int x, int y) - { - int yy = this.GetRowIndex(y); - int xx = this.Rectangle.X + x; - return yy + xx; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal int GetRowIndex(int y) - { - return (y + this.Rectangle.Y) * this.DestinationBuffer.Width; - } - public void Clear() { // Optimization for when the size of the area is the same as the buffer size. if (this.IsFullBufferArea) { - this.DestinationBuffer.GetSpan().Clear(); + this.DestinationBuffer.FastMemoryGroup.Clear(); return; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs new file mode 100644 index 000000000..89aca914d --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents discontiguous group of multiple uniformly-sized memory segments. + /// The last segment can be smaller than the preceding ones. + /// + /// The element type. + public interface IMemoryGroup : IReadOnlyList> + where T : struct + { + /// + /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. + /// The last buffer is allowed to be smaller. + /// + int BufferLength { get; } + + /// + /// Gets the aggregate number of elements in the group. + /// + long TotalLength { get; } + + /// + /// Gets a value indicating whether the group has been invalidated. + /// + /// + /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces + /// the image buffers internally. + /// + bool IsValid { get; } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs new file mode 100644 index 000000000..28da49263 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -0,0 +1,232 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal static class MemoryGroupExtensions + { + internal static void Fill(this IMemoryGroup group, T value) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Fill(value); + } + } + + internal static void Clear(this IMemoryGroup group) + where T : struct + { + foreach (Memory memory in group) + { + memory.Span.Clear(); + } + } + + /// + /// Returns a slice that is expected to be within the bounds of a single buffer. + /// Otherwise is thrown. + /// + internal static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length) + where T : struct + { + Guard.NotNull(group, nameof(group)); + Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!"); + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); + + int bufferIdx = (int)(start / group.BufferLength); + if (bufferIdx >= group.Count) + { + throw new ArgumentOutOfRangeException(nameof(start)); + } + + int bufferStart = (int)(start % group.BufferLength); + int bufferEnd = bufferStart + length; + Memory memory = group[bufferIdx]; + + if (bufferEnd > memory.Length) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return memory.Slice(bufferStart, length); + } + + internal static void CopyTo(this IMemoryGroup source, Span target) + where T : struct + { + Guard.NotNull(source, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); + + var cur = new MemoryGroupCursor(source); + long position = 0; + while (position < source.TotalLength) + { + int fwd = Math.Min(cur.LookAhead(), target.Length); + cur.GetSpan(fwd).CopyTo(target); + + cur.Forward(fwd); + target = target.Slice(fwd); + position += fwd; + } + } + + internal static void CopyTo(this Span source, IMemoryGroup target) + where T : struct + => CopyTo((ReadOnlySpan)source, target); + + internal static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) + where T : struct + { + Guard.NotNull(target, nameof(target)); + Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); + + var cur = new MemoryGroupCursor(target); + + while (!source.IsEmpty) + { + int fwd = Math.Min(cur.LookAhead(), source.Length); + source.Slice(0, fwd).CopyTo(cur.GetSpan(fwd)); + cur.Forward(fwd); + source = source.Slice(fwd); + } + } + + internal static void CopyTo(this IMemoryGroup source, IMemoryGroup target) + where T : struct + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + srcSpan.CopyTo(trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + internal static void TransformTo( + this IMemoryGroup source, + IMemoryGroup target, + TransformItemsDelegate transform) + where TSource : struct + where TTarget : struct + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(target, nameof(target)); + Guard.NotNull(transform, nameof(transform)); + Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); + Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); + Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + + if (source.IsEmpty()) + { + return; + } + + long position = 0; + var srcCur = new MemoryGroupCursor(source); + var trgCur = new MemoryGroupCursor(target); + + while (position < source.TotalLength) + { + int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); + Span srcSpan = srcCur.GetSpan(fwd); + Span trgSpan = trgCur.GetSpan(fwd); + transform(srcSpan, trgSpan); + + srcCur.Forward(fwd); + trgCur.Forward(fwd); + position += fwd; + } + } + + internal static void TransformInplace( + this IMemoryGroup memoryGroup, + TransformItemsInplaceDelegate transform) + where T : struct + { + foreach (Memory memory in memoryGroup) + { + transform(memory.Span); + } + } + + internal static bool IsEmpty(this IMemoryGroup group) + where T : struct + => group.Count == 0; + + private struct MemoryGroupCursor + where T : struct + { + private readonly IMemoryGroup memoryGroup; + + private int bufferIndex; + + private int elementIndex; + + public MemoryGroupCursor(IMemoryGroup memoryGroup) + { + this.memoryGroup = memoryGroup; + this.bufferIndex = 0; + this.elementIndex = 0; + } + + private bool IsAtLastBuffer => this.bufferIndex == this.memoryGroup.Count - 1; + + private int CurrentBufferLength => this.memoryGroup[this.bufferIndex].Length; + + public Span GetSpan(int length) + { + return this.memoryGroup[this.bufferIndex].Span.Slice(this.elementIndex, length); + } + + public int LookAhead() + { + return this.CurrentBufferLength - this.elementIndex; + } + + public void Forward(int steps) + { + int nextIdx = this.elementIndex + steps; + int currentBufferLength = this.CurrentBufferLength; + + if (nextIdx < currentBufferLength) + { + this.elementIndex = nextIdx; + } + else if (nextIdx == currentBufferLength) + { + this.bufferIndex++; + this.elementIndex = 0; + } + else + { + // If we get here, it indicates a bug in CopyTo: + throw new ArgumentException("Can't forward multiple buffers!", nameof(steps)); + } + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs new file mode 100644 index 000000000..3f39ba12f --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupView{T}.cs @@ -0,0 +1,134 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Implements , defining a view for + /// rather than owning the segments. + /// + /// + /// This type provides an indirection, protecting the users of publicly exposed memory API-s + /// from internal memory-swaps. Whenever an internal swap happens, the + /// instance becomes invalid, throwing an exception on all operations. + /// + /// The element type. + internal class MemoryGroupView : IMemoryGroup + where T : struct + { + private MemoryGroup owner; + private readonly MemoryOwnerWrapper[] memoryWrappers; + + public MemoryGroupView(MemoryGroup owner) + { + this.owner = owner; + this.memoryWrappers = new MemoryOwnerWrapper[owner.Count]; + + for (int i = 0; i < owner.Count; i++) + { + this.memoryWrappers[i] = new MemoryOwnerWrapper(this, i); + } + } + + public int Count + { + get + { + this.EnsureIsValid(); + return this.owner.Count; + } + } + + public int BufferLength + { + get + { + this.EnsureIsValid(); + return this.owner.BufferLength; + } + } + + public long TotalLength + { + get + { + this.EnsureIsValid(); + return this.owner.TotalLength; + } + } + + public bool IsValid => this.owner != null; + + public Memory this[int index] + { + get + { + this.EnsureIsValid(); + return this.memoryWrappers[index].Memory; + } + } + + public IEnumerator> GetEnumerator() + { + this.EnsureIsValid(); + for (int i = 0; i < this.Count; i++) + { + yield return this.memoryWrappers[i].Memory; + } + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + internal void Invalidate() + { + this.owner = null; + } + + private void EnsureIsValid() + { + if (!this.IsValid) + { + throw new InvalidMemoryOperationException("Can not access an invalidated MemoryGroupView!"); + } + } + + private class MemoryOwnerWrapper : MemoryManager + { + private readonly MemoryGroupView view; + + private readonly int index; + + public MemoryOwnerWrapper(MemoryGroupView view, int index) + { + this.view = view; + this.index = index; + } + + protected override void Dispose(bool disposing) + { + } + + public override Span GetSpan() + { + this.view.EnsureIsValid(); + return this.view.owner[this.index].Span; + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + this.view.EnsureIsValid(); + return this.view.owner[this.index].Pin(); + } + + public override void Unpin() + { + throw new NotSupportedException(); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs new file mode 100644 index 000000000..f1fe4ed9c --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Memory +{ + internal abstract partial class MemoryGroup + { + // Analogous to the "consumed" variant of MemorySource + private sealed class Consumed : MemoryGroup + { + private readonly Memory[] source; + + public Consumed(Memory[] source, int bufferLength, long totalLength) + : base(bufferLength, totalLength) + { + this.source = source; + this.View = new MemoryGroupView(this); + } + + public override int Count => this.source.Length; + + public override Memory this[int index] => this.source[index]; + + public override IEnumerator> GetEnumerator() + { + for (int i = 0; i < this.source.Length; i++) + { + yield return this.source[i]; + } + } + + public override void Dispose() + { + this.View.Invalidate(); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs new file mode 100644 index 000000000..b42b90d28 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; + +namespace SixLabors.ImageSharp.Memory +{ + // Analogous to the "owned" variant of MemorySource + internal abstract partial class MemoryGroup + { + private sealed class Owned : MemoryGroup + { + private IMemoryOwner[] memoryOwners; + + public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) + : base(bufferLength, totalLength) + { + this.memoryOwners = memoryOwners; + this.Swappable = swappable; + this.View = new MemoryGroupView(this); + } + + public bool Swappable { get; } + + private bool IsDisposed => this.memoryOwners == null; + + public override int Count + { + get + { + this.EnsureNotDisposed(); + return this.memoryOwners.Length; + } + } + + public override Memory this[int index] + { + get + { + this.EnsureNotDisposed(); + return this.memoryOwners[index].Memory; + } + } + + public override IEnumerator> GetEnumerator() + { + this.EnsureNotDisposed(); + return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); + } + + public override void Dispose() + { + if (this.IsDisposed) + { + return; + } + + this.View.Invalidate(); + + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + memoryOwner.Dispose(); + } + + this.memoryOwners = null; + this.IsValid = false; + } + + private void EnsureNotDisposed() + { + if (this.memoryOwners == null) + { + throw new ObjectDisposedException(nameof(MemoryGroup)); + } + } + + internal static void SwapContents(Owned a, Owned b) + { + a.EnsureNotDisposed(); + b.EnsureNotDisposed(); + + IMemoryOwner[] tempOwners = a.memoryOwners; + long tempTotalLength = a.TotalLength; + int tempBufferLength = a.BufferLength; + + a.memoryOwners = b.memoryOwners; + a.TotalLength = b.TotalLength; + a.BufferLength = b.BufferLength; + + b.memoryOwners = tempOwners; + b.TotalLength = tempTotalLength; + b.BufferLength = tempBufferLength; + + a.View.Invalidate(); + b.View.Invalidate(); + a.View = new MemoryGroupView(a); + b.View = new MemoryGroupView(b); + } + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs new file mode 100644 index 000000000..38de57b4a --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Represents discontinuous group of multiple uniformly-sized memory segments. + /// The underlying buffers may change with time, therefore it's not safe to expose them directly on + /// and . + /// + /// The element type. + internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable + where T : struct + { + private static readonly int ElementSize = Unsafe.SizeOf(); + + private MemoryGroup(int bufferLength, long totalLength) + { + this.BufferLength = bufferLength; + this.TotalLength = totalLength; + } + + /// + public abstract int Count { get; } + + /// + public int BufferLength { get; private set; } + + /// + public long TotalLength { get; private set; } + + /// + public bool IsValid { get; private set; } = true; + + public MemoryGroupView View { get; private set; } + + /// + public abstract Memory this[int index] { get; } + + /// + public abstract void Dispose(); + + /// + public abstract IEnumerator> GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + + /// + /// Creates a new memory group, allocating it's buffers with the provided allocator. + /// + /// The to use. + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The . + /// A new . + /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. + public static MemoryGroup Allocate( + MemoryAllocator allocator, + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + { + Guard.NotNull(allocator, nameof(allocator)); + Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); + + int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; + + if (bufferAlignment > blockCapacityInElements) + { + throw new InvalidMemoryOperationException( + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); + } + + if (totalLength == 0) + { + var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; + return new Owned(buffers0, 0, 0, true); + } + + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; + int bufferLength = numberOfAlignedSegments * bufferAlignment; + if (totalLength > 0 && totalLength < bufferLength) + { + bufferLength = (int)totalLength; + } + + int sizeOfLastBuffer = (int)(totalLength % bufferLength); + long bufferCount = totalLength / bufferLength; + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } + + var buffers = new IMemoryOwner[bufferCount]; + for (int i = 0; i < buffers.Length - 1; i++) + { + buffers[i] = allocator.Allocate(bufferLength, options); + } + + if (bufferCount > 0) + { + buffers[buffers.Length - 1] = allocator.Allocate(sizeOfLastBuffer, options); + } + + return new Owned(buffers, bufferLength, totalLength, true); + } + + public static MemoryGroup Wrap(params Memory[] source) + { + int bufferLength = source.Length > 0 ? source[0].Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source[i].Length != bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source[source.Length - 1].Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Length : 0; + + return new Consumed(source, bufferLength, totalLength); + } + + public static MemoryGroup Wrap(params IMemoryOwner[] source) + { + int bufferLength = source.Length > 0 ? source[0].Memory.Length : 0; + for (int i = 1; i < source.Length - 1; i++) + { + if (source[i].Memory.Length != bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: buffers should be uniformly sized!"); + } + } + + if (source.Length > 0 && source[source.Length - 1].Memory.Length > bufferLength) + { + throw new InvalidMemoryOperationException("Wrap: the last buffer is too large!"); + } + + long totalLength = bufferLength > 0 ? ((long)bufferLength * (source.Length - 1)) + source[source.Length - 1].Memory.Length : 0; + + return new Owned(source, bufferLength, totalLength, false); + } + + /// + /// Swaps the contents of 'target' with 'source' if the buffers are allocated (1), + /// copies the contents of 'source' to 'target' otherwise (2). + /// Groups should be of same TotalLength in case 2. + /// + public static bool SwapOrCopyContent(MemoryGroup target, MemoryGroup source) + { + if (source is Owned ownedSrc && ownedSrc.Swappable && + target is Owned ownedTarget && ownedTarget.Swappable) + { + Owned.SwapContents(ownedTarget, ownedSrc); + return true; + } + else + { + if (target.TotalLength != source.TotalLength) + { + throw new InvalidMemoryOperationException( + "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); + } + + source.CopyTo(target); + return false; + } + } + } +} diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs new file mode 100644 index 000000000..c1d5c5d41 --- /dev/null +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Exception thrown when the library detects an invalid memory allocation request, + /// or an attempt has been made to use an invalidated . + /// + public class InvalidMemoryOperationException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + /// The exception message text. + public InvalidMemoryOperationException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + public InvalidMemoryOperationException() + { + } + } +} diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 6e317bb8f..22d1bddd2 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Memory /// The type of buffer items to allocate. /// The memory allocator. /// The buffer width. - /// The buffer heght. + /// The buffer height. /// The allocation options. /// The . public static Buffer2D Allocate2D( @@ -27,10 +27,9 @@ namespace SixLabors.ImageSharp.Memory AllocationOptions options = AllocationOptions.None) where T : struct { - IMemoryOwner buffer = memoryAllocator.Allocate(width * height, options); - var memorySource = new MemorySource(buffer, true); - - return new Buffer2D(memorySource, width, height); + long groupLength = (long)width * height; + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + return new Buffer2D(memoryGroup, width, height); } /// @@ -49,14 +48,30 @@ namespace SixLabors.ImageSharp.Memory where T : struct => Allocate2D(memoryAllocator, size.Width, size.Height, options); + internal static Buffer2D Allocate2DOveraligned( + this MemoryAllocator memoryAllocator, + int width, + int height, + int alignmentMultiplier, + AllocationOptions options = AllocationOptions.None) + where T : struct + { + long groupLength = (long)width * height; + MemoryGroup memoryGroup = memoryAllocator.AllocateGroup( + groupLength, + width * alignmentMultiplier, + options); + return new Buffer2D(memoryGroup, width, height); + } + /// - /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea) + /// Allocates padded buffers for BMP encoder/decoder. (Replacing old PixelRow/PixelArea). /// - /// The + /// The . /// Pixel count in the row - /// The pixel size in bytes, eg. 3 for RGB - /// The padding - /// A + /// The pixel size in bytes, eg. 3 for RGB. + /// The padding. + /// A . internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer( this MemoryAllocator memoryAllocator, int width, @@ -66,5 +81,22 @@ namespace SixLabors.ImageSharp.Memory int length = (width * pixelSizeInBytes) + paddingInBytes; return memoryAllocator.AllocateManagedByteBuffer(length); } + + /// + /// Allocates a . + /// + /// The to use. + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The . + /// A new . + /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. + internal static MemoryGroup AllocateGroup( + this MemoryAllocator memoryAllocator, + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + where T : struct + => MemoryGroup.Allocate(memoryAllocator, totalLength, bufferAlignment, options); } } diff --git a/src/ImageSharp/Memory/MemorySource.cs b/src/ImageSharp/Memory/MemorySource.cs deleted file mode 100644 index 54f1bb0d1..000000000 --- a/src/ImageSharp/Memory/MemorySource.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Holds a that is either OWNED or CONSUMED. - /// When the memory is being owned, the instance is also known. - /// Implements content transfer logic in that depends on the ownership status. - /// This is needed to transfer the contents of a temporary - /// to a persistent without copying the buffer. - /// - /// - /// For a deeper understanding of the owner/consumer model, check out the following docs:
- /// https://gist.github.com/GrabYourPitchforks/4c3e1935fd4d9fa2831dbfcab35dffc6 - /// https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T - ///
- internal struct MemorySource : IDisposable - { - /// - /// Initializes a new instance of the struct - /// by wrapping an existing . - /// - /// The to wrap - /// - /// A value indicating whether is an internal memory source managed by ImageSharp. - /// Eg. allocated by a . - /// - public MemorySource(IMemoryOwner memoryOwner, bool isInternalMemorySource) - { - this.MemoryOwner = memoryOwner; - this.Memory = memoryOwner.Memory; - this.HasSwappableContents = isInternalMemorySource; - } - - public MemorySource(Memory memory) - { - this.Memory = memory; - this.MemoryOwner = null; - this.HasSwappableContents = false; - } - - public IMemoryOwner MemoryOwner { get; private set; } - - public Memory Memory { get; private set; } - - /// - /// Gets a value indicating whether we are allowed to swap the contents of this buffer - /// with an other instance. - /// The value is true only and only if is present, - /// and it's coming from an internal source managed by ImageSharp (). - /// - public bool HasSwappableContents { get; } - - public Span GetSpan() => this.Memory.Span; - - public void Clear() => this.Memory.Span.Clear(); - - /// - /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), - /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! - /// - public static void SwapOrCopyContent(ref MemorySource destination, ref MemorySource source) - { - if (source.HasSwappableContents && destination.HasSwappableContents) - { - SwapContents(ref destination, ref source); - } - else - { - if (destination.Memory.Length != source.Memory.Length) - { - throw new InvalidOperationException("SwapOrCopyContents(): buffers should both owned or the same size!"); - } - - source.Memory.CopyTo(destination.Memory); - } - } - - /// - public void Dispose() - { - this.MemoryOwner?.Dispose(); - } - - private static void SwapContents(ref MemorySource a, ref MemorySource b) - { - IMemoryOwner tempOwner = a.MemoryOwner; - Memory tempMemory = a.Memory; - - a.MemoryOwner = b.MemoryOwner; - a.Memory = b.Memory; - - b.MemoryOwner = tempOwner; - b.Memory = tempMemory; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs new file mode 100644 index 000000000..31825b7b4 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsDelegate{T}.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); +} diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs new file mode 100644 index 000000000..023606f52 --- /dev/null +++ b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Memory +{ + internal delegate void TransformItemsInplaceDelegate(Span data); +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs index c7112c47a..c58b224e4 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifConstants.cs @@ -1,11 +1,13 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal static class ExifConstants { - public static readonly byte[] LittleEndianByteOrderMarker = + public static ReadOnlySpan LittleEndianByteOrderMarker => new byte[] { (byte)'I', (byte)'I', @@ -13,7 +15,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif 0x00, }; - public static readonly byte[] BigEndianByteOrderMarker = + public static ReadOnlySpan BigEndianByteOrderMarker => new byte[] { (byte)'M', (byte)'M', @@ -21,4 +23,4 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif 0x2A }; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index c2a731825..11d0bd01b 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// The . /// public Image CreateThumbnail() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.InitializeValues(); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index c068461b2..b00813730 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif int i = 0; // The byte order marker for little-endian, followed by the number 42 and a 0 - ExifConstants.LittleEndianByteOrderMarker.AsSpan().CopyTo(result.AsSpan(start: i)); + ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); i += ExifConstants.LittleEndianByteOrderMarker.Length; uint ifdOffset = ((uint)i - startIndex) + 4U; diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index a0ee1d5e5..a30e45dde 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc else { // The type is not know, so the values need be read - var values = new double[channelCount][]; + double[][] values = new double[channelCount][]; for (int i = 0; i < channelCount; i++) { values[i] = new double[] { this.ReadUFix16(), this.ReadUFix16() }; @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc return new IccCurveTagDataEntry(this.ReadUFix8()); } - var cdata = new float[pointCount]; + float[] cdata = new float[pointCount]; for (int i = 0; i < pointCount; i++) { cdata[i] = this.ReadUInt16() / 65535f; @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc // Input LUT var inValues = new IccLut[inChCount]; - var gridPointCount = new byte[inChCount]; + byte[] gridPointCount = new byte[inChCount]; for (int i = 0; i < inChCount; i++) { inValues[i] = this.ReadLut16(inTableCount); @@ -299,7 +299,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc // Input LUT var inValues = new IccLut[inChCount]; - var gridPointCount = new byte[inChCount]; + byte[] gridPointCount = new byte[inChCount]; for (int i = 0; i < inChCount; i++) { inValues[i] = this.ReadLut8(); @@ -464,8 +464,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc var text = new IccLocalizedString[recordCount]; var culture = new CultureInfo[recordCount]; - var length = new uint[recordCount]; - var offset = new uint[recordCount]; + uint[] length = new uint[recordCount]; + uint[] offset = new uint[recordCount]; for (int i = 0; i < recordCount; i++) { @@ -627,7 +627,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc ushort channelCount = this.ReadUInt16(); ushort measurementCount = this.ReadUInt16(); - var offset = new uint[measurementCount]; + uint[] offset = new uint[measurementCount]; for (int i = 0; i < measurementCount; i++) { offset[i] = this.ReadUInt32(); @@ -651,7 +651,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc public IccFix16ArrayTagDataEntry ReadFix16ArrayTagDataEntry(uint size) { uint count = (size - 8) / 4; - var arrayData = new float[count]; + float[] arrayData = new float[count]; for (int i = 0; i < count; i++) { arrayData[i] = this.ReadFix16() / 256f; @@ -687,7 +687,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc public IccUFix16ArrayTagDataEntry ReadUFix16ArrayTagDataEntry(uint size) { uint count = (size - 8) / 4; - var arrayData = new float[count]; + float[] arrayData = new float[count]; for (int i = 0; i < count; i++) { arrayData[i] = this.ReadUFix16(); @@ -704,7 +704,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc public IccUInt16ArrayTagDataEntry ReadUInt16ArrayTagDataEntry(uint size) { uint count = (size - 8) / 2; - var arrayData = new ushort[count]; + ushort[] arrayData = new ushort[count]; for (int i = 0; i < count; i++) { arrayData[i] = this.ReadUInt16(); @@ -721,7 +721,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc public IccUInt32ArrayTagDataEntry ReadUInt32ArrayTagDataEntry(uint size) { uint count = (size - 8) / 4; - var arrayData = new uint[count]; + uint[] arrayData = new uint[count]; for (int i = 0; i < count; i++) { arrayData[i] = this.ReadUInt32(); @@ -738,7 +738,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc public IccUInt64ArrayTagDataEntry ReadUInt64ArrayTagDataEntry(uint size) { uint count = (size - 8) / 8; - var arrayData = new ulong[count]; + ulong[] arrayData = new ulong[count]; for (int i = 0; i < count; i++) { arrayData[i] = this.ReadUInt64(); @@ -878,14 +878,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc public IccUcrBgTagDataEntry ReadUcrBgTagDataEntry(uint size) { uint ucrCount = this.ReadUInt32(); - var ucrCurve = new ushort[ucrCount]; + ushort[] ucrCurve = new ushort[ucrCount]; for (int i = 0; i < ucrCurve.Length; i++) { ucrCurve[i] = this.ReadUInt16(); } uint bgCount = this.ReadUInt32(); - var bgCurve = new ushort[bgCount]; + ushort[] bgCurve = new ushort[bgCount]; for (int i = 0; i < bgCurve.Length; i++) { bgCurve[i] = this.ReadUInt16(); diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs index 88e3c4cae..0dfaef7d4 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs index f8bf3f042..929a70ed8 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs index 505b73a89..1e7d53231 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccXyzTagDataEntry.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Metadata.Profiles.Icc { @@ -53,4 +54,4 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc return this.Equals((IccTagDataEntry)other); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/ColorConstants.cs b/src/ImageSharp/PixelFormats/ColorConstants.cs deleted file mode 100644 index 14df38569..000000000 --- a/src/ImageSharp/PixelFormats/ColorConstants.cs +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides useful color definitions. - /// - public static class ColorConstants - { - /// - /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. - /// - public static readonly Rgba32[] WebSafeColors = - { - Rgba32.AliceBlue, - Rgba32.AntiqueWhite, - Rgba32.Aqua, - Rgba32.Aquamarine, - Rgba32.Azure, - Rgba32.Beige, - Rgba32.Bisque, - Rgba32.Black, - Rgba32.BlanchedAlmond, - Rgba32.Blue, - Rgba32.BlueViolet, - Rgba32.Brown, - Rgba32.BurlyWood, - Rgba32.CadetBlue, - Rgba32.Chartreuse, - Rgba32.Chocolate, - Rgba32.Coral, - Rgba32.CornflowerBlue, - Rgba32.Cornsilk, - Rgba32.Crimson, - Rgba32.Cyan, - Rgba32.DarkBlue, - Rgba32.DarkCyan, - Rgba32.DarkGoldenrod, - Rgba32.DarkGray, - Rgba32.DarkGreen, - Rgba32.DarkKhaki, - Rgba32.DarkMagenta, - Rgba32.DarkOliveGreen, - Rgba32.DarkOrange, - Rgba32.DarkOrchid, - Rgba32.DarkRed, - Rgba32.DarkSalmon, - Rgba32.DarkSeaGreen, - Rgba32.DarkSlateBlue, - Rgba32.DarkSlateGray, - Rgba32.DarkTurquoise, - Rgba32.DarkViolet, - Rgba32.DeepPink, - Rgba32.DeepSkyBlue, - Rgba32.DimGray, - Rgba32.DodgerBlue, - Rgba32.Firebrick, - Rgba32.FloralWhite, - Rgba32.ForestGreen, - Rgba32.Fuchsia, - Rgba32.Gainsboro, - Rgba32.GhostWhite, - Rgba32.Gold, - Rgba32.Goldenrod, - Rgba32.Gray, - Rgba32.Green, - Rgba32.GreenYellow, - Rgba32.Honeydew, - Rgba32.HotPink, - Rgba32.IndianRed, - Rgba32.Indigo, - Rgba32.Ivory, - Rgba32.Khaki, - Rgba32.Lavender, - Rgba32.LavenderBlush, - Rgba32.LawnGreen, - Rgba32.LemonChiffon, - Rgba32.LightBlue, - Rgba32.LightCoral, - Rgba32.LightCyan, - Rgba32.LightGoldenrodYellow, - Rgba32.LightGray, - Rgba32.LightGreen, - Rgba32.LightPink, - Rgba32.LightSalmon, - Rgba32.LightSeaGreen, - Rgba32.LightSkyBlue, - Rgba32.LightSlateGray, - Rgba32.LightSteelBlue, - Rgba32.LightYellow, - Rgba32.Lime, - Rgba32.LimeGreen, - Rgba32.Linen, - Rgba32.Magenta, - Rgba32.Maroon, - Rgba32.MediumAquamarine, - Rgba32.MediumBlue, - Rgba32.MediumOrchid, - Rgba32.MediumPurple, - Rgba32.MediumSeaGreen, - Rgba32.MediumSlateBlue, - Rgba32.MediumSpringGreen, - Rgba32.MediumTurquoise, - Rgba32.MediumVioletRed, - Rgba32.MidnightBlue, - Rgba32.MintCream, - Rgba32.MistyRose, - Rgba32.Moccasin, - Rgba32.NavajoWhite, - Rgba32.Navy, - Rgba32.OldLace, - Rgba32.Olive, - Rgba32.OliveDrab, - Rgba32.Orange, - Rgba32.OrangeRed, - Rgba32.Orchid, - Rgba32.PaleGoldenrod, - Rgba32.PaleGreen, - Rgba32.PaleTurquoise, - Rgba32.PaleVioletRed, - Rgba32.PapayaWhip, - Rgba32.PeachPuff, - Rgba32.Peru, - Rgba32.Pink, - Rgba32.Plum, - Rgba32.PowderBlue, - Rgba32.Purple, - Rgba32.RebeccaPurple, - Rgba32.Red, - Rgba32.RosyBrown, - Rgba32.RoyalBlue, - Rgba32.SaddleBrown, - Rgba32.Salmon, - Rgba32.SandyBrown, - Rgba32.SeaGreen, - Rgba32.SeaShell, - Rgba32.Sienna, - Rgba32.Silver, - Rgba32.SkyBlue, - Rgba32.SlateBlue, - Rgba32.SlateGray, - Rgba32.Snow, - Rgba32.SpringGreen, - Rgba32.SteelBlue, - Rgba32.Tan, - Rgba32.Teal, - Rgba32.Thistle, - Rgba32.Tomato, - Rgba32.Transparent, - Rgba32.Turquoise, - Rgba32.Violet, - Rgba32.Wheat, - Rgba32.White, - Rgba32.WhiteSmoke, - Rgba32.Yellow, - Rgba32.YellowGreen - }; - - /// - /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. - /// The hex codes were collected and defined by Nicholas Rougeux - /// - public static readonly Rgba32[] WernerColors = - { - Rgba32.FromHex("#f1e9cd"), - Rgba32.FromHex("#f2e7cf"), - Rgba32.FromHex("#ece6d0"), - Rgba32.FromHex("#f2eacc"), - Rgba32.FromHex("#f3e9ca"), - Rgba32.FromHex("#f2ebcd"), - Rgba32.FromHex("#e6e1c9"), - Rgba32.FromHex("#e2ddc6"), - Rgba32.FromHex("#cbc8b7"), - Rgba32.FromHex("#bfbbb0"), - Rgba32.FromHex("#bebeb3"), - Rgba32.FromHex("#b7b5ac"), - Rgba32.FromHex("#bab191"), - Rgba32.FromHex("#9c9d9a"), - Rgba32.FromHex("#8a8d84"), - Rgba32.FromHex("#5b5c61"), - Rgba32.FromHex("#555152"), - Rgba32.FromHex("#413f44"), - Rgba32.FromHex("#454445"), - Rgba32.FromHex("#423937"), - Rgba32.FromHex("#433635"), - Rgba32.FromHex("#252024"), - Rgba32.FromHex("#241f20"), - Rgba32.FromHex("#281f3f"), - Rgba32.FromHex("#1c1949"), - Rgba32.FromHex("#4f638d"), - Rgba32.FromHex("#383867"), - Rgba32.FromHex("#5c6b8f"), - Rgba32.FromHex("#657abb"), - Rgba32.FromHex("#6f88af"), - Rgba32.FromHex("#7994b5"), - Rgba32.FromHex("#6fb5a8"), - Rgba32.FromHex("#719ba2"), - Rgba32.FromHex("#8aa1a6"), - Rgba32.FromHex("#d0d5d3"), - Rgba32.FromHex("#8590ae"), - Rgba32.FromHex("#3a2f52"), - Rgba32.FromHex("#39334a"), - Rgba32.FromHex("#6c6d94"), - Rgba32.FromHex("#584c77"), - Rgba32.FromHex("#533552"), - Rgba32.FromHex("#463759"), - Rgba32.FromHex("#bfbac0"), - Rgba32.FromHex("#77747f"), - Rgba32.FromHex("#4a475c"), - Rgba32.FromHex("#b8bfaf"), - Rgba32.FromHex("#b2b599"), - Rgba32.FromHex("#979c84"), - Rgba32.FromHex("#5d6161"), - Rgba32.FromHex("#61ac86"), - Rgba32.FromHex("#a4b6a7"), - Rgba32.FromHex("#adba98"), - Rgba32.FromHex("#93b778"), - Rgba32.FromHex("#7d8c55"), - Rgba32.FromHex("#33431e"), - Rgba32.FromHex("#7c8635"), - Rgba32.FromHex("#8e9849"), - Rgba32.FromHex("#c2c190"), - Rgba32.FromHex("#67765b"), - Rgba32.FromHex("#ab924b"), - Rgba32.FromHex("#c8c76f"), - Rgba32.FromHex("#ccc050"), - Rgba32.FromHex("#ebdd99"), - Rgba32.FromHex("#ab9649"), - Rgba32.FromHex("#dbc364"), - Rgba32.FromHex("#e6d058"), - Rgba32.FromHex("#ead665"), - Rgba32.FromHex("#d09b2c"), - Rgba32.FromHex("#a36629"), - Rgba32.FromHex("#a77d35"), - Rgba32.FromHex("#f0d696"), - Rgba32.FromHex("#d7c485"), - Rgba32.FromHex("#f1d28c"), - Rgba32.FromHex("#efcc83"), - Rgba32.FromHex("#f3daa7"), - Rgba32.FromHex("#dfa837"), - Rgba32.FromHex("#ebbc71"), - Rgba32.FromHex("#d17c3f"), - Rgba32.FromHex("#92462f"), - Rgba32.FromHex("#be7249"), - Rgba32.FromHex("#bb603c"), - Rgba32.FromHex("#c76b4a"), - Rgba32.FromHex("#a75536"), - Rgba32.FromHex("#b63e36"), - Rgba32.FromHex("#b5493a"), - Rgba32.FromHex("#cd6d57"), - Rgba32.FromHex("#711518"), - Rgba32.FromHex("#e9c49d"), - Rgba32.FromHex("#eedac3"), - Rgba32.FromHex("#eecfbf"), - Rgba32.FromHex("#ce536b"), - Rgba32.FromHex("#b74a70"), - Rgba32.FromHex("#b7757c"), - Rgba32.FromHex("#612741"), - Rgba32.FromHex("#7a4848"), - Rgba32.FromHex("#3f3033"), - Rgba32.FromHex("#8d746f"), - Rgba32.FromHex("#4d3635"), - Rgba32.FromHex("#6e3b31"), - Rgba32.FromHex("#864735"), - Rgba32.FromHex("#553d3a"), - Rgba32.FromHex("#613936"), - Rgba32.FromHex("#7a4b3a"), - Rgba32.FromHex("#946943"), - Rgba32.FromHex("#c39e6d"), - Rgba32.FromHex("#513e32"), - Rgba32.FromHex("#8b7859"), - Rgba32.FromHex("#9b856b"), - Rgba32.FromHex("#766051"), - Rgba32.FromHex("#453b32") - }; - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 61adedb0d..6d1c03e4b 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The type implementing this interface public interface IPixel : IPixel, IEquatable - where TSelf : struct, IPixel + where TSelf : unmanaged, IPixel { /// /// Creates a instance for this pixel type. diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index 4075b664c..f966de63c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// to be opaque /// internal static class DefaultPixelBlenders - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index ccb98c495..a882de066 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// to be opaque /// internal static class DefaultPixelBlenders - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { <# diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 4616ce36c..8184f1577 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -13,7 +13,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders - /// /// Returns the result of the "NormalSrc" compositing equation. /// @@ -202,7 +201,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -221,7 +220,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -240,7 +239,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -259,7 +258,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -278,7 +277,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -297,7 +296,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -316,7 +315,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -335,7 +334,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -354,7 +353,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -373,7 +372,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -392,7 +391,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -411,7 +410,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel NormalXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -419,7 +418,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "MultiplySrc" compositing equation. /// @@ -608,7 +606,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -627,7 +625,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -646,7 +644,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -665,7 +663,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -684,7 +682,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -703,7 +701,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -722,7 +720,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -741,7 +739,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -760,7 +758,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -779,7 +777,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -798,7 +796,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -817,7 +815,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel MultiplyXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -825,7 +823,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "AddSrc" compositing equation. /// @@ -1014,7 +1011,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1033,7 +1030,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1052,7 +1049,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1071,7 +1068,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1090,7 +1087,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1109,7 +1106,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1128,7 +1125,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1147,7 +1144,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1166,7 +1163,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1185,7 +1182,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1204,7 +1201,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1223,7 +1220,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel AddXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1231,7 +1228,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "SubtractSrc" compositing equation. /// @@ -1420,7 +1416,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1439,7 +1435,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1458,7 +1454,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1477,7 +1473,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1496,7 +1492,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1515,7 +1511,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1534,7 +1530,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1553,7 +1549,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1572,7 +1568,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1591,7 +1587,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1610,7 +1606,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1629,7 +1625,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel SubtractXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1637,7 +1633,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "ScreenSrc" compositing equation. /// @@ -1826,7 +1821,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1845,7 +1840,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1864,7 +1859,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1883,7 +1878,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1902,7 +1897,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1921,7 +1916,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1940,7 +1935,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1959,7 +1954,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1978,7 +1973,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -1997,7 +1992,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2016,7 +2011,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2035,7 +2030,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel ScreenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2043,7 +2038,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "DarkenSrc" compositing equation. /// @@ -2232,7 +2226,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2251,7 +2245,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2270,7 +2264,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2289,7 +2283,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2308,7 +2302,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2327,7 +2321,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2346,7 +2340,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2365,7 +2359,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2384,7 +2378,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2403,7 +2397,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2422,7 +2416,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2441,7 +2435,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel DarkenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2449,7 +2443,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "LightenSrc" compositing equation. /// @@ -2638,7 +2631,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2657,7 +2650,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2676,7 +2669,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2695,7 +2688,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2714,7 +2707,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2733,7 +2726,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2752,7 +2745,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2771,7 +2764,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2790,7 +2783,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2809,7 +2802,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2828,7 +2821,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2847,7 +2840,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel LightenXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -2855,7 +2848,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "OverlaySrc" compositing equation. /// @@ -3044,7 +3036,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3063,7 +3055,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3082,7 +3074,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3101,7 +3093,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3120,7 +3112,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlaySrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3139,7 +3131,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3158,7 +3150,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3177,7 +3169,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3196,7 +3188,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3215,7 +3207,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3234,7 +3226,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3253,7 +3245,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel OverlayXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3261,7 +3253,6 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders return dest; } - /// /// Returns the result of the "HardLightSrc" compositing equation. /// @@ -3450,7 +3441,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3469,7 +3460,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3488,7 +3479,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3507,7 +3498,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3526,7 +3517,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightSrcOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3545,7 +3536,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDest(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3564,7 +3555,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestAtop(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3583,7 +3574,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestOver(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3602,7 +3593,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestIn(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3621,7 +3612,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightDestOut(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3640,7 +3631,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightClear(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; @@ -3659,7 +3650,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel HardLightXor(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index bc4fa17c2..be2beb2f8 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TPixel <#=blender#><#=composer#>(TPixel backdrop, TPixel source, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { opacity = opacity.Clamp(0, 1); TPixel dest = default; diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index c64fd3a2d..244aba7de 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The type of the pixel public abstract class PixelBlender - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Blend 2 pixels together. @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.PixelFormats ReadOnlySpan background, ReadOnlySpan source, float amount) - where TPixelSrc : struct, IPixel + where TPixelSrc : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.PixelFormats ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - where TPixelSrc : struct, IPixel + where TPixelSrc : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index cf55a2245..444221d88 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(A8 left, A8 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F); + public readonly Vector4 ToVector4() => new Vector4(0, 0, 0, this.PackedValue / 255F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The object to compare. /// True if the object is equal to the packed vector. - public override bool Equals(object obj) => obj is A8 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is A8 other && this.Equals(other); /// /// Compares another A8 packed vector with the packed vector. @@ -144,17 +144,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// The A8 packed vector to compare. /// True if the packed vectors are equal. [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(A8 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(A8 other) => this.PackedValue.Equals(other.PackedValue); /// /// Gets a string representation of the packed vector. /// /// A string representation of the packed vector. - public override string ToString() => $"A8({this.PackedValue})"; + public override readonly string ToString() => $"A8({this.PackedValue})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// /// Packs a into a byte. diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index 4dc5c9fb5..52f6bcaa1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Argb { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -138,7 +138,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => this.Argb; + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => this.Argb; + + [MethodImpl(InliningOptions.ShortMethod)] set => this.Argb = value; } @@ -181,7 +184,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Argb32 left, Argb32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -189,7 +192,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -197,7 +200,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -320,21 +323,21 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); + public override readonly bool Equals(object obj) => obj is Argb32 argb32 && this.Equals(argb32); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Argb32 other) => this.Argb == other.Argb; + public readonly bool Equals(Argb32 other) => this.Argb == other.Argb; /// /// Gets a string representation of the packed vector. /// /// A string representation of the packed vector. - public override string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Argb({this.A}, {this.R}, {this.G}, {this.B})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.Argb.GetHashCode(); + public override readonly int GetHashCode() => this.Argb.GetHashCode(); /// /// Packs the four floats into a color. @@ -370,7 +373,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 1cd0b8027..0a2f58409 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgr24 left, Bgr24 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -219,16 +219,16 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Bgr24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// - public override bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgr24 other && this.Equals(other); /// - public override string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; + public override readonly string ToString() => $"Bgra({this.B}, {this.G}, {this.R})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 4a7bbded9..2659689bd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgr565 left, Bgr565 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector3(), 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector3(), 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector3 ToVector3() + public readonly Vector3 ToVector3() { return new Vector3( ((this.PackedValue >> 11) & 0x1F) * (1F / 31F), @@ -153,14 +153,14 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgr565 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgr565 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector3(); return FormattableString.Invariant($"Bgr565({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##})"); @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector3 vector) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index e4ae35c26..40c187eb2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Bgra { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => this.Bgra; + readonly get => this.Bgra; set => this.Bgra = value; } @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra32 left, Bgra32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -276,16 +276,16 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgra32 other && this.Equals(other); /// - public bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); + public readonly bool Equals(Bgra32 other) => this.Bgra.Equals(other.Bgra); /// - public override int GetHashCode() => this.Bgra.GetHashCode(); + public override readonly int GetHashCode() => this.Bgra.GetHashCode(); /// - public override string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; + public override readonly string ToString() => $"Bgra32({this.B}, {this.G}, {this.R}, {this.A})"; /// /// Packs a into a color. @@ -296,7 +296,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index f4479603f..bbbf9145c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra4444 left, Bgra4444 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float Max = 1 / 15F; @@ -142,14 +142,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Bgra4444 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgra4444 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Bgra4444({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); @@ -157,12 +157,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); return (ushort)((((int)Math.Round(vector.W * 15F) & 0x0F) << 12) | (((int)Math.Round(vector.X * 15F) & 0x0F) << 8) | (((int)Math.Round(vector.Y * 15F) & 0x0F) << 4) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index b3d7015cf..d10d10b47 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Bgra5551 left, Bgra5551 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( ((this.PackedValue >> 10) & 0x1F) / 31F, @@ -147,10 +147,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Bgra5551 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Bgra5551({vector.Z:#0.##}, {vector.Y:#0.##}, {vector.X:#0.##}, {vector.W:#0.##})"); @@ -158,12 +158,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static ushort Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); return (ushort)( (((int)Math.Round(vector.X * 31F) & 0x1F) << 10) | (((int)Math.Round(vector.Y * 31F) & 0x1F) << 5) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 6583670f1..49b4f4138 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Byte4 left, Byte4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4() / 255F; + public readonly Vector4 ToScaledVector4() => this.ToVector4() / 255F; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( this.PackedValue & 0xFF, @@ -143,18 +143,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); + public override readonly bool Equals(object obj) => obj is Byte4 byte4 && this.Equals(byte4); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Byte4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Byte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.PixelFormats const float Max = 255F; // Clamp the value between min and max values - vector = Vector4.Clamp(vector, Vector4.Zero, new Vector4(Max)); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, new Vector4(Max)); uint byte4 = (uint)Math.Round(vector.X) & 0xFF; uint byte3 = ((uint)Math.Round(vector.Y) & 0xFF) << 0x8; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index 4d6c4985a..977df78b8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { float single = this.ToSingle() + 1F; single /= 2F; @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToSingle(), 0, 0, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -136,20 +136,20 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); + public readonly float ToSingle() => HalfTypeHelper.Unpack(this.PackedValue); /// - public override bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is HalfSingle other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(HalfSingle other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); + public override readonly string ToString() => FormattableString.Invariant($"HalfSingle({this.ToSingle():#0.##})"); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 300458cb2..1ecaa05da 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(HalfVector2 left, HalfVector2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { var vector = this.ToVector2(); return new Vector4(vector.X, vector.Y, 0F, 1F); @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { Vector2 vector; vector.X = HalfTypeHelper.Unpack((ushort)this.PackedValue); @@ -156,14 +156,14 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is HalfVector2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(HalfVector2 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"HalfVector2({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(float x, float y) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 5ccc09e9f..35822779f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(HalfVector4 left, HalfVector4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( HalfTypeHelper.Unpack((ushort)this.PackedValue), @@ -151,14 +151,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is HalfVector4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(HalfVector4 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"HalfVector4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// /// Packs a into a . diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index cbe34745c..815ae6a4e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(L16 left, L16 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { float scaled = this.PackedValue / Max; return new Vector4(scaled, scaled, scaled, 1F); @@ -160,23 +160,23 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.PackedValue = ImageMaths.Get16BitBT709Luminance(source.R, source.G, source.B); /// - public override bool Equals(object obj) => obj is L16 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is L16 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(L16 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => $"L16({this.PackedValue})"; + public override readonly string ToString() => $"L16({this.PackedValue})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] internal void ConvertFromRgbaScaledVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.PackedValue = ImageMaths.Get16BitBT709Luminance( vector.X, vector.Y, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs index b4911ec1c..37a028db2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(L8 left, L8 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { float rgb = this.PackedValue / 255F; return new Vector4(rgb, rgb, rgb, 1F); @@ -138,25 +138,25 @@ namespace SixLabors.ImageSharp.PixelFormats ImageMaths.DownScaleFrom16BitTo8Bit(source.B)); /// - public override bool Equals(object obj) => obj is L8 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is L8 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(L8 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => $"L8({this.PackedValue})"; + public override readonly string ToString() => $"L8({this.PackedValue})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] internal void ConvertFromRgbaScaledVector4(Vector4 vector) { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.PackedValue = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs index 2ab5da158..104c2be45 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -45,8 +45,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// public ushort PackedValue { - get => Unsafe.As(ref this); - + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); set => Unsafe.As(ref this) = value; } @@ -73,21 +72,21 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(La16 left, La16 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(La16 other) => this.PackedValue.Equals(other.PackedValue); /// - public override bool Equals(object obj) => obj is La16 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is La16 other && this.Equals(other); /// - public override string ToString() => $"La16({this.L}, {this.A})"; + public override readonly string ToString() => $"La16({this.L}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -204,11 +203,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float Max = 255F; float rgb = this.L / Max; @@ -220,7 +219,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.L = ImageMaths.Get8BitBT709Luminance((byte)vector.X, (byte)vector.Y, (byte)vector.Z); this.A = (byte)vector.W; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs index b13c43827..98a6cdae4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -45,8 +45,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// public uint PackedValue { - get => Unsafe.As(ref this); + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; } @@ -73,21 +75,21 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(La32 left, La32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(La32 other) => this.PackedValue.Equals(other.PackedValue); /// - public override bool Equals(object obj) => obj is La32 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is La32 other && this.Equals(other); /// - public override string ToString() => $"La32({this.L}, {this.A})"; + public override readonly string ToString() => $"La32({this.L}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -218,11 +220,11 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { float scaled = this.L / Max; return new Vector4(scaled, scaled, scaled, this.A / Max); @@ -231,7 +233,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] internal void ConvertFromRgbaScaledVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.L = ImageMaths.Get16BitBT709Luminance( vector.X, vector.Y, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index d6362dacc..54effcb22 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -60,20 +60,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedByte2 left, NormalizedByte2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - var scaled = new Vector2(vector.X, vector.Y) * 2F; + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; scaled -= Vector2.One; this.PackedValue = Pack(scaled); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { return new Vector2( (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, @@ -159,18 +159,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedByte2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedByte2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"NormalizedByte2({vector.X:#0.##}, {vector.Y:#0.##})"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index f6c5d2580..a7b350d55 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedByte4 left, NormalizedByte4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (sbyte)((this.PackedValue >> 0) & 0xFF) / 127F, @@ -154,18 +154,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedByte4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedByte4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"NormalizedByte4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, MinusOne, Vector4.One) * Half; + vector = Vector4Utilities.FastClamp(vector, MinusOne, Vector4.One) * Half; uint byte4 = ((uint)MathF.Round(vector.X) & 0xFF) << 0; uint byte3 = ((uint)MathF.Round(vector.Y) & 0xFF) << 8; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 989c03e22..6be347bcc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -60,20 +60,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedShort2 left, NormalizedShort2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - var scaled = new Vector2(vector.X, vector.Y) * 2F; + Vector2 scaled = new Vector2(vector.X, vector.Y) * 2F; scaled -= Vector2.One; this.PackedValue = Pack(scaled); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += Vector2.One; @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0, 1); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() + public readonly Vector2 ToVector2() { const float MaxVal = 0x7FFF; @@ -164,18 +164,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedShort2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedShort2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"NormalizedShort2({vector.X:#0.##}, {vector.Y:#0.##})"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index ed849a6c7..59433f17e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(NormalizedShort4 left, NormalizedShort4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += Vector4.One; @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { const float MaxVal = 0x7FFF; @@ -156,18 +156,18 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is NormalizedShort4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(NormalizedShort4 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"NormalizedShort4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.PixelFormats private static ulong Pack(ref Vector4 vector) { vector *= Max; - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); // Round rather than truncate. ulong word4 = ((ulong)MathF.Round(vector.X) & 0xFFFF) << 0x00; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index a7385d5af..60c401003 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rg32 left, Rg32 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.ToVector2(), 0F, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -142,17 +142,17 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; + public readonly Vector2 ToVector2() => new Vector2(this.PackedValue & 0xFFFF, (this.PackedValue >> 16) & 0xFFFF) / Max; /// - public override bool Equals(object obj) => obj is Rg32 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rg32 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Rg32 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"Rg32({vector.X:#0.##}, {vector.Y:#0.##})"); @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(Vector2 vector) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 65191e86f..6e4839fed 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgb24 left, Rgb24 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); + public readonly Vector4 ToVector4() => new Rgba32(this.R, this.G, this.B, byte.MaxValue).ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -232,18 +232,18 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rgb24 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgb24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Rgb24 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.B, this.G); /// - public override string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Rgb24({this.R}, {this.G}, {this.B})"; /// /// Packs a into a color. @@ -254,7 +254,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index c78219a48..dff8fe83f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgb48 left, Rgb48 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -79,13 +79,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.R = (ushort)MathF.Round(vector.X); this.G = (ushort)MathF.Round(vector.Y); this.B = (ushort)MathF.Round(vector.Z); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); + public readonly Vector4 ToVector4() => new Vector4(this.R / Max, this.G / Max, this.B / Max, 1F); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -201,17 +201,17 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgb48(Rgb48 source) => this = source; /// - public override bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); + public override readonly bool Equals(object obj) => obj is Rgb48 rgb48 && this.Equals(rgb48); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); + public readonly bool Equals(Rgb48 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); /// - public override string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; + public override readonly string ToString() => $"Rgb48({this.R}, {this.G}, {this.B})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index 330f5a8ee..7ca47f838 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba1010102 left, Rgba1010102 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (this.PackedValue >> 0) & 0x03FF, @@ -143,14 +143,14 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Rgba1010102 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; + public readonly bool Equals(Rgba1010102 other) => this.PackedValue == other.PackedValue; /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Rgba1010102({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -158,12 +158,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); [MethodImpl(InliningOptions.ShortMethod)] private static uint Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Multiplier; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Multiplier; return (uint)( (((int)Math.Round(vector.X) & 0x03FF) << 0) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs deleted file mode 100644 index f9cc3256c..000000000 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.Definitions.cs +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.PixelFormats -{ - /// - /// Provides standardized definitions for named colors. - /// - public partial struct Rgba32 - { - /// - /// Represents a matching the W3C definition that has an hex value of #F0F8FF. - /// - public static readonly Rgba32 AliceBlue = Color.AliceBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAEBD7. - /// - public static readonly Rgba32 AntiqueWhite = Color.AntiqueWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Rgba32 Aqua = Color.Aqua; - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFFD4. - /// - public static readonly Rgba32 Aquamarine = Color.Aquamarine; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFFF. - /// - public static readonly Rgba32 Azure = Color.Azure; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5DC. - /// - public static readonly Rgba32 Beige = Color.Beige; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4C4. - /// - public static readonly Rgba32 Bisque = Color.Bisque; - - /// - /// Represents a matching the W3C definition that has an hex value of #000000. - /// - public static readonly Rgba32 Black = Color.Black; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEBCD. - /// - public static readonly Rgba32 BlanchedAlmond = Color.BlanchedAlmond; - - /// - /// Represents a matching the W3C definition that has an hex value of #0000FF. - /// - public static readonly Rgba32 Blue = Color.Blue; - - /// - /// Represents a matching the W3C definition that has an hex value of #8A2BE2. - /// - public static readonly Rgba32 BlueViolet = Color.BlueViolet; - - /// - /// Represents a matching the W3C definition that has an hex value of #A52A2A. - /// - public static readonly Rgba32 Brown = Color.Brown; - - /// - /// Represents a matching the W3C definition that has an hex value of #DEB887. - /// - public static readonly Rgba32 BurlyWood = Color.BurlyWood; - - /// - /// Represents a matching the W3C definition that has an hex value of #5F9EA0. - /// - public static readonly Rgba32 CadetBlue = Color.CadetBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #7FFF00. - /// - public static readonly Rgba32 Chartreuse = Color.Chartreuse; - - /// - /// Represents a matching the W3C definition that has an hex value of #D2691E. - /// - public static readonly Rgba32 Chocolate = Color.Chocolate; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF7F50. - /// - public static readonly Rgba32 Coral = Color.Coral; - - /// - /// Represents a matching the W3C definition that has an hex value of #6495ED. - /// - public static readonly Rgba32 CornflowerBlue = Color.CornflowerBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF8DC. - /// - public static readonly Rgba32 Cornsilk = Color.Cornsilk; - - /// - /// Represents a matching the W3C definition that has an hex value of #DC143C. - /// - public static readonly Rgba32 Crimson = Color.Crimson; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FFFF. - /// - public static readonly Rgba32 Cyan = Color.Cyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #00008B. - /// - public static readonly Rgba32 DarkBlue = Color.DarkBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #008B8B. - /// - public static readonly Rgba32 DarkCyan = Color.DarkCyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #B8860B. - /// - public static readonly Rgba32 DarkGoldenrod = Color.DarkGoldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #A9A9A9. - /// - public static readonly Rgba32 DarkGray = Color.DarkGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #006400. - /// - public static readonly Rgba32 DarkGreen = Color.DarkGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #BDB76B. - /// - public static readonly Rgba32 DarkKhaki = Color.DarkKhaki; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B008B. - /// - public static readonly Rgba32 DarkMagenta = Color.DarkMagenta; - - /// - /// Represents a matching the W3C definition that has an hex value of #556B2F. - /// - public static readonly Rgba32 DarkOliveGreen = Color.DarkOliveGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF8C00. - /// - public static readonly Rgba32 DarkOrange = Color.DarkOrange; - - /// - /// Represents a matching the W3C definition that has an hex value of #9932CC. - /// - public static readonly Rgba32 DarkOrchid = Color.DarkOrchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B0000. - /// - public static readonly Rgba32 DarkRed = Color.DarkRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #E9967A. - /// - public static readonly Rgba32 DarkSalmon = Color.DarkSalmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #8FBC8B. - /// - public static readonly Rgba32 DarkSeaGreen = Color.DarkSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #483D8B. - /// - public static readonly Rgba32 DarkSlateBlue = Color.DarkSlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #2F4F4F. - /// - public static readonly Rgba32 DarkSlateGray = Color.DarkSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #00CED1. - /// - public static readonly Rgba32 DarkTurquoise = Color.DarkTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #9400D3. - /// - public static readonly Rgba32 DarkViolet = Color.DarkViolet; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF1493. - /// - public static readonly Rgba32 DeepPink = Color.DeepPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #00BFFF. - /// - public static readonly Rgba32 DeepSkyBlue = Color.DeepSkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #696969. - /// - public static readonly Rgba32 DimGray = Color.DimGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #1E90FF. - /// - public static readonly Rgba32 DodgerBlue = Color.DodgerBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #B22222. - /// - public static readonly Rgba32 Firebrick = Color.Firebrick; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAF0. - /// - public static readonly Rgba32 FloralWhite = Color.FloralWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #228B22. - /// - public static readonly Rgba32 ForestGreen = Color.ForestGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Rgba32 Fuchsia = Color.Fuchsia; - - /// - /// Represents a matching the W3C definition that has an hex value of #DCDCDC. - /// - public static readonly Rgba32 Gainsboro = Color.Gainsboro; - - /// - /// Represents a matching the W3C definition that has an hex value of #F8F8FF. - /// - public static readonly Rgba32 GhostWhite = Color.GhostWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFD700. - /// - public static readonly Rgba32 Gold = Color.Gold; - - /// - /// Represents a matching the W3C definition that has an hex value of #DAA520. - /// - public static readonly Rgba32 Goldenrod = Color.Goldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #808080. - /// - public static readonly Rgba32 Gray = Color.Gray; - - /// - /// Represents a matching the W3C definition that has an hex value of #008000. - /// - public static readonly Rgba32 Green = Color.Green; - - /// - /// Represents a matching the W3C definition that has an hex value of #ADFF2F. - /// - public static readonly Rgba32 GreenYellow = Color.GreenYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0FFF0. - /// - public static readonly Rgba32 Honeydew = Color.Honeydew; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF69B4. - /// - public static readonly Rgba32 HotPink = Color.HotPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #CD5C5C. - /// - public static readonly Rgba32 IndianRed = Color.IndianRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #4B0082. - /// - public static readonly Rgba32 Indigo = Color.Indigo; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFF0. - /// - public static readonly Rgba32 Ivory = Color.Ivory; - - /// - /// Represents a matching the W3C definition that has an hex value of #F0E68C. - /// - public static readonly Rgba32 Khaki = Color.Khaki; - - /// - /// Represents a matching the W3C definition that has an hex value of #E6E6FA. - /// - public static readonly Rgba32 Lavender = Color.Lavender; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF0F5. - /// - public static readonly Rgba32 LavenderBlush = Color.LavenderBlush; - - /// - /// Represents a matching the W3C definition that has an hex value of #7CFC00. - /// - public static readonly Rgba32 LawnGreen = Color.LawnGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFACD. - /// - public static readonly Rgba32 LemonChiffon = Color.LemonChiffon; - - /// - /// Represents a matching the W3C definition that has an hex value of #ADD8E6. - /// - public static readonly Rgba32 LightBlue = Color.LightBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #F08080. - /// - public static readonly Rgba32 LightCoral = Color.LightCoral; - - /// - /// Represents a matching the W3C definition that has an hex value of #E0FFFF. - /// - public static readonly Rgba32 LightCyan = Color.LightCyan; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAFAD2. - /// - public static readonly Rgba32 LightGoldenrodYellow = Color.LightGoldenrodYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #D3D3D3. - /// - public static readonly Rgba32 LightGray = Color.LightGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #90EE90. - /// - public static readonly Rgba32 LightGreen = Color.LightGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFB6C1. - /// - public static readonly Rgba32 LightPink = Color.LightPink; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA07A. - /// - public static readonly Rgba32 LightSalmon = Color.LightSalmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #20B2AA. - /// - public static readonly Rgba32 LightSeaGreen = Color.LightSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEFA. - /// - public static readonly Rgba32 LightSkyBlue = Color.LightSkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #778899. - /// - public static readonly Rgba32 LightSlateGray = Color.LightSlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0C4DE. - /// - public static readonly Rgba32 LightSteelBlue = Color.LightSteelBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFE0. - /// - public static readonly Rgba32 LightYellow = Color.LightYellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF00. - /// - public static readonly Rgba32 Lime = Color.Lime; - - /// - /// Represents a matching the W3C definition that has an hex value of #32CD32. - /// - public static readonly Rgba32 LimeGreen = Color.LimeGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FAF0E6. - /// - public static readonly Rgba32 Linen = Color.Linen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF00FF. - /// - public static readonly Rgba32 Magenta = Color.Magenta; - - /// - /// Represents a matching the W3C definition that has an hex value of #800000. - /// - public static readonly Rgba32 Maroon = Color.Maroon; - - /// - /// Represents a matching the W3C definition that has an hex value of #66CDAA. - /// - public static readonly Rgba32 MediumAquamarine = Color.MediumAquamarine; - - /// - /// Represents a matching the W3C definition that has an hex value of #0000CD. - /// - public static readonly Rgba32 MediumBlue = Color.MediumBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #BA55D3. - /// - public static readonly Rgba32 MediumOrchid = Color.MediumOrchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #9370DB. - /// - public static readonly Rgba32 MediumPurple = Color.MediumPurple; - - /// - /// Represents a matching the W3C definition that has an hex value of #3CB371. - /// - public static readonly Rgba32 MediumSeaGreen = Color.MediumSeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #7B68EE. - /// - public static readonly Rgba32 MediumSlateBlue = Color.MediumSlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FA9A. - /// - public static readonly Rgba32 MediumSpringGreen = Color.MediumSpringGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #48D1CC. - /// - public static readonly Rgba32 MediumTurquoise = Color.MediumTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #C71585. - /// - public static readonly Rgba32 MediumVioletRed = Color.MediumVioletRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #191970. - /// - public static readonly Rgba32 MidnightBlue = Color.MidnightBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5FFFA. - /// - public static readonly Rgba32 MintCream = Color.MintCream; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4E1. - /// - public static readonly Rgba32 MistyRose = Color.MistyRose; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFE4B5. - /// - public static readonly Rgba32 Moccasin = Color.Moccasin; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDEAD. - /// - public static readonly Rgba32 NavajoWhite = Color.NavajoWhite; - - /// - /// Represents a matching the W3C definition that has an hex value of #000080. - /// - public static readonly Rgba32 Navy = Color.Navy; - - /// - /// Represents a matching the W3C definition that has an hex value of #FDF5E6. - /// - public static readonly Rgba32 OldLace = Color.OldLace; - - /// - /// Represents a matching the W3C definition that has an hex value of #808000. - /// - public static readonly Rgba32 Olive = Color.Olive; - - /// - /// Represents a matching the W3C definition that has an hex value of #6B8E23. - /// - public static readonly Rgba32 OliveDrab = Color.OliveDrab; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFA500. - /// - public static readonly Rgba32 Orange = Color.Orange; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF4500. - /// - public static readonly Rgba32 OrangeRed = Color.OrangeRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #DA70D6. - /// - public static readonly Rgba32 Orchid = Color.Orchid; - - /// - /// Represents a matching the W3C definition that has an hex value of #EEE8AA. - /// - public static readonly Rgba32 PaleGoldenrod = Color.PaleGoldenrod; - - /// - /// Represents a matching the W3C definition that has an hex value of #98FB98. - /// - public static readonly Rgba32 PaleGreen = Color.PaleGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #AFEEEE. - /// - public static readonly Rgba32 PaleTurquoise = Color.PaleTurquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #DB7093. - /// - public static readonly Rgba32 PaleVioletRed = Color.PaleVioletRed; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFEFD5. - /// - public static readonly Rgba32 PapayaWhip = Color.PapayaWhip; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFDAB9. - /// - public static readonly Rgba32 PeachPuff = Color.PeachPuff; - - /// - /// Represents a matching the W3C definition that has an hex value of #CD853F. - /// - public static readonly Rgba32 Peru = Color.Peru; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFC0CB. - /// - public static readonly Rgba32 Pink = Color.Pink; - - /// - /// Represents a matching the W3C definition that has an hex value of #DDA0DD. - /// - public static readonly Rgba32 Plum = Color.Plum; - - /// - /// Represents a matching the W3C definition that has an hex value of #B0E0E6. - /// - public static readonly Rgba32 PowderBlue = Color.PowderBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #800080. - /// - public static readonly Rgba32 Purple = Color.Purple; - - /// - /// Represents a matching the W3C definition that has an hex value of #663399. - /// - public static readonly Rgba32 RebeccaPurple = Color.RebeccaPurple; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF0000. - /// - public static readonly Rgba32 Red = Color.Red; - - /// - /// Represents a matching the W3C definition that has an hex value of #BC8F8F. - /// - public static readonly Rgba32 RosyBrown = Color.RosyBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #4169E1. - /// - public static readonly Rgba32 RoyalBlue = Color.RoyalBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #8B4513. - /// - public static readonly Rgba32 SaddleBrown = Color.SaddleBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #FA8072. - /// - public static readonly Rgba32 Salmon = Color.Salmon; - - /// - /// Represents a matching the W3C definition that has an hex value of #F4A460. - /// - public static readonly Rgba32 SandyBrown = Color.SandyBrown; - - /// - /// Represents a matching the W3C definition that has an hex value of #2E8B57. - /// - public static readonly Rgba32 SeaGreen = Color.SeaGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFF5EE. - /// - public static readonly Rgba32 SeaShell = Color.SeaShell; - - /// - /// Represents a matching the W3C definition that has an hex value of #A0522D. - /// - public static readonly Rgba32 Sienna = Color.Sienna; - - /// - /// Represents a matching the W3C definition that has an hex value of #C0C0C0. - /// - public static readonly Rgba32 Silver = Color.Silver; - - /// - /// Represents a matching the W3C definition that has an hex value of #87CEEB. - /// - public static readonly Rgba32 SkyBlue = Color.SkyBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #6A5ACD. - /// - public static readonly Rgba32 SlateBlue = Color.SlateBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #708090. - /// - public static readonly Rgba32 SlateGray = Color.SlateGray; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFAFA. - /// - public static readonly Rgba32 Snow = Color.Snow; - - /// - /// Represents a matching the W3C definition that has an hex value of #00FF7F. - /// - public static readonly Rgba32 SpringGreen = Color.SpringGreen; - - /// - /// Represents a matching the W3C definition that has an hex value of #4682B4. - /// - public static readonly Rgba32 SteelBlue = Color.SteelBlue; - - /// - /// Represents a matching the W3C definition that has an hex value of #D2B48C. - /// - public static readonly Rgba32 Tan = Color.Tan; - - /// - /// Represents a matching the W3C definition that has an hex value of #008080. - /// - public static readonly Rgba32 Teal = Color.Teal; - - /// - /// Represents a matching the W3C definition that has an hex value of #D8BFD8. - /// - public static readonly Rgba32 Thistle = Color.Thistle; - - /// - /// Represents a matching the W3C definition that has an hex value of #FF6347. - /// - public static readonly Rgba32 Tomato = Color.Tomato; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Rgba32 Transparent = Color.Transparent; - - /// - /// Represents a matching the W3C definition that has an hex value of #40E0D0. - /// - public static readonly Rgba32 Turquoise = Color.Turquoise; - - /// - /// Represents a matching the W3C definition that has an hex value of #EE82EE. - /// - public static readonly Rgba32 Violet = Color.Violet; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5DEB3. - /// - public static readonly Rgba32 Wheat = Color.Wheat; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFFFF. - /// - public static readonly Rgba32 White = Color.White; - - /// - /// Represents a matching the W3C definition that has an hex value of #F5F5F5. - /// - public static readonly Rgba32 WhiteSmoke = Color.WhiteSmoke; - - /// - /// Represents a matching the W3C definition that has an hex value of #FFFF00. - /// - public static readonly Rgba32 Yellow = Color.Yellow; - - /// - /// Represents a matching the W3C definition that has an hex value of #9ACD32. - /// - public static readonly Rgba32 YellowGreen = Color.YellowGreen; - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs index 7337c0c89..0b0e9b1c1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.PixelOperations.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.PixelFormats Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); destinationVectors = destinationVectors.Slice(0, sourcePixels.Length); - SimdUtils.BulkConvertByteToNormalizedFloat( + SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(sourcePixels), MemoryMarshal.Cast(destinationVectors)); Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.PixelFormats destinationPixels = destinationPixels.Slice(0, sourceVectors.Length); Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + SimdUtils.NormalizedFloatToByteSaturate( MemoryMarshal.Cast(sourceVectors), MemoryMarshal.Cast(destinationPixels)); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 10631e2cf..43ec095a1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint Rgba { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Rgb24 Rgb { [MethodImpl(InliningOptions.ShortMethod)] - get => new Rgb24(this.R, this.G, this.B); + readonly get => new Rgb24(this.R, this.G, this.B); [MethodImpl(InliningOptions.ShortMethod)] set @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Bgr24 Bgr { [MethodImpl(InliningOptions.ShortMethod)] - get => new Bgr24(this.R, this.G, this.B); + readonly get => new Bgr24(this.R, this.G, this.B); [MethodImpl(InliningOptions.ShortMethod)] set @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.PixelFormats public uint PackedValue { [MethodImpl(InliningOptions.ShortMethod)] - get => this.Rgba; + readonly get => this.Rgba; [MethodImpl(InliningOptions.ShortMethod)] set => this.Rgba = value; @@ -230,7 +230,8 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba32 left, Rgba32 right) => !left.Equals(right); /// - /// Creates a new instance of the struct. + /// Creates a new instance of the struct + /// from the given hexadecimal string. /// /// /// The hexadecimal representation of the combined color components arranged @@ -239,23 +240,54 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static Rgba32 FromHex(string hex) + [MethodImpl(InliningOptions.ShortMethod)] + public static Rgba32 ParseHex(string hex) + { + Guard.NotNull(hex, nameof(hex)); + + if (!TryParseHex(hex, out Rgba32 rgba)) + { + throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + } + + return rgba; + } + + /// + /// Attempts to creates a new instance of the struct + /// from the given hexadecimal string. + /// + /// + /// The hexadecimal representation of the combined color components arranged + /// in rgb, rgba, rrggbb, or rrggbbaa format to match web syntax. + /// + /// When this method returns, contains the equivalent of the hexadecimal input. + /// + /// The . + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool TryParseHex(string hex, out Rgba32 result) { - Guard.NotNullOrWhiteSpace(hex, nameof(hex)); + result = default; + if (string.IsNullOrWhiteSpace(hex)) + { + return false; + } hex = ToRgbaHex(hex); if (hex is null || !uint.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint packedValue)) { - throw new ArgumentException("Hexadecimal string is not in the correct format.", nameof(hex)); + return false; } packedValue = BinaryPrimitives.ReverseEndianness(packedValue); - return Unsafe.As(ref packedValue); + result = Unsafe.As(ref packedValue); + return true; } /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -263,7 +295,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -271,7 +303,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -390,25 +422,25 @@ namespace SixLabors.ImageSharp.PixelFormats /// Converts the value of this instance to a hexadecimal string. /// /// A hexadecimal string representation of the value. - public string ToHex() + public readonly string ToHex() { uint hexOrder = (uint)(this.A << 0 | this.B << 8 | this.G << 16 | this.R << 24); return hexOrder.ToString("X8"); } /// - public override bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); + public override readonly bool Equals(object obj) => obj is Rgba32 rgba32 && this.Equals(rgba32); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); + public readonly bool Equals(Rgba32 other) => this.Rgba.Equals(other.Rgba); /// - public override string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; + public override readonly string ToString() => $"Rgba32({this.R}, {this.G}, {this.B}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.Rgba.GetHashCode(); + public override readonly int GetHashCode() => this.Rgba.GetHashCode(); /// /// Packs a into a color returning a new instance as a result. @@ -420,7 +452,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); return new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W); } @@ -459,7 +491,7 @@ namespace SixLabors.ImageSharp.PixelFormats { vector *= MaxBytes; vector += Half; - vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, MaxBytes); this.R = (byte)vector.X; this.G = (byte)vector.Y; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 56bc6f455..8e5f8f093 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public Rgba64(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.R = (ushort)MathF.Round(vector.X); this.G = (ushort)MathF.Round(vector.Y); this.B = (ushort)MathF.Round(vector.Z); @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.PixelFormats public Rgb48 Rgb { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.PixelFormats public ulong PackedValue { [MethodImpl(InliningOptions.ShortMethod)] - get => Unsafe.As(ref this); + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); [MethodImpl(InliningOptions.ShortMethod)] set => Unsafe.As(ref this) = value; @@ -195,7 +195,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Rgba64 left, Rgba64 right) => left.PackedValue != right.PackedValue; /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -203,13 +203,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One) * Max; + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One) * Max; this.R = (ushort)MathF.Round(vector.X); this.G = (ushort)MathF.Round(vector.Y); this.B = (ushort)MathF.Round(vector.Z); @@ -218,7 +218,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / Max; /// [MethodImpl(InliningOptions.ShortMethod)] @@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . [MethodImpl(InliningOptions.ShortMethod)] - public Rgba32 ToRgba32() + public readonly Rgba32 ToRgba32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The . [MethodImpl(InliningOptions.ShortMethod)] - public Bgra32 ToBgra32() + public readonly Bgra32 ToBgra32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -371,7 +371,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The . [MethodImpl(InliningOptions.ShortMethod)] - public Argb32 ToArgb32() + public readonly Argb32 ToArgb32() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -385,7 +385,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The . [MethodImpl(InliningOptions.ShortMethod)] - public Rgb24 ToRgb24() + public readonly Rgb24 ToRgb24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The . [MethodImpl(InliningOptions.ShortMethod)] - public Bgr24 ToBgr24() + public readonly Bgr24 ToBgr24() { byte r = ImageMaths.DownScaleFrom16BitTo8Bit(this.R); byte g = ImageMaths.DownScaleFrom16BitTo8Bit(this.G); @@ -407,17 +407,17 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); + public override readonly bool Equals(object obj) => obj is Rgba64 rgba64 && this.Equals(rgba64); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Rgba64 other) => this.PackedValue.Equals(other.PackedValue); /// - public override string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; + public override readonly string ToString() => $"Rgba64({this.R}, {this.G}, {this.B}, {this.A})"; /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 67f09f3a5..8a6f882c3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -94,10 +94,10 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The . /// - public static RgbaVector FromHex(string hex) => Color.FromHex(hex).ToPixel(); + public static RgbaVector FromHex(string hex) => Color.ParseHex(hex).ToPixel(); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -105,13 +105,13 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() => this.ToVector4(); + public readonly Vector4 ToScaledVector4() => this.ToVector4(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromVector4(Vector4 vector) { - vector = Vector4.Clamp(vector, Vector4.Zero, Vector4.One); + vector = Vector4Utilities.FastClamp(vector, Vector4.Zero, Vector4.One); this.R = vector.X; this.G = vector.Y; this.B = vector.Z; @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Converts the value of this instance to a hexadecimal string. ///
/// A hexadecimal string representation of the value. - public string ToHex() + public readonly string ToHex() { // Hex is RRGGBBAA Vector4 vector = this.ToVector4() * Max; @@ -188,23 +188,23 @@ namespace SixLabors.ImageSharp.PixelFormats } /// - public override bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is RgbaVector other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(RgbaVector other) => + public readonly bool Equals(RgbaVector other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); /// - public override string ToString() + public override readonly string ToString() { return FormattableString.Invariant($"RgbaVector({this.R:#0.##}, {this.G:#0.##}, {this.B:#0.##}, {this.A:#0.##})"); } /// - public override int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 1cc7d269c..526e831f8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -66,20 +66,20 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Short2 left, Short2 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] public void FromScaledVector4(Vector4 vector) { - var scaled = new Vector2(vector.X, vector.Y) * 65534F; + Vector2 scaled = new Vector2(vector.X, vector.Y) * 65534F; scaled -= new Vector2(32767F); this.PackedValue = Pack(scaled); } /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector2(); scaled += new Vector2(32767F); @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); + public readonly Vector4 ToVector4() => new Vector4((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10), 0, 1); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -157,21 +157,21 @@ namespace SixLabors.ImageSharp.PixelFormats ///
/// The . [MethodImpl(InliningOptions.ShortMethod)] - public Vector2 ToVector2() => new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); + public readonly Vector2 ToVector2() => new Vector2((short)(this.PackedValue & 0xFFFF), (short)(this.PackedValue >> 0x10)); /// - public override bool Equals(object obj) => obj is Short2 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Short2 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); + public readonly bool Equals(Short2 other) => this.PackedValue.Equals(other.PackedValue); /// [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector2(); return FormattableString.Invariant($"Short2({vector.X:#0.##}, {vector.Y:#0.##})"); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index 433f49f15..135aa8d58 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.PixelFormats public static bool operator !=(Short4 left, Short4 right) => !left.Equals(right); /// - public PixelOperations CreatePixelOperations() => new PixelOperations(); + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); /// [MethodImpl(InliningOptions.ShortMethod)] @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToScaledVector4() + public readonly Vector4 ToScaledVector4() { var scaled = this.ToVector4(); scaled += new Vector4(32767F); @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public Vector4 ToVector4() + public readonly Vector4 ToVector4() { return new Vector4( (short)(this.PackedValue & 0xFFFF), @@ -160,21 +160,21 @@ namespace SixLabors.ImageSharp.PixelFormats public void FromRgba64(Rgba64 source) => this.FromScaledVector4(source.ToScaledVector4()); /// - public override bool Equals(object obj) => obj is Short4 other && this.Equals(other); + public override readonly bool Equals(object obj) => obj is Short4 other && this.Equals(other); /// [MethodImpl(InliningOptions.ShortMethod)] - public bool Equals(Short4 other) => this.PackedValue.Equals(other); + public readonly bool Equals(Short4 other) => this.PackedValue.Equals(other); /// /// Gets the hash code for the current instance. /// /// Hash code for the instance. [MethodImpl(InliningOptions.ShortMethod)] - public override int GetHashCode() => this.PackedValue.GetHashCode(); + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); /// - public override string ToString() + public override readonly string ToString() { var vector = this.ToVector4(); return FormattableString.Invariant($"Short4({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] private static ulong Pack(ref Vector4 vector) { - vector = Vector4.Clamp(vector, Min, Max); + vector = Vector4Utilities.FastClamp(vector, Min, Max); // Clamp the value between min and max values ulong word4 = ((ulong)Math.Round(vector.X) & 0xFFFF) << 0x00; diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index 9f6fd2785..17af972a8 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// Provides access to pixel blenders /// public partial class PixelOperations - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Find an instance of the pixel blender. diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 8ef894737..1e1047e2b 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// /// The pixel format. public partial class PixelOperations - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Gets the global instance for the pixel type @@ -104,7 +104,7 @@ namespace SixLabors.ImageSharp.PixelFormats Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - where TSourcePixel : struct, IPixel + where TSourcePixel : unmanaged, IPixel { const int SliceLength = 1024; int numberOfSlices = sourcePixels.Length / SliceLength; @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.PixelFormats Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - where TDestinationPixel : struct, IPixel + where TDestinationPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index e67bd9d53..ac50dd8c4 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ApplyBackwardConversionModifiers(sourceVectors, modifiers); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (modifiers.IsDefined(PixelConversionModifiers.Scale)) { @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeFromVector4Core( ReadOnlySpan sourceVectors, Span destPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); ref TPixel destRef = ref MemoryMarshal.GetReference(destPixels); @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeToVector4Core( ReadOnlySpan sourcePixels, Span destVectors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Vector4 destRef = ref MemoryMarshal.GetReference(destVectors); @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeFromScaledVector4Core( ReadOnlySpan sourceVectors, Span destinationColors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceVectors); ref TPixel destRef = ref MemoryMarshal.GetReference(destinationColors); @@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils private static void UnsafeToScaledVector4Core( ReadOnlySpan sourceColors, Span destinationVectors) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref TPixel sourceRef = ref MemoryMarshal.GetReference(sourceColors); ref Vector4 destRef = ref MemoryMarshal.GetReference(destinationVectors); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 0350c669a..4ee645c20 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils ReadOnlySpan sourcePixels, Span destVectors, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destVectors, nameof(destVectors)); @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils // 'destVectors' and 'lastQuarterOfDestBuffer' are overlapping buffers, // but we are always reading/writing at different positions: - SimdUtils.BulkConvertByteToNormalizedFloat( + SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(lastQuarterOfDestBuffer), MemoryMarshal.Cast(destVectors.Slice(0, countWithoutLastItem))); @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils Span sourceVectors, Span destPixels, PixelConversionModifiers modifiers) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourceVectors, destPixels, nameof(destPixels)); @@ -107,7 +107,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils { Span tempSpan = tempBuffer.Memory.Span; - SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows( + SimdUtils.NormalizedFloatToByteSaturate( MemoryMarshal.Cast(sourceVectors), MemoryMarshal.Cast(tempSpan)); @@ -122,8 +122,8 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils return int.MaxValue; } - return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.IsAvx2CompatibleArchitecture ? 256 : 128; + return SimdUtils.ExtendedIntrinsics.IsAvailable && SimdUtils.HasVector8 ? 256 : 128; } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index 447869a7d..ba676b3b8 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) { - Vector4Utils.Premultiply(vectors); + Vector4Utilities.Premultiply(vectors); } } @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils { if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) { - Vector4Utils.UnPremultiply(vectors); + Vector4Utilities.UnPremultiply(vectors); } if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) diff --git a/src/ImageSharp/Primitives/ColorMatrix.cs b/src/ImageSharp/Primitives/ColorMatrix.cs index 477d120fa..09a2d17ae 100644 --- a/src/ImageSharp/Primitives/ColorMatrix.cs +++ b/src/ImageSharp/Primitives/ColorMatrix.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp /// The resulting matrix. public static ColorMatrix operator +(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 + value2.M11; m.M12 = value1.M12 + value2.M12; @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp /// The result of the subtraction. public static ColorMatrix operator -(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 - value2.M11; m.M12 = value1.M12 - value2.M12; @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp /// The negated matrix. public static ColorMatrix operator -(ColorMatrix value) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = -value.M11; m.M12 = -value.M12; @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp /// The result of the multiplication. public static ColorMatrix operator *(ColorMatrix value1, ColorMatrix value2) { - ColorMatrix m; + var m = default(ColorMatrix); // First row m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp /// The scaled matrix. public static ColorMatrix operator *(ColorMatrix value1, float value2) { - ColorMatrix m; + var m = default(ColorMatrix); m.M11 = value1.M11 * value2; m.M12 = value1.M12 * value2; diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs index 4229e69e7..3fda03b77 100644 --- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs +++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp /// [MethodImpl(InliningOptions.ShortMethod)] #pragma warning disable SA1008 // Opening parenthesis should be spaced correctly - public static implicit operator T[,] (in DenseMatrix data) + public static implicit operator T[,](in DenseMatrix data) #pragma warning restore SA1008 // Opening parenthesis should be spaced correctly { var result = new T[data.Rows, data.Columns]; @@ -153,6 +153,24 @@ namespace SixLabors.ImageSharp return result; } + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(DenseMatrix left, DenseMatrix right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(DenseMatrix left, DenseMatrix right) + => !(left == right); + /// /// Transposes the rows and columns of the dense matrix. /// @@ -210,15 +228,32 @@ namespace SixLabors.ImageSharp } /// - public override bool Equals(object obj) => obj is DenseMatrix other && this.Equals(other); + public override bool Equals(object obj) + => obj is DenseMatrix other && this.Equals(other); /// + [MethodImpl(InliningOptions.ShortMethod)] public bool Equals(DenseMatrix other) => this.Columns == other.Columns && this.Rows == other.Rows && this.Span.SequenceEqual(other.Span); /// - public override int GetHashCode() => this.Data.GetHashCode(); + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + { + HashCode code = default; + + code.Add(this.Columns); + code.Add(this.Rows); + + Span span = this.Span; + for (int i = 0; i < span.Length; i++) + { + code.Add(span[i]); + } + + return code.ToHashCode(); + } } } diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index 95b01fd9d..d391057a9 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -460,4 +460,4 @@ namespace SixLabors.ImageSharp this.Width.Equals(other.Width) && this.Height.Equals(other.Height); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 90e00924a..dde7beb3e 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Prepend(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Prepends a rotation matrix using the given rotation in degrees at the given origin. @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationMatrixRadians(radians, size)); + => this.Append(size => TransformUtilities.CreateRotationMatrixRadians(radians, size)); /// /// Appends a rotation matrix using the given rotation in degrees at the given origin. @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder PrependSkewDegrees(float degreesX, float degreesY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Prepends a centered skew matrix from the give angles in radians. @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Prepend(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in degrees. /// The . public AffineTransformBuilder AppendSkewDegrees(float degreesX, float degreesY) - => this.Append(size => TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size)); /// /// Appends a centered skew matrix from the give angles in radians. @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size)); + => this.Append(size => TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size)); /// /// Appends a skew matrix using the given angles in degrees at the given origin. diff --git a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs index a4a3f9b3d..2e5919d1e 100644 --- a/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs +++ b/src/ImageSharp/Processing/DefaultImageProcessorContext{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The pixel format internal class DefaultImageProcessorContext : IInternalImageProcessingContext - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly bool mutate; private readonly Image source; diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDiffuseExtensions.cs deleted file mode 100644 index 66337f669..000000000 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDiffuseExtensions.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extension methods to apply binary diffusion on an - /// using Mutate/Clone. - /// - public static class BinaryDiffuseExtensions - { - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold) => - source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold)); - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Rectangle rectangle) => - source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold), rectangle); - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Color upperColor, - Color lowerColor) => - source.ApplyProcessor(new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor)); - - /// - /// Dithers the image reducing it to two colors using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext BinaryDiffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Color upperColor, - Color lowerColor, - Rectangle rectangle) => - source.ApplyProcessor( - new BinaryErrorDiffusionProcessor(diffuser, threshold, upperColor, lowerColor), - rectangle); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs index afd4a4941..659b538fc 100644 --- a/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Binarization/BinaryDitherExtensions.cs @@ -1,7 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors.Binarization; using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing @@ -19,8 +18,8 @@ namespace SixLabors.ImageSharp.Processing /// The ordered ditherer. /// The to allow chaining of operations. public static IImageProcessingContext - BinaryDither(this IImageProcessingContext source, IOrderedDither dither) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither)); + BinaryDither(this IImageProcessingContext source, IDither dither) => + BinaryDither(source, dither, Color.White, Color.Black); /// /// Dithers the image reducing it to two colors using ordered dithering. @@ -32,10 +31,10 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext BinaryDither( this IImageProcessingContext source, - IOrderedDither dither, + IDither dither, Color upperColor, Color lowerColor) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor)); + source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor })); /// /// Dithers the image reducing it to two colors using ordered dithering. @@ -48,9 +47,9 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext BinaryDither( this IImageProcessingContext source, - IOrderedDither dither, + IDither dither, Rectangle rectangle) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither), rectangle); + BinaryDither(source, dither, Color.White, Color.Black, rectangle); /// /// Dithers the image reducing it to two colors using ordered dithering. @@ -65,10 +64,10 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext BinaryDither( this IImageProcessingContext source, - IOrderedDither dither, + IDither dither, Color upperColor, Color lowerColor, Rectangle rectangle) => - source.ApplyProcessor(new BinaryOrderedDitherProcessor(dither, upperColor, lowerColor), rectangle); + source.ApplyProcessor(new PaletteDitherProcessor(dither, new[] { upperColor, lowerColor }), rectangle); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DiffuseExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DiffuseExtensions.cs deleted file mode 100644 index 92d312fdf..000000000 --- a/src/ImageSharp/Processing/Extensions/Dithering/DiffuseExtensions.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Defines extension methods to apply diffusion to an - /// using Mutate/Clone. - /// - public static class DiffuseExtensions - { - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse(this IImageProcessingContext source) => - Diffuse(source, KnownDiffusers.FloydSteinberg, .5F); - - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse(this IImageProcessingContext source, float threshold) => - Diffuse(source, KnownDiffusers.FloydSteinberg, threshold); - - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold)); - - /// - /// Dithers the image reducing it to a web-safe palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - Rectangle rectangle) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold), rectangle); - - /// - /// Dithers the image reducing it to the given palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The palette to select substitute colors from. - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - ReadOnlyMemory palette) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette)); - - /// - /// Dithers the image reducing it to the given palette using error diffusion. - /// - /// The image this method extends. - /// The diffusion algorithm to apply. - /// The threshold to apply binarization of the image. Must be between 0 and 1. - /// The palette to select substitute colors from. - /// - /// The structure that specifies the portion of the image object to alter. - /// - /// The to allow chaining of operations. - public static IImageProcessingContext Diffuse( - this IImageProcessingContext source, - IErrorDiffuser diffuser, - float threshold, - ReadOnlyMemory palette, - Rectangle rectangle) => - source.ApplyProcessor(new ErrorDiffusionPaletteProcessor(diffuser, threshold, palette), rectangle); - } -} diff --git a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs index f58b025f3..a04aa0df8 100644 --- a/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing @@ -14,12 +13,12 @@ namespace SixLabors.ImageSharp.Processing public static class DitherExtensions { /// - /// Dithers the image reducing it to a web-safe palette using Bayer4x4 ordered dithering. + /// Dithers the image reducing it to a web-safe palette using . /// /// The image this method extends. /// The to allow chaining of operations. public static IImageProcessingContext Dither(this IImageProcessingContext source) => - Dither(source, KnownDitherers.BayerDither4x4); + Dither(source, KnownDitherings.Bayer8x8); /// /// Dithers the image reducing it to a web-safe palette using ordered dithering. @@ -27,21 +26,62 @@ namespace SixLabors.ImageSharp.Processing /// The image this method extends. /// The ordered ditherer. /// The to allow chaining of operations. - public static IImageProcessingContext Dither(this IImageProcessingContext source, IOrderedDither dither) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither)); + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither) => + source.ApplyProcessor(new PaletteDitherProcessor(dither)); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale)); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + ReadOnlyMemory palette) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, palette)); /// /// Dithers the image reducing it to the given palette using ordered dithering. /// /// The image this method extends. /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. /// The palette to select substitute colors from. /// The to allow chaining of operations. public static IImageProcessingContext Dither( this IImageProcessingContext source, - IOrderedDither dither, + IDither dither, + float ditherScale, ReadOnlyMemory palette) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette)); + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette)); + + /// + /// Dithers the image reducing it to a web-safe palette using . + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither(this IImageProcessingContext source, Rectangle rectangle) => + Dither(source, KnownDitherings.Bayer8x8, rectangle); /// /// Dithers the image reducing it to a web-safe palette using ordered dithering. @@ -54,15 +94,50 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext Dither( this IImageProcessingContext source, - IOrderedDither dither, + IDither dither, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); + + /// + /// Dithers the image reducing it to a web-safe palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + float ditherScale, + Rectangle rectangle) => + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle); + + /// + /// Dithers the image reducing it to the given palette using ordered dithering. + /// + /// The image this method extends. + /// The ordered ditherer. + /// The palette to select substitute colors from. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Dither( + this IImageProcessingContext source, + IDither dither, + ReadOnlyMemory palette, Rectangle rectangle) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither), rectangle); + source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle); /// /// Dithers the image reducing it to the given palette using ordered dithering. /// /// The image this method extends. /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. /// The palette to select substitute colors from. /// /// The structure that specifies the portion of the image object to alter. @@ -70,9 +145,10 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext Dither( this IImageProcessingContext source, - IOrderedDither dither, + IDither dither, + float ditherScale, ReadOnlyMemory palette, Rectangle rectangle) => - source.ApplyProcessor(new OrderedDitherPaletteProcessor(dither, palette), rectangle); + source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale, palette), rectangle); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs index 088f61884..f89540e24 100644 --- a/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Filters/FilterExtensions.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing public static class FilterExtensions { /// - /// Filters an image but the given color matrix + /// Filters an image by the given color matrix /// /// The image this method extends. /// The filter color matrix @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing => source.ApplyProcessor(new FilterProcessor(matrix)); /// - /// Filters an image but the given color matrix + /// Filters an image by the given color matrix /// /// The image this method extends. /// The filter color matrix @@ -32,4 +32,4 @@ namespace SixLabors.ImageSharp.Processing public static IImageProcessingContext Filter(this IImageProcessingContext source, ColorMatrix matrix, Rectangle rectangle) => source.ApplyProcessor(new FilterProcessor(matrix), rectangle); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs index 36966c296..59be5bf02 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing /// The image to mutate. /// The operation to perform on the source. public static void Mutate(this Image source, Action operation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Mutate(source, source.GetConfiguration(), operation); /// @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing /// The configuration which allows altering default behaviour or extending the library. /// The operation to perform on the source. public static void Mutate(this Image source, Configuration configuration, Action operation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing /// The image to mutate. /// The operations to perform on the source. public static void Mutate(this Image source, params IImageProcessor[] operations) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Mutate(source, source.GetConfiguration(), operations); /// @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing /// The configuration which allows altering default behaviour or extending the library. /// The operations to perform on the source. public static void Mutate(this Image source, Configuration configuration, params IImageProcessor[] operations) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(source, nameof(source)); Guard.NotNull(operations, nameof(operations)); @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing /// The operation to perform on the clone. /// The new public static Image Clone(this Image source, Action operation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Clone(source, source.GetConfiguration(), operation); /// @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Processing /// The operation to perform on the clone. /// The new public static Image Clone(this Image source, Configuration configuration, Action operation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Processing /// The operations to perform on the clone. /// The new public static Image Clone(this Image source, params IImageProcessor[] operations) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => Clone(source, source.GetConfiguration(), operations); /// @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Processing /// The operations to perform on the clone. /// The new public static Image Clone(this Image source, Configuration configuration, params IImageProcessor[] operations) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); @@ -231,7 +231,7 @@ namespace SixLabors.ImageSharp.Processing public Image ResultImage { get; private set; } public void Visit(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IInternalImageProcessingContext operationsRunner = this.configuration.ImageOperationsProvider.CreateImageProcessingContext(this.configuration, image, this.mutate); diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs index 3410ee6be..86ccddd85 100644 --- a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -27,5 +27,28 @@ namespace SixLabors.ImageSharp.Processing /// The to allow chaining of operations. public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer) => source.ApplyProcessor(new QuantizeProcessor(quantizer)); + + /// + /// Applies quantization to the image using the . + /// + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => + Quantize(source, KnownQuantizers.Octree, rectangle); + + /// + /// Applies quantization to the image. + /// + /// The image this method extends. + /// The quantizer to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The to allow chaining of operations. + public static IImageProcessingContext Quantize(this IImageProcessingContext source, IQuantizer quantizer, Rectangle rectangle) => + source.ApplyProcessor(new QuantizeProcessor(quantizer), rectangle); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs index 630564955..ee8f3854e 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing IResampler sampler) { Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); } @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing IResampler sampler) { Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = TransformUtils.GetTransformedSize(sourceRectangle.Size, transform); + Size targetDimensions = TransformUtilities.GetTransformedSize(sourceRectangle.Size, transform); return ctx.Transform(sourceRectangle, transform, targetDimensions, sampler); } diff --git a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs index e3051ccdd..5394fac8b 100644 --- a/src/ImageSharp/Processing/IImageProcessingContextFactory.cs +++ b/src/ImageSharp/Processing/IImageProcessingContextFactory.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing /// A flag to determine whether image operations are allowed to mutate the source image. /// A new IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } /// @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing { /// public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return new DefaultImageProcessorContext(configuration, source, mutate); } diff --git a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs index 9d023cca8..a39483b0d 100644 --- a/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs +++ b/src/ImageSharp/Processing/IInternalImageProcessingContext{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing /// /// The pixel type. internal interface IInternalImageProcessingContext : IImageProcessingContext - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Returns the result image to return by diff --git a/src/ImageSharp/Processing/KnownDiffusers.cs b/src/ImageSharp/Processing/KnownDiffusers.cs deleted file mode 100644 index 2b10312fe..000000000 --- a/src/ImageSharp/Processing/KnownDiffusers.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Contains reusable static instances of known error diffusion algorithms - /// - public static class KnownDiffusers - { - /// - /// Gets the error diffuser that implements the Atkinson algorithm. - /// - public static IErrorDiffuser Atkinson { get; } = new AtkinsonDiffuser(); - - /// - /// Gets the error diffuser that implements the Burks algorithm. - /// - public static IErrorDiffuser Burks { get; } = new BurksDiffuser(); - - /// - /// Gets the error diffuser that implements the Floyd-Steinberg algorithm. - /// - public static IErrorDiffuser FloydSteinberg { get; } = new FloydSteinbergDiffuser(); - - /// - /// Gets the error diffuser that implements the Jarvis-Judice-Ninke algorithm. - /// - public static IErrorDiffuser JarvisJudiceNinke { get; } = new JarvisJudiceNinkeDiffuser(); - - /// - /// Gets the error diffuser that implements the Sierra-2 algorithm. - /// - public static IErrorDiffuser Sierra2 { get; } = new Sierra2Diffuser(); - - /// - /// Gets the error diffuser that implements the Sierra-3 algorithm. - /// - public static IErrorDiffuser Sierra3 { get; } = new Sierra3Diffuser(); - - /// - /// Gets the error diffuser that implements the Sierra-Lite algorithm. - /// - public static IErrorDiffuser SierraLite { get; } = new SierraLiteDiffuser(); - - /// - /// Gets the error diffuser that implements the Stevenson-Arce algorithm. - /// - public static IErrorDiffuser StevensonArce { get; } = new StevensonArceDiffuser(); - - /// - /// Gets the error diffuser that implements the Stucki algorithm. - /// - public static IErrorDiffuser Stucki { get; } = new StuckiDiffuser(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDitherers.cs b/src/ImageSharp/Processing/KnownDitherers.cs deleted file mode 100644 index dad5bb38c..000000000 --- a/src/ImageSharp/Processing/KnownDitherers.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing -{ - /// - /// Contains reusable static instances of known ordered dither matrices - /// - public static class KnownDitherers - { - /// - /// Gets the order ditherer using the 2x2 Bayer dithering matrix - /// - public static IOrderedDither BayerDither2x2 { get; } = new BayerDither2x2(); - - /// - /// Gets the order ditherer using the 3x3 dithering matrix - /// - public static IOrderedDither OrderedDither3x3 { get; } = new OrderedDither3x3(); - - /// - /// Gets the order ditherer using the 4x4 Bayer dithering matrix - /// - public static IOrderedDither BayerDither4x4 { get; } = new BayerDither4x4(); - - /// - /// Gets the order ditherer using the 8x8 Bayer dithering matrix - /// - public static IOrderedDither BayerDither8x8 { get; } = new BayerDither8x8(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/KnownDitherings.cs b/src/ImageSharp/Processing/KnownDitherings.cs new file mode 100644 index 000000000..bb968d2ef --- /dev/null +++ b/src/ImageSharp/Processing/KnownDitherings.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing +{ + /// + /// Contains reusable static instances of known dithering algorithms. + /// + public static class KnownDitherings + { + /// + /// Gets the order ditherer using the 2x2 Bayer dithering matrix + /// + public static IDither Bayer2x2 { get; } = OrderedDither.Bayer2x2; + + /// + /// Gets the order ditherer using the 3x3 dithering matrix + /// + public static IDither Ordered3x3 { get; } = OrderedDither.Ordered3x3; + + /// + /// Gets the order ditherer using the 4x4 Bayer dithering matrix + /// + public static IDither Bayer4x4 { get; } = OrderedDither.Bayer4x4; + + /// + /// Gets the order ditherer using the 8x8 Bayer dithering matrix + /// + public static IDither Bayer8x8 { get; } = OrderedDither.Bayer8x8; + + /// + /// Gets the error Dither that implements the Atkinson algorithm. + /// + public static IDither Atkinson { get; } = ErrorDither.Atkinson; + + /// + /// Gets the error Dither that implements the Burks algorithm. + /// + public static IDither Burks { get; } = ErrorDither.Burkes; + + /// + /// Gets the error Dither that implements the Floyd-Steinberg algorithm. + /// + public static IDither FloydSteinberg { get; } = ErrorDither.FloydSteinberg; + + /// + /// Gets the error Dither that implements the Jarvis-Judice-Ninke algorithm. + /// + public static IDither JarvisJudiceNinke { get; } = ErrorDither.JarvisJudiceNinke; + + /// + /// Gets the error Dither that implements the Sierra-2 algorithm. + /// + public static IDither Sierra2 { get; } = ErrorDither.Sierra2; + + /// + /// Gets the error Dither that implements the Sierra-3 algorithm. + /// + public static IDither Sierra3 { get; } = ErrorDither.Sierra3; + + /// + /// Gets the error Dither that implements the Sierra-Lite algorithm. + /// + public static IDither SierraLite { get; } = ErrorDither.SierraLite; + + /// + /// Gets the error Dither that implements the Stevenson-Arce algorithm. + /// + public static IDither StevensonArce { get; } = ErrorDither.StevensonArce; + + /// + /// Gets the error Dither that implements the Stucki algorithm. + /// + public static IDither Stucki { get; } = ErrorDither.Stucki; + } +} diff --git a/src/ImageSharp/Processing/KnownResamplers.cs b/src/ImageSharp/Processing/KnownResamplers.cs index 621215b28..348c08407 100644 --- a/src/ImageSharp/Processing/KnownResamplers.cs +++ b/src/ImageSharp/Processing/KnownResamplers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -13,86 +13,86 @@ namespace SixLabors.ImageSharp.Processing /// /// Gets the Bicubic sampler that implements the bicubic kernel algorithm W(x) /// - public static IResampler Bicubic { get; } = new BicubicResampler(); + public static IResampler Bicubic { get; } = default(BicubicResampler); /// /// Gets the Box sampler that implements the box algorithm. Similar to nearest neighbor when upscaling. /// When downscaling the pixels will average, merging pixels together. /// - public static IResampler Box { get; } = new BoxResampler(); + public static IResampler Box { get; } = default(BoxResampler); /// /// Gets the Catmull-Rom sampler, a well known standard Cubic Filter often used as a interpolation function /// - public static IResampler CatmullRom { get; } = new CatmullRomResampler(); + public static IResampler CatmullRom { get; } = CubicResampler.CatmullRom; /// /// Gets the Hermite sampler. A type of smoothed triangular interpolation filter that rounds off strong edges while /// preserving flat 'color levels' in the original image. /// - public static IResampler Hermite { get; } = new HermiteResampler(); + public static IResampler Hermite { get; } = CubicResampler.Hermite; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 2 pixels. /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos2 { get; } = new Lanczos2Resampler(); + public static IResampler Lanczos2 { get; } = LanczosResampler.Lanczos2; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 3 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos3 { get; } = new Lanczos3Resampler(); + public static IResampler Lanczos3 { get; } = LanczosResampler.Lanczos3; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 5 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos5 { get; } = new Lanczos5Resampler(); + public static IResampler Lanczos5 { get; } = LanczosResampler.Lanczos5; /// /// Gets the Lanczos kernel sampler that implements smooth interpolation with a radius of 8 pixels /// This algorithm provides sharpened results when compared to others when downsampling. /// - public static IResampler Lanczos8 { get; } = new Lanczos8Resampler(); + public static IResampler Lanczos8 { get; } = LanczosResampler.Lanczos8; /// /// Gets the Mitchell-Netravali sampler. This seperable cubic algorithm yields a very good equilibrium between /// detail preservation (sharpness) and smoothness. /// - public static IResampler MitchellNetravali { get; } = new MitchellNetravaliResampler(); + public static IResampler MitchellNetravali { get; } = CubicResampler.MitchellNetravali; /// /// Gets the Nearest-Neighbour sampler that implements the nearest neighbor algorithm. This uses a very fast, unscaled filter /// which will select the closest pixel to the new pixels position. /// - public static IResampler NearestNeighbor { get; } = new NearestNeighborResampler(); + public static IResampler NearestNeighbor { get; } = default(NearestNeighborResampler); /// /// Gets the Robidoux sampler. This algorithm developed by Nicolas Robidoux providing a very good equilibrium between /// detail preservation (sharpness) and smoothness comparable to . /// - public static IResampler Robidoux { get; } = new RobidouxResampler(); + public static IResampler Robidoux { get; } = CubicResampler.Robidoux; /// /// Gets the Robidoux Sharp sampler. A sharpened form of the sampler /// - public static IResampler RobidouxSharp { get; } = new RobidouxSharpResampler(); + public static IResampler RobidouxSharp { get; } = CubicResampler.RobidouxSharp; /// - /// Gets the Spline sampler. A seperable cubic algorithm similar to but yielding smoother results. + /// Gets the Spline sampler. A separable cubic algorithm similar to but yielding smoother results. /// - public static IResampler Spline { get; } = new SplineResampler(); + public static IResampler Spline { get; } = CubicResampler.Spline; /// /// Gets the Triangle sampler, otherwise known as Bilinear. This interpolation algorithm can be used where perfect image transformation /// with pixel matching is impossible, so that one can calculate and assign appropriate intensity values to pixels /// - public static IResampler Triangle { get; } = new TriangleResampler(); + public static IResampler Triangle { get; } = default(TriangleResampler); /// /// Gets the Welch sampler. A high speed algorithm that delivers very sharpened results. /// - public static IResampler Welch { get; } = new WelchResampler(); + public static IResampler Welch { get; } = default(WelchResampler); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs deleted file mode 100644 index 287853979..000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Performs binary threshold filtering against an image using error diffusion. - /// - public class BinaryErrorDiffusionProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser) - : this(diffuser, .5F) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold) - : this(diffuser, threshold, Color.White, Color.Black) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold. - public BinaryErrorDiffusionProcessor(IErrorDiffuser diffuser, float threshold, Color upperColor, Color lowerColor) - { - Guard.NotNull(diffuser, nameof(diffuser)); - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - - this.Diffuser = diffuser; - this.Threshold = threshold; - this.UpperColor = upperColor; - this.LowerColor = lowerColor; - } - - /// - /// Gets the error diffuser. - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the threshold value. - /// - public float Threshold { get; } - - /// - /// Gets the color to use for pixels that are above the threshold. - /// - public Color UpperColor { get; } - - /// - /// Gets the color to use for pixels that fall below the threshold. - /// - public Color LowerColor { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new BinaryErrorDiffusionProcessor(configuration, this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs deleted file mode 100644 index 262e9d024..000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryErrorDiffusionProcessor{TPixel}.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Performs binary threshold filtering against an image using error diffusion. - /// - /// The pixel format. - internal sealed class BinaryErrorDiffusionProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly BinaryErrorDiffusionProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BinaryErrorDiffusionProcessor(Configuration configuration, BinaryErrorDiffusionProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - TPixel upperColor = this.definition.UpperColor.ToPixel(); - TPixel lowerColor = this.definition.LowerColor.ToPixel(); - IErrorDiffuser diffuser = this.definition.Diffuser; - - byte threshold = (byte)MathF.Round(this.definition.Threshold * 255F); - bool isAlphaOnly = typeof(TPixel) == typeof(A8); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - TPixel transformedPixel = luminance >= threshold ? upperColor : lowerColor; - diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs deleted file mode 100644 index 1626bbe80..000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Defines a binary threshold filtering using ordered dithering. - /// - public class BinaryOrderedDitherProcessor : IImageProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - public BinaryOrderedDitherProcessor(IOrderedDither dither) - : this(dither, Color.White, Color.Black) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The color to use for pixels that are above the threshold. - /// The color to use for pixels that are below the threshold. - public BinaryOrderedDitherProcessor(IOrderedDither dither, Color upperColor, Color lowerColor) - { - this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); - this.UpperColor = upperColor; - this.LowerColor = lowerColor; - } - - /// - /// Gets the ditherer. - /// - public IOrderedDither Dither { get; } - - /// - /// Gets the color to use for pixels that are above the threshold. - /// - public Color UpperColor { get; } - - /// - /// Gets the color to use for pixels that fall below the threshold. - /// - public Color LowerColor { get; } - - /// - public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new BinaryOrderedDitherProcessor(configuration, this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs deleted file mode 100644 index 66b92d1ce..000000000 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryOrderedDitherProcessor{TPixel}.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Binarization -{ - /// - /// Performs binary threshold filtering against an image using ordered dithering. - /// - /// The pixel format. - internal class BinaryOrderedDitherProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private readonly BinaryOrderedDitherProcessor definition; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public BinaryOrderedDitherProcessor(Configuration configuration, BinaryOrderedDitherProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } - - /// - protected override void OnFrameApply(ImageFrame source) - { - IOrderedDither dither = this.definition.Dither; - TPixel upperColor = this.definition.UpperColor.ToPixel(); - TPixel lowerColor = this.definition.LowerColor.ToPixel(); - - bool isAlphaOnly = typeof(TPixel) == typeof(A8); - - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - sourcePixel.ToRgba32(ref rgba); - luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - dither.Dither(source, sourcePixel, upperColor, lowerColor, luminance, x, y); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 7bfb02446..17fb39df3 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new BinaryThresholdProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 380ce64d2..0d363689d 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// /// The pixel format. internal class BinaryThresholdProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly BinaryThresholdProcessor definition; @@ -42,36 +42,64 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization Configuration configuration = this.Configuration; var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - bool isAlphaOnly = typeof(TPixel) == typeof(A8); - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - - ParallelHelper.IterateRows( - workingRect, + var operation = new RowOperation(interest, source, upper, lower, threshold, isAlphaOnly); + ParallelRowIterator.IterateRows( configuration, - rows => - { - Rgba32 rgba = default; - for (int y = rows.Min; y < rows.Max; y++) - { - Span row = source.GetPixelRowSpan(y); + interest, + in operation); + } + + /// + /// A implementing the clone logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly TPixel upper; + private readonly TPixel lower; + private readonly byte threshold; + private readonly int minX; + private readonly int maxX; + private readonly bool isAlphaOnly; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + ImageFrame source, + TPixel upper, + TPixel lower, + byte threshold, + bool isAlphaOnly) + { + this.source = source; + this.upper = upper; + this.lower = lower; + this.threshold = threshold; + this.minX = bounds.X; + this.maxX = bounds.Right; + this.isAlphaOnly = isAlphaOnly; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Rgba32 rgba = default; + Span row = this.source.GetPixelRowSpan(y); + ref TPixel rowRef = ref MemoryMarshal.GetReference(row); - for (int x = startX; x < endX; x++) - { - ref TPixel color = ref row[x]; - color.ToRgba32(ref rgba); + for (int x = this.minX; x < this.maxX; x++) + { + ref TPixel color = ref Unsafe.Add(ref rowRef, x); + color.ToRgba32(ref rgba); - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - color = luminance >= threshold ? upper : lower; - } - } - }); + // Convert to grayscale using ITU-R Recommendation BT.709 if required + byte luminance = this.isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); + color = luminance >= this.threshold ? this.upper : this.lower; + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs index 92c84a945..36cc7f697 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors { /// public abstract ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; /// IImageProcessor IImageProcessor.CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs index c539861f9..e933978c2 100644 --- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public abstract class CloningImageProcessor : ICloningImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.Processing.Processors } /// - /// Gets the size of the target image. + /// Gets the size of the destination image. /// /// The . - protected abstract Size GetTargetSize(); + protected abstract Size GetDestinationSize(); /// /// This method is called before the process is applied to prepare the processor. @@ -168,21 +168,21 @@ namespace SixLabors.ImageSharp.Processing.Processors private Image CreateTarget() { Image source = this.Source; - Size targetSize = this.GetTargetSize(); + Size destinationSize = this.GetDestinationSize(); // We will always be creating the clone even for mutate because we may need to resize the canvas. - var targetFrames = new ImageFrame[source.Frames.Count]; - for (int i = 0; i < targetFrames.Length; i++) + var destinationFrames = new ImageFrame[source.Frames.Count]; + for (int i = 0; i < destinationFrames.Length; i++) { - targetFrames[i] = new ImageFrame( + destinationFrames[i] = new ImageFrame( this.Configuration, - targetSize.Width, - targetSize.Height, + destinationSize.Width, + destinationSize.Height, source.Frames[i].Metadata.DeepClone()); } // Use the overload to prevent an extra frame being added. - return new Image(this.Configuration, source.Metadata.DeepClone(), targetFrames); + return new Image(this.Configuration, source.Metadata.DeepClone(), destinationFrames); } private void CheckFrameCount(Image a, Image b) diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs index 6bb02f1d1..65aa81c60 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new BokehBlurProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 316579da7..cf97751be 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -2,13 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; @@ -21,28 +19,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// The pixel format. /// This processor is based on the code from Mike Pound, see github.com/mikepound/convolve. internal class BokehBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - /// - /// The kernel radius. - /// - private readonly int radius; - /// /// The gamma highlight factor to use when applying the effect /// private readonly float gamma; - /// - /// The maximum size of the kernel in either direction - /// - private readonly int kernelSize; - - /// - /// The number of components to use when applying the bokeh blur - /// - private readonly int componentsCount; - /// /// The kernel parameters to use for the current instance (a: X, b: Y, A: Z, B: W) /// @@ -53,16 +36,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// private readonly Complex64[][] kernels; - /// - /// The scaling factor for kernel values - /// - private readonly float kernelsScale; - - /// - /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances - /// - private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); - /// /// Initializes a new instance of the class. /// @@ -73,29 +46,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution public BokehBlurProcessor(Configuration configuration, BokehBlurProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.radius = definition.Radius; - this.kernelSize = (this.radius * 2) + 1; - this.componentsCount = definition.Components; this.gamma = definition.Gamma; - // Reuse the initialized values from the cache, if possible - var parameters = new BokehBlurParameters(this.radius, this.componentsCount); - if (Cache.TryGetValue(parameters, out BokehBlurKernelData info)) - { - this.kernelParameters = info.Parameters; - this.kernelsScale = info.Scale; - this.kernels = info.Kernels; - } - else - { - // Initialize the complex kernels and parameters with the current arguments - (this.kernelParameters, this.kernelsScale) = this.GetParameters(); - this.kernels = this.CreateComplexKernels(); - this.NormalizeKernels(); + // Get the bokeh blur data + BokehBlurKernelData data = BokehBlurKernelDataProvider.GetBokehBlurKernelData( + definition.Radius, + (definition.Radius * 2) + 1, + definition.Components); - // Store them in the cache for future use - Cache.TryAdd(parameters, new BokehBlurKernelData(this.kernelParameters, this.kernelsScale, this.kernels)); - } + this.kernelParameters = data.Parameters; + this.kernels = data.Kernels; } /// @@ -108,403 +68,250 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public IReadOnlyList KernelParameters => this.kernelParameters; - /// - /// Gets the kernel scales to adjust the component values in each kernel - /// - private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; - - /// - /// Gets the available bokeh blur kernel parameters - /// - private static IReadOnlyList KernelComponents { get; } = new[] + /// + protected override void OnFrameApply(ImageFrame source) { - // 1 component - new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, + // Preliminary gamma highlight pass + var gammaOperation = new ApplyGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.SourceRectangle, + in gammaOperation); - // 2 components - new[] - { - new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), - new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) - }, + // Create a 0-filled buffer to use to store the result of the component convolutions + using Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean); - // 3 components - new[] - { - new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f), - new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f), - new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f) - }, + // Perform the 1D convolutions on all the kernel components and accumulate the results + this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); - // 4 components - new[] - { - new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f), - new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f), - new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f), - new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f) - }, - - // 5 components - new[] - { - new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f), - new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f), - new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f), - new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f), - new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f) - }, - - // 6 components - new[] - { - new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f), - new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f), - new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f), - new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f), - new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f), - new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f) - } - }; + float inverseGamma = 1 / this.gamma; - /// - /// Gets the kernel parameters and scaling factor for the current count value in the current instance - /// - private (Vector4[] Parameters, float Scale) GetParameters() - { - // Prepare the kernel components - int index = Math.Max(0, Math.Min(this.componentsCount - 1, KernelComponents.Count)); - return (KernelComponents[index], KernelScales[index]); + // Apply the inverse gamma exposure pass, and write the final pixel data + var operation = new ApplyInverseGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); + ParallelRowIterator.IterateRows( + this.Configuration, + this.SourceRectangle, + in operation); } /// - /// Creates the collection of complex 1D kernels with the specified parameters + /// Computes and aggregates the convolution for each complex kernel component in the processor. /// - private Complex64[][] CreateComplexKernels() + /// The source image. Cannot be null. + /// The structure that specifies the portion of the image object to draw. + /// The configuration. + /// The buffer with the raw pixel data to use to aggregate the results of each convolution. + private void OnFrameApplyCore( + ImageFrame source, + Rectangle sourceRectangle, + Configuration configuration, + Buffer2D processingBuffer) { - var kernels = new Complex64[this.kernelParameters.Length][]; - ref Vector4 baseRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - for (int i = 0; i < this.kernelParameters.Length; i++) + // Allocate the buffer with the intermediate convolution results + using Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + // Perform two 1D convolutions for each component in the current instance + ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); + ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); + for (int i = 0; i < this.kernels.Length; i++) { - ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i); - kernels[i] = this.CreateComplex1DKernel(paramsRef.X, paramsRef.Y); + // Compute the resulting complex buffer for the current component + Complex64[] kernel = Unsafe.Add(ref baseRef, i); + Vector4 parameters = Unsafe.Add(ref paramsRef, i); + + // Compute the vertical 1D convolution + var verticalOperation = new ApplyVerticalConvolutionRowOperation(sourceRectangle, firstPassBuffer, source.PixelBuffer, kernel); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in verticalOperation); + + // Compute the horizontal 1D convolutions and accumulate the partial results on the target buffer + var horizontalOperation = new ApplyHorizontalConvolutionRowOperation(sourceRectangle, processingBuffer, firstPassBuffer, kernel, parameters.Z, parameters.W); + ParallelRowIterator.IterateRows( + configuration, + sourceRectangle, + in horizontalOperation); } - - return kernels; } /// - /// Creates a complex 1D kernel with the specified parameters + /// A implementing the vertical convolution logic for . /// - /// The exponential parameter for each complex component - /// The angle component for each complex component - private Complex64[] CreateComplex1DKernel(float a, float b) + private readonly struct ApplyVerticalConvolutionRowOperation : IRowOperation { - var kernel = new Complex64[this.kernelSize]; - ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); - int r = this.radius, n = -r; - - for (int i = 0; i < this.kernelSize; i++, n++) + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourcePixels; + private readonly Complex64[] kernel; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyVerticalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourcePixels, + Complex64[] kernel) { - // Incrementally compute the range values - float value = n * this.kernelsScale * (1f / r); - value *= value; - - // Fill in the complex kernel values - Unsafe.Add(ref baseRef, i) = new Complex64( - MathF.Exp(-a * value) * MathF.Cos(b * value), - MathF.Exp(-a * value) * MathF.Sin(b * value)); + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetValues = targetValues; + this.sourcePixels = sourcePixels; + this.kernel = kernel; } - return kernel; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); + + for (int x = 0; x < this.bounds.Width; x++) + { + Buffer2DUtils.Convolve4(this.kernel, this.sourcePixels, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX); + } + } } /// - /// Normalizes the kernels with respect to A * real + B * imaginary + /// A implementing the horizontal convolution logic for . /// - private void NormalizeKernels() + private readonly struct ApplyHorizontalConvolutionRowOperation : IRowOperation { - // Calculate the complex weighted sum - float total = 0; - Span kernelsSpan = this.kernels.AsSpan(); - ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan); - ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - - for (int i = 0; i < this.kernelParameters.Length; i++) + private readonly Rectangle bounds; + private readonly Buffer2D targetValues; + private readonly Buffer2D sourceValues; + private readonly Complex64[] kernel; + private readonly float z; + private readonly float w; + private readonly int maxY; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyHorizontalConvolutionRowOperation( + Rectangle bounds, + Buffer2D targetValues, + Buffer2D sourceValues, + Complex64[] kernel, + float z, + float w) { - ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i); - int length = kernelRef.Length; - ref Complex64 valueRef = ref kernelRef[0]; - ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i); - - for (int j = 0; j < length; j++) - { - for (int k = 0; k < length; k++) - { - ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j); - ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k); - total += - (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary))) - + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real))); - } - } + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetValues = targetValues; + this.sourceValues = sourceValues; + this.kernel = kernel; + this.z = z; + this.w = w; } - // Normalize the kernels - float scalar = 1f / MathF.Sqrt(total); - for (int i = 0; i < kernelsSpan.Length; i++) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i); - int length = kernelsRef.Length; - ref Complex64 valueRef = ref kernelsRef[0]; + Span targetRowSpan = this.targetValues.GetRowSpan(y).Slice(this.bounds.X); - for (int j = 0; j < length; j++) + for (int x = 0; x < this.bounds.Width; x++) { - Unsafe.Add(ref valueRef, j) *= scalar; + Buffer2DUtils.Convolve4AndAccumulatePartials(this.kernel, this.sourceValues, targetRowSpan, y, x, this.bounds.Y, this.maxY, this.bounds.X, this.maxX, this.z, this.w); } } } - /// - protected override void OnFrameApply(ImageFrame source) + /// + /// A implementing the gamma exposure logic for . + /// + private readonly struct ApplyGammaExposureRowOperation : IRowOperation { - // Preliminary gamma highlight pass - this.ApplyGammaExposure(source.PixelBuffer, this.SourceRectangle, this.Configuration); - - // Create a 0-filled buffer to use to store the result of the component convolutions - using (Buffer2D processingBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size(), AllocationOptions.Clean)) + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Configuration configuration; + private readonly float gamma; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyGammaExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Configuration configuration, + float gamma) { - // Perform the 1D convolutions on all the kernel components and accumulate the results - this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); - - // Apply the inverse gamma exposure pass, and write the final pixel data - this.ApplyInverseGammaExposure(source.PixelBuffer, processingBuffer, this.SourceRectangle, this.Configuration); + this.bounds = bounds; + this.targetPixels = targetPixels; + this.configuration = configuration; + this.gamma = gamma; } - } - /// - /// Computes and aggregates the convolution for each complex kernel component in the processor. - /// - /// The source image. Cannot be null. - /// The structure that specifies the portion of the image object to draw. - /// The configuration. - /// The buffer with the raw pixel data to use to aggregate the results of each convolution. - private void OnFrameApplyCore( - ImageFrame source, - Rectangle sourceRectangle, - Configuration configuration, - Buffer2D processingBuffer) - { - using (Buffer2D firstPassBuffer = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - // Perform two 1D convolutions for each component in the current instance - ref Complex64[] baseRef = ref MemoryMarshal.GetReference(this.kernels.AsSpan()); - ref Vector4 paramsRef = ref MemoryMarshal.GetReference(this.kernelParameters.AsSpan()); - for (int i = 0; i < this.kernels.Length; i++) + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + + for (int x = 0; x < this.bounds.Width; x++) { - // Compute the resulting complex buffer for the current component - var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); - Complex64[] kernel = Unsafe.Add(ref baseRef, i); - Vector4 parameters = Unsafe.Add(ref paramsRef, i); - - // Compute the two 1D convolutions and accumulate the partial results on the target buffer - this.ApplyConvolution(firstPassBuffer, source.PixelBuffer, interest, kernel, configuration); - this.ApplyConvolution(processingBuffer, firstPassBuffer, interest, kernel, configuration, parameters.Z, parameters.W); + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.X = MathF.Pow(v.X, this.gamma); + v.Y = MathF.Pow(v.Y, this.gamma); + v.Z = MathF.Pow(v.Z, this.gamma); } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the inverse gamma exposure logic for . /// - /// The target values to use to store the results. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The 1D kernel. - /// The - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration) + private readonly struct ApplyInverseGammaExposureRowOperation : IRowOperation { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); - - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4(kernel, sourcePixels, targetRowSpan, y, x, startY, maxY, startX, maxX); - } - } - }); - } + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourceValues; + private readonly Configuration configuration; + private readonly float inverseGamma; + + [MethodImpl(InliningOptions.ShortMethod)] + public ApplyInverseGammaExposureRowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourceValues, + Configuration configuration, + float inverseGamma) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourceValues = sourceValues; + this.configuration = configuration; + this.inverseGamma = inverseGamma; + } - /// - /// Applies the process to the specified portion of the specified buffer at the specified location - /// and with the specified size. - /// - /// The target values to use to store the results. - /// The source complex values. Cannot be null. - /// The structure that specifies the portion of the image object to draw. - /// The 1D kernel. - /// The - /// The weight factor for the real component of the complex pixel values. - /// The weight factor for the imaginary component of the complex pixel values. - private void ApplyConvolution( - Buffer2D targetValues, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Complex64[] kernel, - Configuration configuration, - float z, - float w) - { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetValues.GetRowSpan(y).Slice(startX); - - for (int x = 0; x < width; x++) - { - Buffer2DUtils.Convolve4AndAccumulatePartials(kernel, sourceValues, targetRowSpan, y, x, startY, maxY, startX, maxX, z, w); - } - } - }); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Vector4 low = Vector4.Zero; + var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - /// - /// Applies the gamma correction/highlight to the input pixel buffer. - /// - /// The target pixel buffer to adjust. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyGammaExposure( - Buffer2D targetPixels, - Rectangle sourceRectangle, - Configuration configuration) - { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float exp = this.gamma; - - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, x); - v.X = MathF.Pow(v.X, exp); - v.Y = MathF.Pow(v.Y, exp); - v.Z = MathF.Pow(v.Z, exp); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan.Slice(0, length), targetRowSpan); - } - }); - } + Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - /// - /// Applies the inverse gamma correction/highlight pass, and converts the input buffer into pixel values. - /// - /// The target pixels to apply the process to. - /// The source values. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The - private void ApplyInverseGammaExposure( - Buffer2D targetPixels, - Buffer2D sourceValues, - Rectangle sourceRectangle, - Configuration configuration) - { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - float expGamma = 1 / this.gamma; - - ParallelHelper.IterateRows( - workingRectangle, - configuration, - rows => - { - Vector4 low = Vector4.Zero; - var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetPixelSpan = targetPixels.GetRowSpan(y).Slice(startX); - Span sourceRowSpan = sourceValues.GetRowSpan(y).Slice(startX); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - - for (int x = 0; x < width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); - var clamp = Vector4.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, expGamma); - v.Y = MathF.Pow(clamp.Y, expGamma); - v.Z = MathF.Pow(clamp.Z, expGamma); - } - - PixelOperations.Instance.FromVector4Destructive(configuration, sourceRowSpan.Slice(0, width), targetPixelSpan, PixelConversionModifiers.Premultiply); - } - }); + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref sourceRef, x); + var clamp = Vector4Utilities.FastClamp(v, low, high); + v.X = MathF.Pow(clamp.X, this.inverseGamma); + v.Y = MathF.Pow(clamp.Y, this.inverseGamma); + v.Z = MathF.Pow(clamp.Z, this.inverseGamma); + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 7ca4b6c6f..92f7ab02d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new BoxBlurProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs index 095c91bac..fc80905ee 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class BoxBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -40,10 +40,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index c2b85a4ab..f7439879e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -3,9 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class Convolution2DProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -60,79 +60,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix matrixY = this.KernelY; - DenseMatrix matrixX = this.KernelX; - bool preserveAlpha = this.PreserveAlpha; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - int maxY = endY - 1; - int maxX = endX - 1; + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelY, this.KernelX, this.Configuration, this.PreserveAlpha); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) - { - source.CopyTo(targetPixels); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernelY; + private readonly DenseMatrix kernelX; + private readonly Configuration configuration; + private readonly bool preserveAlpha; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernelY, + DenseMatrix kernelX, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernelY = kernelY; + this.kernelX = kernelX; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D3( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve2D4( - in matrixY, - in matrixX, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D3( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve2D4( + in this.kernelY, + in this.kernelX, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 32bdf6bc5..4bbb15cba 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -3,9 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class Convolution2PassProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -59,95 +59,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - this.ApplyConvolution(firstPassPixels, source.PixelBuffer, interest, this.KernelX, this.Configuration); - this.ApplyConvolution(source.PixelBuffer, firstPassPixels, interest, this.KernelY, this.Configuration); - } + using Buffer2D firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + // Horizontal convolution + var horizontalOperation = new RowOperation(interest, firstPassPixels, source.PixelBuffer, this.KernelX, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in horizontalOperation); + + // Vertical convolution + var verticalOperation = new RowOperation(interest, source.PixelBuffer, firstPassPixels, this.KernelY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in verticalOperation); } /// - /// Applies the process to the specified portion of the specified at the specified location - /// and with the specified size. + /// A implementing the convolution logic for . /// - /// The target pixels to apply the process to. - /// The source pixels. Cannot be null. - /// - /// The structure that specifies the portion of the image object to draw. - /// - /// The kernel operator. - /// The - private void ApplyConvolution( - Buffer2D targetPixels, - Buffer2D sourcePixels, - Rectangle sourceRectangle, - in DenseMatrix kernel, - Configuration configuration) + private readonly struct RowOperation : IRowOperation { - DenseMatrix matrix = kernel; - bool preserveAlpha = this.PreserveAlpha; - - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; - - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - configuration, - (rows, vectorBuffer) => + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); + + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; + + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); + + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan.Slice(0, length), vectorSpan); - - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve3( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve4( - in matrix, - sourcePixels, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, targetRowSpan); - } - }); + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + maxY, + this.bounds.X, + maxX); + } + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 285bcab27..8201b8e23 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -3,9 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class ConvolutionProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -51,76 +51,96 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - DenseMatrix matrix = this.KernelXY; - bool preserveAlpha = this.PreserveAlpha; + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + + source.CopyTo(targetPixels); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - int maxY = endY - 1; - int maxX = endX - 1; + var operation = new RowOperation(interest, targetPixels, source.PixelBuffer, this.KernelXY, this.Configuration, this.PreserveAlpha); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size())) - { - source.CopyTo(targetPixels); + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - var workingRectangle = Rectangle.FromLTRB(startX, startY, endX, endY); - int width = workingRectangle.Width; + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int maxY; + private readonly int maxX; + private readonly Buffer2D targetPixels; + private readonly Buffer2D sourcePixels; + private readonly DenseMatrix kernel; + private readonly Configuration configuration; + private readonly bool preserveAlpha; - ParallelHelper.IterateRowsWithTempBuffer( - workingRectangle, - this.Configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - ref Vector4 vectorSpanRef = ref MemoryMarshal.GetReference(vectorSpan); + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + Buffer2D targetPixels, + Buffer2D sourcePixels, + DenseMatrix kernel, + Configuration configuration, + bool preserveAlpha) + { + this.bounds = bounds; + this.maxY = this.bounds.Bottom - 1; + this.maxX = this.bounds.Right - 1; + this.targetPixels = targetPixels; + this.sourcePixels = sourcePixels; + this.kernel = kernel; + this.configuration = configuration; + this.preserveAlpha = preserveAlpha; + } - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = targetPixels.GetRowSpan(y).Slice(startX); - PixelOperations.Instance.ToVector4(this.Configuration, targetRowSpan.Slice(0, length), vectorSpan); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + ref Vector4 spanRef = ref MemoryMarshal.GetReference(span); - if (preserveAlpha) - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve3( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } - else - { - for (int x = 0; x < width; x++) - { - DenseMatrixUtils.Convolve4( - in matrix, - source.PixelBuffer, - ref vectorSpanRef, - y, - x, - startY, - maxY, - startX, - maxX); - } - } + Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span); - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, targetRowSpan); - } - }); + if (this.preserveAlpha) + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve3( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } + else + { + for (int x = 0; x < this.bounds.Width; x++) + { + DenseMatrixUtils.Convolve4( + in this.kernel, + this.sourcePixels, + ref spanRef, + y, + x, + this.bounds.Y, + this.maxY, + this.bounds.X, + this.maxX); + } + } - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs index 31c4fad79..8bb60286a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetector2DProcessor{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class EdgeDetector2DProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -52,6 +52,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void BeforeImageApply() { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) + { + opaque.Execute(); + } + if (this.Grayscale) { new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); @@ -63,10 +68,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2DProcessor(this.Configuration, this.KernelX, this.KernelY, true, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index c1897bed8..1b07589b5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -1,12 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Filters; @@ -18,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class EdgeDetectorCompassProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -42,6 +40,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void BeforeImageApply() { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) + { + opaque.Execute(); + } + if (this.Grayscale) { new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); @@ -55,89 +58,77 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { DenseMatrix[] kernels = this.Kernels.Flatten(); - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + // We need a clean copy for each pass to start from + using ImageFrame cleanCopy = source.Clone(); - // we need a clean copy for each pass to start from - using (ImageFrame cleanCopy = source.Clone()) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, interest)) { - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[0], true, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + processor.Apply(source); + } - if (kernels.Length == 1) - { - return; - } + if (kernels.Length == 1) + { + return; + } - int shiftY = startY; - int shiftX = startX; + // Additional runs + for (int i = 1; i < kernels.Length; i++) + { + using ImageFrame pass = cleanCopy.Clone(); - // Reset offset if necessary. - if (minX > 0) + using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, interest)) { - shiftX = 0; + processor.Apply(pass); } - if (minY > 0) - { - shiftY = 0; - } + var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Buffer2D targetPixels; + private readonly Buffer2D passPixels; + private readonly int minX; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Buffer2D targetPixels, + Buffer2D passPixels, + Rectangle bounds) + { + this.targetPixels = targetPixels; + this.passPixels = passPixels; + this.minX = bounds.X; + this.maxX = bounds.Right; + } - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); - // Additional runs. - // ReSharper disable once ForCanBeConvertedToForeach - for (int i = 1; i < kernels.Length; i++) + for (int x = this.minX; x < this.maxX; x++) { - using (ImageFrame pass = cleanCopy.Clone()) - { - using (var processor = new ConvolutionProcessor(this.Configuration, kernels[i], true, this.Source, this.SourceRectangle)) - { - processor.Apply(pass); - } - - Buffer2D passPixels = pass.PixelBuffer; - Buffer2D targetPixels = source.PixelBuffer; - - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - shiftY; - - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - shiftX; - - // Grab the max components of the two pixels - ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); - ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); - - var pixelValue = Vector4.Max( - currentPassPixel.ToVector4(), - currentTargetPixel.ToVector4()); - - currentTargetPixel.FromVector4(pixelValue); - } - } - }); - } + // Grab the max components of the two pixels + ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); + ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); + + var pixelValue = Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); + + currentTargetPixel.FromVector4(pixelValue); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs index eb7f07905..472547765 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor.cs @@ -26,6 +26,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs index ce19ba82d..8ca548d97 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorProcessor{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class EdgeDetectorProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -43,6 +43,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void BeforeImageApply() { + using (IImageProcessor opaque = new OpaqueProcessor(this.Configuration, this.Source, this.SourceRectangle)) + { + opaque.Execute(); + } + if (this.Grayscale) { new GrayscaleBt709Processor(1F).Execute(this.Configuration, this.Source, this.SourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index 9f511a754..d566f6691 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new GaussianBlurProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs index 3c1f82caa..cb77c8741 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class GaussianBlurProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index b1f47863d..4854eae68 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs index f4f27a42d..24c56363a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// /// The pixel format. internal class GaussianSharpenProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -44,10 +44,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution /// protected override void OnFrameApply(ImageFrame source) { - using (var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle)) - { - processor.Apply(source); - } + using var processor = new Convolution2PassProcessor(this.Configuration, this.KernelX, this.KernelY, false, this.Source, this.SourceRectangle); + + processor.Apply(source); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs index 5f03396ba..561892683 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -15,11 +15,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters /// public readonly Vector4[] Parameters; - /// - /// The scaling factor for the kernel values - /// - public readonly float Scale; - /// /// The kernel components to apply the bokeh blur effect /// @@ -29,12 +24,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters /// Initializes a new instance of the struct. /// /// The kernel parameters - /// The kernel scale factor /// The complex kernel components - public BokehBlurKernelData(Vector4[] parameters, float scale, Complex64[][] kernels) + public BokehBlurKernelData(Vector4[] parameters, Complex64[][] kernels) { this.Parameters = parameters; - this.Scale = scale; this.Kernels = kernels; } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs new file mode 100644 index 000000000..f7828fa9e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters +{ + /// + /// Provides parameters to be used in the . + /// + internal static class BokehBlurKernelDataProvider + { + /// + /// The mapping of initialized complex kernels and parameters, to speed up the initialization of new instances + /// + private static readonly ConcurrentDictionary Cache = new ConcurrentDictionary(); + + /// + /// Gets the kernel scales to adjust the component values in each kernel + /// + private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; + + /// + /// Gets the available bokeh blur kernel parameters + /// + private static IReadOnlyList KernelComponents { get; } = new[] + { + // 1 component + new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, + + // 2 components + new[] + { + new Vector4(0.886528f, 5.268909f, 0.411259f, -0.548794f), + new Vector4(1.960518f, 1.558213f, 0.513282f, 4.56111f) + }, + + // 3 components + new[] + { + new Vector4(2.17649f, 5.043495f, 1.621035f, -2.105439f), + new Vector4(1.019306f, 9.027613f, -0.28086f, -0.162882f), + new Vector4(2.81511f, 1.597273f, -0.366471f, 10.300301f) + }, + + // 4 components + new[] + { + new Vector4(4.338459f, 1.553635f, -5.767909f, 46.164397f), + new Vector4(3.839993f, 4.693183f, 9.795391f, -15.227561f), + new Vector4(2.791880f, 8.178137f, -3.048324f, 0.302959f), + new Vector4(1.342190f, 12.328289f, 0.010001f, 0.244650f) + }, + + // 5 components + new[] + { + new Vector4(4.892608f, 1.685979f, -22.356787f, 85.91246f), + new Vector4(4.71187f, 4.998496f, 35.918936f, -28.875618f), + new Vector4(4.052795f, 8.244168f, -13.212253f, -1.578428f), + new Vector4(2.929212f, 11.900859f, 0.507991f, 1.816328f), + new Vector4(1.512961f, 16.116382f, 0.138051f, -0.01f) + }, + + // 6 components + new[] + { + new Vector4(5.143778f, 2.079813f, -82.326596f, 111.231024f), + new Vector4(5.612426f, 6.153387f, 113.878661f, 58.004879f), + new Vector4(5.982921f, 9.802895f, 39.479083f, -162.028887f), + new Vector4(6.505167f, 11.059237f, -71.286026f, 95.027069f), + new Vector4(3.869579f, 14.81052f, 1.405746f, -3.704914f), + new Vector4(2.201904f, 19.032909f, -0.152784f, -0.107988f) + } + }; + + /// + /// Gets the bokeh blur kernel data for the specified parameters. + /// + /// The value representing the size of the area to sample. + /// The size of each kernel to compute. + /// The number of components to use to approximate the original 2D bokeh blur convolution kernel. + /// A instance with the kernel data for the current parameters. + public static BokehBlurKernelData GetBokehBlurKernelData( + int radius, + int kernelSize, + int componentsCount) + { + // Reuse the initialized values from the cache, if possible + var parameters = new BokehBlurParameters(radius, componentsCount); + if (!Cache.TryGetValue(parameters, out BokehBlurKernelData info)) + { + // Initialize the complex kernels and parameters with the current arguments + (Vector4[] kernelParameters, float kernelsScale) = GetParameters(componentsCount); + Complex64[][] kernels = CreateComplexKernels(kernelParameters, radius, kernelSize, kernelsScale); + NormalizeKernels(kernels, kernelParameters); + + // Store them in the cache for future use + info = new BokehBlurKernelData(kernelParameters, kernels); + Cache.TryAdd(parameters, info); + } + + return info; + } + + /// + /// Gets the kernel parameters and scaling factor for the current count value in the current instance + /// + private static (Vector4[] Parameters, float Scale) GetParameters(int componentsCount) + { + // Prepare the kernel components + int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Count)); + + return (KernelComponents[index], KernelScales[index]); + } + + /// + /// Creates the collection of complex 1D kernels with the specified parameters + /// + /// The parameters to use to normalize the kernels + /// The value representing the size of the area to sample. + /// The size of each kernel to compute. + /// The scale factor for each kernel. + private static Complex64[][] CreateComplexKernels( + Vector4[] kernelParameters, + int radius, + int kernelSize, + float kernelsScale) + { + var kernels = new Complex64[kernelParameters.Length][]; + ref Vector4 baseRef = ref MemoryMarshal.GetReference(kernelParameters.AsSpan()); + for (int i = 0; i < kernelParameters.Length; i++) + { + ref Vector4 paramsRef = ref Unsafe.Add(ref baseRef, i); + kernels[i] = CreateComplex1DKernel(radius, kernelSize, kernelsScale, paramsRef.X, paramsRef.Y); + } + + return kernels; + } + + /// + /// Creates a complex 1D kernel with the specified parameters + /// + /// The value representing the size of the area to sample. + /// The size of each kernel to compute. + /// The scale factor for each kernel. + /// The exponential parameter for each complex component + /// The angle component for each complex component + private static Complex64[] CreateComplex1DKernel( + int radius, + int kernelSize, + float kernelsScale, + float a, + float b) + { + var kernel = new Complex64[kernelSize]; + ref Complex64 baseRef = ref MemoryMarshal.GetReference(kernel.AsSpan()); + int r = radius, n = -r; + + for (int i = 0; i < kernelSize; i++, n++) + { + // Incrementally compute the range values + float value = n * kernelsScale * (1f / r); + value *= value; + + // Fill in the complex kernel values + Unsafe.Add(ref baseRef, i) = new Complex64( + MathF.Exp(-a * value) * MathF.Cos(b * value), + MathF.Exp(-a * value) * MathF.Sin(b * value)); + } + + return kernel; + } + + /// + /// Normalizes the kernels with respect to A * real + B * imaginary + /// + /// The current convolution kernels to normalize + /// The parameters to use to normalize the kernels + private static void NormalizeKernels(Complex64[][] kernels, Vector4[] kernelParameters) + { + // Calculate the complex weighted sum + float total = 0; + Span kernelsSpan = kernels.AsSpan(); + ref Complex64[] baseKernelsRef = ref MemoryMarshal.GetReference(kernelsSpan); + ref Vector4 baseParamsRef = ref MemoryMarshal.GetReference(kernelParameters.AsSpan()); + + for (int i = 0; i < kernelParameters.Length; i++) + { + ref Complex64[] kernelRef = ref Unsafe.Add(ref baseKernelsRef, i); + int length = kernelRef.Length; + ref Complex64 valueRef = ref kernelRef[0]; + ref Vector4 paramsRef = ref Unsafe.Add(ref baseParamsRef, i); + + for (int j = 0; j < length; j++) + { + for (int k = 0; k < length; k++) + { + ref Complex64 jRef = ref Unsafe.Add(ref valueRef, j); + ref Complex64 kRef = ref Unsafe.Add(ref valueRef, k); + total += + (paramsRef.Z * ((jRef.Real * kRef.Real) - (jRef.Imaginary * kRef.Imaginary))) + + (paramsRef.W * ((jRef.Real * kRef.Imaginary) + (jRef.Imaginary * kRef.Real))); + } + } + } + + // Normalize the kernels + float scalar = 1f / MathF.Sqrt(total); + for (int i = 0; i < kernelsSpan.Length; i++) + { + ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, i); + int length = kernelsRef.Length; + ref Complex64 valueRef = ref kernelsRef[0]; + + for (int j = 0; j < length; j++) + { + Unsafe.Add(ref valueRef, j) *= scalar; + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs deleted file mode 100644 index 9cf10ce59..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/AtkinsonDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. - /// - /// - public sealed class AtkinsonDiffuser : ErrorDiffuser - { - private const float Divisor = 8F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix AtkinsonMatrix = - new float[,] - { - { 0, 0, 1 / Divisor, 1 / Divisor }, - { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 }, - { 0, 1 / Divisor, 0, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public AtkinsonDiffuser() - : base(AtkinsonMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs deleted file mode 100644 index b7fdfbfe5..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither2x2.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 2x2 Bayer dithering matrix. - /// - public sealed class BayerDither2x2 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither2x2() - : base(2) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs deleted file mode 100644 index 4f6d5dd07..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither4x4.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 4x4 Bayer dithering matrix. - /// - public sealed class BayerDither4x4 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither4x4() - : base(4) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs b/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs deleted file mode 100644 index 8d0c23aa3..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BayerDither8x8.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 8x8 Bayer dithering matrix. - /// - public sealed class BayerDither8x8 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public BayerDither8x8() - : base(8) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs deleted file mode 100644 index 152704ec2..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/BurksDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Burks image dithering algorithm. - /// - /// - public sealed class BurksDiffuser : ErrorDiffuser - { - private const float Divisor = 32F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix BurksMatrix = - new float[,] - { - { 0, 0, 0, 8 / Divisor, 4 / Divisor }, - { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public BurksDiffuser() - : base(BurksMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs new file mode 100644 index 000000000..d39237a2c --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErroDither.KnownTypes.cs @@ -0,0 +1,188 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An error diffusion dithering implementation. + /// + public readonly partial struct ErrorDither + { + /// + /// Applies error diffusion based dithering using the Atkinson image dithering algorithm. + /// + public static ErrorDither Atkinson = CreateAtkinson(); + + /// + /// Applies error diffusion based dithering using the Burks image dithering algorithm. + /// + public static ErrorDither Burkes = CreateBurks(); + + /// + /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. + /// + public static ErrorDither FloydSteinberg = CreateFloydSteinberg(); + + /// + /// Applies error diffusion based dithering using the Jarvis, Judice, Ninke image dithering algorithm. + /// + public static ErrorDither JarvisJudiceNinke = CreateJarvisJudiceNinke(); + + /// + /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. + /// + public static ErrorDither Sierra2 = CreateSierra2(); + + /// + /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. + /// + public static ErrorDither Sierra3 = CreateSierra3(); + + /// + /// Applies error diffusion based dithering using the Sierra Lite image dithering algorithm. + /// + public static ErrorDither SierraLite = CreateSierraLite(); + + /// + /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. + /// + public static ErrorDither StevensonArce = CreateStevensonArce(); + + /// + /// Applies error diffusion based dithering using the Stucki image dithering algorithm. + /// + public static ErrorDither Stucki = CreateStucki(); + + private static ErrorDither CreateAtkinson() + { + const float Divisor = 8F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 1 / Divisor, 1 / Divisor }, + { 1 / Divisor, 1 / Divisor, 1 / Divisor, 0 }, + { 0, 1 / Divisor, 0, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateBurks() + { + const float Divisor = 32F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateFloydSteinberg() + { + const float Divisor = 16F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 7 / Divisor }, + { 3 / Divisor, 5 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateJarvisJudiceNinke() + { + const float Divisor = 48F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 7 / Divisor, 5 / Divisor }, + { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor }, + { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierra2() + { + const float Divisor = 16F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 4 / Divisor, 3 / Divisor }, + { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierra3() + { + const float Divisor = 32F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 5 / Divisor, 3 / Divisor }, + { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor }, + { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateSierraLite() + { + const float Divisor = 4F; + const int Offset = 1; + + var matrix = new float[,] + { + { 0, 0, 2 / Divisor }, + { 1 / Divisor, 1 / Divisor, 0 } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateStevensonArce() + { + const float Divisor = 200F; + const int Offset = 3; + + var matrix = new float[,] + { + { 0, 0, 0, 0, 0, 32 / Divisor, 0 }, + { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor }, + { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 }, + { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + + private static ErrorDither CreateStucki() + { + const float Divisor = 42F; + const int Offset = 2; + + var matrix = new float[,] + { + { 0, 0, 0, 8 / Divisor, 4 / Divisor }, + { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }, + { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor } + }; + + return new ErrorDither(matrix, Offset); + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs deleted file mode 100644 index d6ccfb369..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffuser.cs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// The base class for performing error diffusion based dithering. - /// - public abstract class ErrorDiffuser : IErrorDiffuser - { - private readonly int offset; - private readonly DenseMatrix matrix; - - /// - /// Initializes a new instance of the class. - /// - /// The dithering matrix. - internal ErrorDiffuser(in DenseMatrix matrix) - { - // Calculate the offset position of the pixel relative to - // the diffusion matrix. - this.offset = 0; - - for (int col = 0; col < matrix.Columns; col++) - { - if (matrix[0, col] != 0) - { - this.offset = col - 1; - break; - } - } - - this.matrix = matrix; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY) - where TPixel : struct, IPixel - { - image[x, y] = transformed; - - // Equal? Break out as there's no error to pass. - if (source.Equals(transformed)) - { - return; - } - - // Calculate the error - Vector4 error = source.ToVector4() - transformed.ToVector4(); - this.DoDither(image, x, y, minX, maxX, maxY, error); - } - - [MethodImpl(InliningOptions.ShortMethod)] - private void DoDither(ImageFrame image, int x, int y, int minX, int maxX, int maxY, Vector4 error) - where TPixel : struct, IPixel - { - int offset = this.offset; - DenseMatrix matrix = this.matrix; - - // Loop through and distribute the error amongst neighboring pixels. - for (int row = 0, targetY = y; row < matrix.Rows && targetY < maxY; row++, targetY++) - { - Span rowSpan = image.GetPixelRowSpan(targetY); - - for (int col = 0; col < matrix.Columns; col++) - { - int targetX = x + (col - offset); - if (targetX >= minX && targetX < maxX) - { - float coefficient = matrix[row, col]; - if (coefficient == 0) - { - continue; - } - - ref TPixel pixel = ref rowSpan[targetX]; - var result = pixel.ToVector4(); - - result += error * coefficient; - pixel.FromVector4(result); - } - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs deleted file mode 100644 index 059816065..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Defines a dither operation using error diffusion. - /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. - /// - public sealed class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser) - : this(diffuser, .5F) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold) - : this(diffuser, threshold, Color.WebSafePalette) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffuser - /// The threshold to split the image. Must be between 0 and 1. - /// The palette to select substitute colors from. - public ErrorDiffusionPaletteProcessor(IErrorDiffuser diffuser, float threshold, ReadOnlyMemory palette) - : base(palette) - { - Guard.NotNull(diffuser, nameof(diffuser)); - Guard.MustBeBetweenOrEqualTo(threshold, 0, 1, nameof(threshold)); - - this.Diffuser = diffuser; - this.Threshold = threshold; - } - - /// - /// Gets the error diffuser. - /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the threshold value. - /// - public float Threshold { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - { - return new ErrorDiffusionPaletteProcessor(configuration, this, source, sourceRectangle); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs deleted file mode 100644 index f0c8610ed..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDiffusionPaletteProcessor{TPixel}.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal sealed class ErrorDiffusionPaletteProcessor : PaletteDitherProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ErrorDiffusionPaletteProcessor(Configuration configuration, ErrorDiffusionPaletteProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - { - } - - private new ErrorDiffusionPaletteProcessor Definition => (ErrorDiffusionPaletteProcessor)base.Definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F); - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - PixelPair pair = this.GetClosestPixelPair(ref sourcePixel); - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - pair = this.GetClosestPixelPair(ref sourcePixel); - - // No error to spread, exact match. - if (sourcePixel.Equals(pair.First)) - { - continue; - } - - sourcePixel.ToRgba32(ref rgba); - luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; - this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, endX, endY); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs new file mode 100644 index 000000000..7d30bada6 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -0,0 +1,215 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An error diffusion dithering implementation. + /// + /// + public readonly partial struct ErrorDither : IDither, IEquatable, IEquatable + { + private readonly int offset; + private readonly DenseMatrix matrix; + + /// + /// Initializes a new instance of the struct. + /// + /// The diffusion matrix. + /// The starting offset within the matrix. + [MethodImpl(InliningOptions.ShortMethod)] + public ErrorDither(in DenseMatrix matrix, int offset) + { + this.matrix = matrix; + this.offset = offset; + } + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(IDither left, ErrorDither right) + => right == left; + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(IDither left, ErrorDither right) + => !(right == left); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(ErrorDither left, IDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(ErrorDither left, IDither right) + => !(left == right); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(ErrorDither left, ErrorDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(ErrorDither left, ErrorDither right) + => !(left == right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + int offsetY = bounds.Top; + int offsetX = bounds.Left; + float scale = quantizer.Options.DitherScale; + + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = bounds.Left; x < bounds.Right; x++) + { + TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); + Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); + this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); + } + } + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + float scale = processor.DitherScale; + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + for (int x = bounds.Left; x < bounds.Right; x++) + { + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel transformed = Unsafe.AsRef(processor).GetPaletteColor(sourcePixel); + this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); + sourcePixel = transformed; + } + } + } + + // Internal for AOT + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( + ImageFrame image, + Rectangle bounds, + TPixel source, + TPixel transformed, + int x, + int y, + float scale) + where TPixel : unmanaged, IPixel + { + // Equal? Break out as there's no error to pass. + if (source.Equals(transformed)) + { + return transformed; + } + + // Calculate the error + Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; + + int offset = this.offset; + DenseMatrix matrix = this.matrix; + + // Loop through and distribute the error amongst neighboring pixels. + for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) + { + if (targetY >= bounds.Bottom) + { + continue; + } + + Span rowSpan = image.GetPixelRowSpan(targetY); + + for (int col = 0; col < matrix.Columns; col++) + { + int targetX = x + (col - offset); + if (targetX < bounds.Left || targetX >= bounds.Right) + { + continue; + } + + float coefficient = matrix[row, col]; + if (coefficient == 0) + { + continue; + } + + ref TPixel pixel = ref rowSpan[targetX]; + var result = pixel.ToVector4(); + + result += error * coefficient; + pixel.FromVector4(result); + } + } + + return transformed; + } + + /// + public override bool Equals(object obj) + => obj is ErrorDither dither && this.Equals(dither); + + /// + public bool Equals(ErrorDither other) + => this.offset == other.offset && this.matrix.Equals(other.matrix); + + /// + public bool Equals(IDither other) + => this.Equals((object)other); + + /// + public override int GetHashCode() + => HashCode.Combine(this.offset, this.matrix); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs deleted file mode 100644 index b3137337b..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/FloydSteinbergDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Floyd–Steinberg image dithering algorithm. - /// - /// - public sealed class FloydSteinbergDiffuser : ErrorDiffuser - { - private const float Divisor = 16F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix FloydSteinbergMatrix = - new float[,] - { - { 0, 0, 7 / Divisor }, - { 3 / Divisor, 5 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public FloydSteinbergDiffuser() - : base(FloydSteinbergMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs new file mode 100644 index 000000000..8f9d82537 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IDither.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Defines the contract for types that apply dithering to images. + /// + public interface IDither + { + /// + /// Transforms the quantized image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. + /// + /// The type of frame quantizer. + /// The pixel format. + /// The frame quantizer. + /// The source image. + /// The destination quantized frame. + /// The region of interest bounds. + void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel; + + /// + /// Transforms the image frame applying a dither matrix. + /// This method should be treated as destructive, altering the input pixels. + /// + /// The type of palette dithering processor. + /// The pixel format. + /// The palette dithering processor. + /// The source image. + /// The region of interest bounds. + void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel; + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs deleted file mode 100644 index 8f4381d30..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IErrorDiffuser.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Encapsulates properties and methods required to perform diffused error dithering on an image. - /// - public interface IErrorDiffuser - { - /// - /// Transforms the image applying the dither matrix. This method alters the input pixels array - /// - /// The image - /// The source pixel - /// The transformed pixel - /// The column index. - /// The row index. - /// The minimum column value. - /// The maximum column value. - /// The maximum row value. - /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel transformed, int x, int y, int minX, int maxX, int maxY) - where TPixel : struct, IPixel; - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs deleted file mode 100644 index 571929b99..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/IOrderedDither.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Encapsulates properties and methods required to perform ordered dithering on an image. - /// - public interface IOrderedDither - { - /// - /// Transforms the image applying the dither matrix. This method alters the input pixels array - /// - /// The image - /// The source pixel - /// The color to apply to the pixels above the threshold. - /// The color to apply to the pixels below the threshold. - /// The threshold to split the image. Must be between 0 and 1. - /// The column index. - /// The row index. - /// The pixel format. - void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) - where TPixel : struct, IPixel; - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs new file mode 100644 index 000000000..a8e08fa3f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/IPaletteDitherImageProcessor{TPixel}.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// Implements an algorithm to alter the pixels of an image via palette dithering. + /// + /// The pixel format. + public interface IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + /// + /// Gets the configuration instance to use when performing operations. + /// + Configuration Configuration { get; } + + /// + /// Gets the dithering palette. + /// + ReadOnlyMemory Palette { get; } + + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + float DitherScale { get; } + + /// + /// Returns the color from the dithering palette corresponding to the given color. + /// + /// The color to match. + /// The match. + TPixel GetPaletteColor(TPixel color); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs deleted file mode 100644 index 40cf79266..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/JarvisJudiceNinkeDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the JarvisJudiceNinke image dithering algorithm. - /// - /// - public sealed class JarvisJudiceNinkeDiffuser : ErrorDiffuser - { - private const float Divisor = 48F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix JarvisJudiceNinkeMatrix = - new float[,] - { - { 0, 0, 0, 7 / Divisor, 5 / Divisor }, - { 3 / Divisor, 5 / Divisor, 7 / Divisor, 5 / Divisor, 3 / Divisor }, - { 1 / Divisor, 3 / Divisor, 5 / Divisor, 3 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public JarvisJudiceNinkeDiffuser() - : base(JarvisJudiceNinkeMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs new file mode 100644 index 000000000..f6026a64f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.KnownTypes.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Processing.Processors.Dithering +{ + /// + /// An ordered dithering matrix with equal sides of arbitrary length + /// + public readonly partial struct OrderedDither + { + /// + /// Applies order dithering using the 2x2 Bayer dithering matrix. + /// + public static OrderedDither Bayer2x2 = new OrderedDither(2); + + /// + /// Applies order dithering using the 4x4 Bayer dithering matrix. + /// + public static OrderedDither Bayer4x4 = new OrderedDither(4); + + /// + /// Applies order dithering using the 8x8 Bayer dithering matrix. + /// + public static OrderedDither Bayer8x8 = new OrderedDither(8); + + /// + /// Applies order dithering using the 3x3 ordered dithering matrix. + /// + public static OrderedDither Ordered3x3 = new OrderedDither(3); + } +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 34eff4fe9..6862cff00 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -1,49 +1,282 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// /// An ordered dithering matrix with equal sides of arbitrary length /// - public class OrderedDither : IOrderedDither + public readonly partial struct OrderedDither : IDither, IEquatable, IEquatable { - private readonly DenseMatrix thresholdMatrix; + private readonly DenseMatrix thresholdMatrix; private readonly int modulusX; private readonly int modulusY; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The length of the matrix sides + [MethodImpl(InliningOptions.ShortMethod)] public OrderedDither(uint length) { DenseMatrix ditherMatrix = OrderedDitherFactory.CreateDitherMatrix(length); + + // Create a new matrix to run against, that pre-thresholds the values. + // We don't want to adjust the original matrix generation code as that + // creates known, easy to test values. + // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm + var thresholdMatrix = new DenseMatrix((int)length); + float m2 = length * length; + for (int y = 0; y < length; y++) + { + for (int x = 0; x < length; x++) + { + thresholdMatrix[y, x] = ((ditherMatrix[y, x] + 1) / m2) - .5F; + } + } + this.modulusX = ditherMatrix.Columns; this.modulusY = ditherMatrix.Rows; + this.thresholdMatrix = thresholdMatrix; + } + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(IDither left, OrderedDither right) + => right == left; + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(IDither left, OrderedDither right) + => !(right == left); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(OrderedDither left, IDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(OrderedDither left, IDither right) + => !(left == right); + + /// + /// Compares the two instances to determine whether they are equal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator ==(OrderedDither left, OrderedDither right) + => left.Equals(right); + + /// + /// Compares the two instances to determine whether they are unequal. + /// + /// The first source instance. + /// The second source instance. + /// The . + public static bool operator !=(OrderedDither left, OrderedDither right) + => !(left == right); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyQuantizationDither( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + var ditherOperation = new QuantizeDitherRowOperation( + ref quantizer, + in Unsafe.AsRef(this), + source, + destination, + bounds); + + ParallelRowIterator.IterateRows( + quantizer.Configuration, + bounds, + in ditherOperation); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyPaletteDither( + in TPaletteDitherImageProcessor processor, + ImageFrame source, + Rectangle bounds) + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel + { + var ditherOperation = new PaletteDitherRowOperation( + in processor, + in Unsafe.AsRef(this), + source, + bounds); - // Adjust the matrix range for 0-255 - // TODO: It looks like it's actually possible to dither an image using it's own colors. We should investigate for V2 - // https://stackoverflow.com/questions/12422407/monochrome-dithering-in-javascript-bayer-atkinson-floyd-steinberg - int multiplier = 256 / ditherMatrix.Count; - for (int y = 0; y < ditherMatrix.Rows; y++) + ParallelRowIterator.IterateRows( + processor.Configuration, + bounds, + in ditherOperation); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal TPixel Dither( + TPixel source, + int x, + int y, + int bitDepth, + float scale) + where TPixel : unmanaged, IPixel + { + Rgba32 rgba = default; + source.ToRgba32(ref rgba); + Rgba32 attempt; + + // Spread assumes an even colorspace distribution and precision. + // Calculated as 0-255/component count. 256 / bitDepth + // https://bisqwit.iki.fi/story/howto/dither/jy/ + // https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm + int spread = 256 / bitDepth; + float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; + + attempt.R = (byte)(rgba.R + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.G = (byte)(rgba.G + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.B = (byte)(rgba.B + factor).Clamp(byte.MinValue, byte.MaxValue); + attempt.A = (byte)(rgba.A + factor).Clamp(byte.MinValue, byte.MaxValue); + + TPixel result = default; + result.FromRgba32(attempt); + + return result; + } + + /// + public override bool Equals(object obj) + => obj is OrderedDither dither && this.Equals(dither); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public bool Equals(OrderedDither other) + => this.thresholdMatrix.Equals(other.thresholdMatrix) && this.modulusX == other.modulusX && this.modulusY == other.modulusY; + + /// + public bool Equals(IDither other) + => this.Equals((object)other); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override int GetHashCode() + => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); + + private readonly struct QuantizeDitherRowOperation : IRowOperation + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly IndexedImageFrame destination; + private readonly Rectangle bounds; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public QuantizeDitherRowOperation( + ref TFrameQuantizer quantizer, + in OrderedDither dither, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) { - for (int x = 0; x < ditherMatrix.Columns; x++) + this.quantizer = quantizer; + this.dither = dither; + this.source = source; + this.destination = destination; + this.bounds = bounds; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(destination.Palette.Length); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + float scale = this.quantizer.Options.DitherScale; + + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - ditherMatrix[y, x] = (uint)((ditherMatrix[y, x] + 1) * multiplier) - 1; + TPixel dithered = this.dither.Dither(Unsafe.Add(ref sourceRowRef, x), x, y, this.bitDepth, scale); + Unsafe.Add(ref destinationRowRef, x - offsetX) = Unsafe.AsRef(this.quantizer).GetQuantizedColor(dithered, out TPixel _); } } - - this.thresholdMatrix = ditherMatrix; } - /// - public void Dither(ImageFrame image, TPixel source, TPixel upper, TPixel lower, float threshold, int x, int y) - where TPixel : struct, IPixel + private readonly struct PaletteDitherRowOperation : IRowOperation + where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor + where TPixel : unmanaged, IPixel { - image[x, y] = this.thresholdMatrix[y % this.modulusY, x % this.modulusX] >= threshold ? lower : upper; + private readonly TPaletteDitherImageProcessor processor; + private readonly OrderedDither dither; + private readonly ImageFrame source; + private readonly Rectangle bounds; + private readonly float scale; + private readonly int bitDepth; + + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteDitherRowOperation( + in TPaletteDitherImageProcessor processor, + in OrderedDither dither, + ImageFrame source, + Rectangle bounds) + { + this.processor = processor; + this.dither = dither; + this.source = source; + this.bounds = bounds; + this.scale = processor.DitherScale; + this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(processor.Palette.Span.Length); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); + TPixel dithered = this.dither.Dither(sourcePixel, x, y, this.bitDepth, this.scale); + sourcePixel = Unsafe.AsRef(this.processor).GetPaletteColor(dithered); + } + } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs deleted file mode 100644 index 93bce0578..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither3x3.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies order dithering using the 3x3 dithering matrix. - /// - public sealed class OrderedDither3x3 : OrderedDither - { - /// - /// Initializes a new instance of the class. - /// - public OrderedDither3x3() - : base(3) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs index f4835f421..48aaa22d6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Runtime.CompilerServices; @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering { // Calculate the the logarithm of length to the base 2 uint exponent = 0; - uint bayerLength = 0; + uint bayerLength; do { exponent++; @@ -90,4 +90,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return result; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs deleted file mode 100644 index e28c662f8..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Defines a dithering operation that dithers an image using error diffusion. - /// If no palette is given this will default to the web safe colors defined in the CSS Color Module Level 4. - /// - public sealed class OrderedDitherPaletteProcessor : PaletteDitherProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - public OrderedDitherPaletteProcessor(IOrderedDither dither) - : this(dither, Color.WebSafePalette) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The ordered ditherer. - /// The palette to select substitute colors from. - public OrderedDitherPaletteProcessor(IOrderedDither dither, ReadOnlyMemory palette) - : base(palette) => this.Dither = dither ?? throw new ArgumentNullException(nameof(dither)); - - /// - /// Gets the ditherer. - /// - public IOrderedDither Dither { get; } - - /// - public override IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - => new OrderedDitherPaletteProcessor(configuration, this, source, sourceRectangle); - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs deleted file mode 100644 index 29baa9750..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDitherPaletteProcessor{TPixel}.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// An that dithers an image using error diffusion. - /// - /// The pixel format. - internal class OrderedDitherPaletteProcessor : PaletteDitherProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public OrderedDitherPaletteProcessor(Configuration configuration, OrderedDitherPaletteProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition, source, sourceRectangle) - { - } - - private new OrderedDitherPaletteProcessor Definition => (OrderedDitherPaletteProcessor)base.Definition; - - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startY = interest.Y; - int endY = interest.Bottom; - int startX = interest.X; - int endX = interest.Right; - - // Collect the values before looping so we can reduce our calculation count for identical sibling pixels - TPixel sourcePixel = source[startX, startY]; - TPixel previousPixel = sourcePixel; - PixelPair pair = this.GetClosestPixelPair(ref sourcePixel); - Rgba32 rgba = default; - sourcePixel.ToRgba32(ref rgba); - - // Convert to grayscale using ITU-R Recommendation BT.709 if required - byte luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - for (int y = startY; y < endY; y++) - { - Span row = source.GetPixelRowSpan(y); - - for (int x = startX; x < endX; x++) - { - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - pair = this.GetClosestPixelPair(ref sourcePixel); - - // No error to spread, exact match. - if (sourcePixel.Equals(pair.First)) - { - continue; - } - - sourcePixel.ToRgba32(ref rgba); - luminance = ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); - - // Setup the previous pointer - previousPixel = sourcePixel; - } - - this.Definition.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); - } - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs index 0a1552c11..5ce7ccec0 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor.cs @@ -2,32 +2,78 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// The base class for dither and diffusion processors that consume a palette. + /// Allows the consumption a palette to dither an image. /// - public abstract class PaletteDitherProcessor : IImageProcessor + public sealed class PaletteDitherProcessor : IImageProcessor { /// /// Initializes a new instance of the class. /// + /// The ordered ditherer. + public PaletteDitherProcessor(IDither dither) + : this(dither, QuantizerConstants.MaxDitherScale) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The ordered ditherer. + /// The dithering scale used to adjust the amount of dither. + public PaletteDitherProcessor(IDither dither, float ditherScale) + : this(dither, ditherScale, Color.WebSafePalette) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The dithering algorithm. /// The palette to select substitute colors from. - protected PaletteDitherProcessor(ReadOnlyMemory palette) + public PaletteDitherProcessor(IDither dither, ReadOnlyMemory palette) + : this(dither, QuantizerConstants.MaxDitherScale, palette) { + } + + /// + /// Initializes a new instance of the class. + /// + /// The dithering algorithm. + /// The dithering scale used to adjust the amount of dither. + /// The palette to select substitute colors from. + public PaletteDitherProcessor(IDither dither, float ditherScale, ReadOnlyMemory palette) + { + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + Guard.NotNull(dither, nameof(dither)); + this.Dither = dither; + this.DitherScale = ditherScale.Clamp(QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); this.Palette = palette; } + /// + /// Gets the dithering algorithm to apply to the output image. + /// + public IDither Dither { get; } + + /// + /// Gets the dithering scale used to adjust the amount of dither. Range 0..1. + /// + public float DitherScale { get; } + /// /// Gets the palette to select substitute colors from. /// public ReadOnlyMemory Palette { get; } /// - public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) + where TPixel : unmanaged, IPixel + => new PaletteDitherProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs index c9f09fc62..e0dd4eae1 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs @@ -3,25 +3,22 @@ using System; using System.Buffers; -using System.Collections.Generic; -using System.Numerics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing.Processors.Dithering { /// - /// The base class for dither and diffusion processors that consume a palette. + /// Allows the consumption a palette to dither an image. /// /// The pixel format. - internal abstract class PaletteDitherProcessor : ImageProcessor - where TPixel : struct, IPixel + internal sealed class PaletteDitherProcessor : ImageProcessor + where TPixel : unmanaged, IPixel { - private readonly Dictionary> cache = new Dictionary>(); - private IMemoryOwner palette; - private IMemoryOwner paletteVector; - private bool palleteVectorMapped; + private readonly DitherProcessor ditherProcessor; + private readonly IDither dither; + private IMemoryOwner paletteOwner; private bool isDisposed; /// @@ -31,35 +28,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering /// The defining the processor parameters. /// The source for the current processor instance. /// The source area to process for the current processor instance. - protected PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) + public PaletteDitherProcessor(Configuration configuration, PaletteDitherProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.Definition = definition; - this.palette = this.Configuration.MemoryAllocator.Allocate(definition.Palette.Length); - this.paletteVector = this.Configuration.MemoryAllocator.Allocate(definition.Palette.Length); - } + this.dither = definition.Dither; + + ReadOnlySpan sourcePalette = definition.Palette.Span; + this.paletteOwner = this.Configuration.MemoryAllocator.Allocate(sourcePalette.Length); + Color.ToPixel(this.Configuration, sourcePalette, this.paletteOwner.Memory.Span); - protected PaletteDitherProcessor Definition { get; } + this.ditherProcessor = new DitherProcessor( + this.Configuration, + this.paletteOwner.Memory, + definition.DitherScale); + } /// - protected override void BeforeFrameApply(ImageFrame source) + protected override void OnFrameApply(ImageFrame source) { - // Lazy init palettes: - if (!this.palleteVectorMapped) - { - ReadOnlySpan sourcePalette = this.Definition.Palette.Span; - Color.ToPixel(this.Configuration, sourcePalette, this.palette.Memory.Span); - - PixelOperations.Instance.ToVector4( - this.Configuration, - this.palette.Memory.Span, - this.paletteVector.Memory.Span, - PixelConversionModifiers.Scale); - } - - this.palleteVectorMapped = true; - - base.BeforeFrameApply(source); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + this.dither.ApplyPaletteDither(in this.ditherProcessor, source, interest); } /// @@ -70,74 +58,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering return; } + this.isDisposed = true; if (disposing) { - this.palette?.Dispose(); - this.paletteVector?.Dispose(); + this.paletteOwner.Dispose(); } - this.palette = null; - this.paletteVector = null; - - this.isDisposed = true; + this.paletteOwner = null; base.Dispose(disposing); } /// - /// Returns the two closest colors from the palette calculated via Euclidean distance in the Rgba space. + /// Used to allow inlining of calls to + /// . /// - /// The source color to match. - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected PixelPair GetClosestPixelPair(ref TPixel pixel) + private readonly struct DitherProcessor : IPaletteDitherImageProcessor { - // Check if the color is in the lookup table - if (this.cache.TryGetValue(pixel, out PixelPair value)) + private readonly EuclideanPixelMap pixelMap; + + [MethodImpl(InliningOptions.ShortMethod)] + public DitherProcessor( + Configuration configuration, + ReadOnlyMemory palette, + float ditherScale) { - return value; + this.Configuration = configuration; + this.pixelMap = new EuclideanPixelMap(configuration, palette); + this.Palette = palette; + this.DitherScale = ditherScale; } - return this.GetClosestPixelPairSlow(ref pixel); - } + public Configuration Configuration { get; } - [MethodImpl(MethodImplOptions.NoInlining)] - private PixelPair GetClosestPixelPairSlow(ref TPixel pixel) - { - // Not found - loop through the palette and find the nearest match. - float leastDistance = float.MaxValue; - float secondLeastDistance = float.MaxValue; - var vector = pixel.ToVector4(); + public ReadOnlyMemory Palette { get; } - TPixel closest = default; - TPixel secondClosest = default; - Span paletteSpan = this.palette.Memory.Span; - ref TPixel paletteSpanBase = ref MemoryMarshal.GetReference(paletteSpan); - Span paletteVectorSpan = this.paletteVector.Memory.Span; - ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan); + public float DitherScale { get; } - for (int index = 0; index < paletteVectorSpan.Length; index++) + [MethodImpl(InliningOptions.ShortMethod)] + public TPixel GetPaletteColor(TPixel color) { - ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index); - float distance = Vector4.DistanceSquared(vector, candidate); - - if (distance < leastDistance) - { - leastDistance = distance; - secondClosest = closest; - closest = Unsafe.Add(ref paletteSpanBase, index); - } - else if (distance < secondLeastDistance) - { - secondLeastDistance = distance; - secondClosest = Unsafe.Add(ref paletteSpanBase, index); - } + this.pixelMap.GetClosestColor(color, out TPixel match); + return match; } - - // Pop it into the cache for next time - var pair = new PixelPair(closest, secondClosest); - this.cache.Add(pixel, pair); - - return pair; } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs b/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs deleted file mode 100644 index 13660d30a..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/PixelPair.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Represents a composite pair of pixels. Used for caching color distance lookups. - /// - /// The pixel format. - internal readonly struct PixelPair : IEquatable> - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the struct. - /// - /// The first pixel color - /// The second pixel color - public PixelPair(TPixel first, TPixel second) - { - this.First = first; - this.Second = second; - } - - /// - /// Gets the first pixel color - /// - public TPixel First { get; } - - /// - /// Gets the second pixel color - /// - public TPixel Second { get; } - - /// - public bool Equals(PixelPair other) - => this.First.Equals(other.First) && this.Second.Equals(other.Second); - - /// - public override bool Equals(object obj) - => obj is PixelPair other && this.First.Equals(other.First) && this.Second.Equals(other.Second); - - /// - public override int GetHashCode() => HashCode.Combine(this.First, this.Second); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs deleted file mode 100644 index 001df19af..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra2Diffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Sierra2 image dithering algorithm. - /// - /// - public sealed class Sierra2Diffuser : ErrorDiffuser - { - private const float Divisor = 16F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix Sierra2Matrix = - new float[,] - { - { 0, 0, 0, 4 / Divisor, 3 / Divisor }, - { 1 / Divisor, 2 / Divisor, 3 / Divisor, 2 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public Sierra2Diffuser() - : base(Sierra2Matrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs deleted file mode 100644 index 3e56c63b3..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/Sierra3Diffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Sierra3 image dithering algorithm. - /// - /// - public sealed class Sierra3Diffuser : ErrorDiffuser - { - private const float Divisor = 32F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix Sierra3Matrix = - new float[,] - { - { 0, 0, 0, 5 / Divisor, 3 / Divisor }, - { 2 / Divisor, 4 / Divisor, 5 / Divisor, 4 / Divisor, 2 / Divisor }, - { 0, 2 / Divisor, 3 / Divisor, 2 / Divisor, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public Sierra3Diffuser() - : base(Sierra3Matrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs deleted file mode 100644 index 763695d66..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/SierraLiteDiffuser.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the SierraLite image dithering algorithm. - /// - /// - public sealed class SierraLiteDiffuser : ErrorDiffuser - { - private const float Divisor = 4F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix SierraLiteMatrix = - new float[,] - { - { 0, 0, 2 / Divisor }, - { 1 / Divisor, 1 / Divisor, 0 } - }; - - /// - /// Initializes a new instance of the class. - /// - public SierraLiteDiffuser() - : base(SierraLiteMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs deleted file mode 100644 index 72ff30c11..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StevensonArceDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Stevenson-Arce image dithering algorithm. - /// - public sealed class StevensonArceDiffuser : ErrorDiffuser - { - private const float Divisor = 200F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix StevensonArceMatrix = - new float[,] - { - { 0, 0, 0, 0, 0, 32 / Divisor, 0 }, - { 12 / Divisor, 0, 26 / Divisor, 0, 30 / Divisor, 0, 16 / Divisor }, - { 0, 12 / Divisor, 0, 26 / Divisor, 0, 12 / Divisor, 0 }, - { 5 / Divisor, 0, 12 / Divisor, 0, 12 / Divisor, 0, 5 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public StevensonArceDiffuser() - : base(StevensonArceMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs b/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs deleted file mode 100644 index 78e8fb4e4..000000000 --- a/src/ImageSharp/Processing/Processors/Dithering/StuckiDiffuser.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Dithering -{ - /// - /// Applies error diffusion based dithering using the Stucki image dithering algorithm. - /// - /// - public sealed class StuckiDiffuser : ErrorDiffuser - { - private const float Divisor = 42F; - - /// - /// The diffusion matrix - /// - private static readonly DenseMatrix StuckiMatrix = - new float[,] - { - { 0, 0, 0, 8 / Divisor, 4 / Divisor }, - { 2 / Divisor, 4 / Divisor, 8 / Divisor, 4 / Divisor, 2 / Divisor }, - { 1 / Divisor, 2 / Divisor, 4 / Divisor, 2 / Divisor, 1 / Divisor } - }; - - /// - /// Initializes a new instance of the class. - /// - public StuckiDiffuser() - : base(StuckiMatrix) - { - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf new file mode 100644 index 000000000..42fb22c95 Binary files /dev/null and b/src/ImageSharp/Processing/Processors/Dithering/optimal-parallel-error-diffusion-dithering.pdf differ diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs index 032a0aab0..34a066049 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixelBg : struct, IPixel + where TPixelBg : unmanaged, IPixel { var visitor = new ProcessorFactoryVisitor(configuration, this, source, sourceRectangle); this.Image.AcceptVisitor(visitor); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing } private class ProcessorFactoryVisitor : IImageVisitor - where TPixelBg : struct, IPixel + where TPixelBg : unmanaged, IPixel { private readonly Configuration configuration; private readonly DrawImageProcessor definition; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing public IImageProcessor Result { get; private set; } public void Visit(Image image) - where TPixelFg : struct, IPixel + where TPixelFg : unmanaged, IPixel { this.Result = new DrawImageProcessor( this.configuration, diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index a8b9093e5..fca896929 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Drawing @@ -15,8 +15,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// The pixel format of destination image. /// The pixel format of source image. internal class DrawImageProcessor : ImageProcessor - where TPixelBg : struct, IPixel - where TPixelFg : struct, IPixel + where TPixelBg : unmanaged, IPixel + where TPixelFg : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -99,18 +99,59 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - ParallelHelper.IterateRows( - workingRect, + var operation = new RowOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + ParallelRowIterator.IterateRows( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span background = source.GetPixelRowSpan(y).Slice(minX, width); - Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); - blender.Blend(configuration, background, background, foreground, this.Opacity); - } - }); + workingRect, + in operation); + } + + /// + /// A implementing the draw logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame sourceFrame; + private readonly Image targetImage; + private readonly PixelBlender blender; + private readonly Configuration configuration; + private readonly int minX; + private readonly int width; + private readonly int locationY; + private readonly int targetX; + private readonly float opacity; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + ImageFrame sourceFrame, + Image targetImage, + PixelBlender blender, + Configuration configuration, + int minX, + int width, + int locationY, + int targetX, + float opacity) + { + this.sourceFrame = sourceFrame; + this.targetImage = targetImage; + this.blender = blender; + this.configuration = configuration; + this.minX = minX; + this.width = width; + this.locationY = locationY; + this.targetX = targetX; + this.opacity = opacity; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs new file mode 100644 index 000000000..626ffd716 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/IPixelRowDelegate.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// An used by the row delegates for a given instance + /// + public interface IPixelRowDelegate + { + /// + /// Applies the current pixel row delegate to a target row of preprocessed pixels. + /// + /// The target row of pixels to process. + /// The initial horizontal and vertical offset for the input pixels to process. + void Invoke(Span span, Point offset); + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index a35e4d828..a816b0cb8 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new OilPaintingProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 472c07aa7..5ee1e40de 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -5,9 +5,7 @@ using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -19,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// Adapted from by Dewald Esterhuizen. /// The pixel format. internal class OilPaintingProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly OilPaintingProcessor definition; @@ -45,122 +43,145 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects throw new ArgumentOutOfRangeException(nameof(brushSize)); } - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; - int maxY = endY - 1; - int maxX = endX - 1; - - int radius = brushSize >> 1; - int levels = this.definition.Levels; - int rowWidth = source.Width; - int rectangleWidth = this.SourceRectangle.Width; - - Configuration configuration = this.Configuration; - using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Size()); + source.CopyTo(targetPixels); - var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); - ParallelHelper.IterateRows( - workingRect, + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); + ParallelRowIterator.IterateRowIntervals( this.Configuration, - (rows) => - { - /* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row. - * The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because - * the two allocated buffers have a length equal to the width of the source image, - * and not just equal to the width of the target rectangle to process. - * Furthermore, there are two buffers being allocated in this case, so using that overload would - * have still required the explicit allocation of the secondary buffer. - * Similarly, one temporary float buffer is also allocated from the pool, and that is used - * to create the target bins for all the color channels being processed. - * This buffer is only rented once outside of the main processing loop, and its contents - * are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ - using (IMemoryOwner sourceRowBuffer = configuration.MemoryAllocator.Allocate(rowWidth)) - using (IMemoryOwner targetRowBuffer = configuration.MemoryAllocator.Allocate(rowWidth)) - using (IMemoryOwner bins = configuration.MemoryAllocator.Allocate(levels * 4)) - { - Span sourceRowVector4Span = sourceRowBuffer.Memory.Span; - Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(startX, rectangleWidth); + this.SourceRectangle, + in operation); - Span targetRowVector4Span = targetRowBuffer.Memory.Span; - Span targetRowAreaVector4Span = targetRowVector4Span.Slice(startX, rectangleWidth); - - ref float binsRef = ref bins.GetReference(); - ref int intensityBinRef = ref Unsafe.As(ref binsRef); - ref float redBinRef = ref Unsafe.Add(ref binsRef, levels); - ref float blueBinRef = ref Unsafe.Add(ref redBinRef, levels); - ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, levels); - - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRowPixelSpan = source.GetPixelRowSpan(y); - Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(startX, rectangleWidth); + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + } - PixelOperations.Instance.ToVector4(configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly Buffer2D targetPixels; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly int radius; + private readonly int levels; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + Buffer2D targetPixels, + ImageFrame source, + Configuration configuration, + int radius, + int levels) + { + this.bounds = bounds; + this.targetPixels = targetPixels; + this.source = source; + this.configuration = configuration; + this.radius = radius; + this.levels = levels; + } - for (int x = startX; x < endX; x++) - { - int maxIntensity = 0; - int maxIndex = 0; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int maxY = this.bounds.Bottom - 1; + int maxX = this.bounds.Right - 1; + + /* Allocate the two temporary Vector4 buffers, one for the source row and one for the target row. + * The ParallelHelper.IterateRowsWithTempBuffers overload is not used in this case because + * the two allocated buffers have a length equal to the width of the source image, + * and not just equal to the width of the target rectangle to process. + * Furthermore, there are two buffers being allocated in this case, so using that overload would + * have still required the explicit allocation of the secondary buffer. + * Similarly, one temporary float buffer is also allocated from the pool, and that is used + * to create the target bins for all the color channels being processed. + * This buffer is only rented once outside of the main processing loop, and its contents + * are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ + using IMemoryOwner sourceRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); + using IMemoryOwner targetRowBuffer = this.configuration.MemoryAllocator.Allocate(this.source.Width); + using IMemoryOwner bins = this.configuration.MemoryAllocator.Allocate(this.levels * 4); + + Span sourceRowVector4Span = sourceRowBuffer.Memory.Span; + Span sourceRowAreaVector4Span = sourceRowVector4Span.Slice(this.bounds.X, this.bounds.Width); + + Span targetRowVector4Span = targetRowBuffer.Memory.Span; + Span targetRowAreaVector4Span = targetRowVector4Span.Slice(this.bounds.X, this.bounds.Width); + + ref float binsRef = ref bins.GetReference(); + ref int intensityBinRef = ref Unsafe.As(ref binsRef); + ref float redBinRef = ref Unsafe.Add(ref binsRef, this.levels); + ref float blueBinRef = ref Unsafe.Add(ref redBinRef, this.levels); + ref float greenBinRef = ref Unsafe.Add(ref blueBinRef, this.levels); + + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - // Clear the current shared buffer before processing each target pixel - bins.Memory.Span.Clear(); + PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); - for (int fy = 0; fy <= radius; fy++) - { - int fyr = fy - radius; - int offsetY = y + fyr; + for (int x = this.bounds.X; x < this.bounds.Right; x++) + { + int maxIntensity = 0; + int maxIndex = 0; - offsetY = offsetY.Clamp(0, maxY); + // Clear the current shared buffer before processing each target pixel + bins.Memory.Span.Clear(); - Span sourceOffsetRow = source.GetPixelRowSpan(offsetY); + for (int fy = 0; fy <= this.radius; fy++) + { + int fyr = fy - this.radius; + int offsetY = y + fyr; - for (int fx = 0; fx <= radius; fx++) - { - int fxr = fx - radius; - int offsetX = x + fxr; - offsetX = offsetX.Clamp(0, maxX); + offsetY = offsetY.Clamp(0, maxY); - var vector = sourceOffsetRow[offsetX].ToVector4(); + Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); - float sourceRed = vector.X; - float sourceBlue = vector.Z; - float sourceGreen = vector.Y; + for (int fx = 0; fx <= this.radius; fx++) + { + int fxr = fx - this.radius; + int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); - int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); + var vector = sourceOffsetRow[offsetX].ToVector4(); - Unsafe.Add(ref intensityBinRef, currentIntensity)++; - Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; - Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; - Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) - { - maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); - maxIndex = currentIntensity; - } - } + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (this.levels - 1)); - float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity); - float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity); - float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity); - float alpha = sourceRowVector4Span[x].W; + Unsafe.Add(ref intensityBinRef, currentIntensity)++; + Unsafe.Add(ref redBinRef, currentIntensity) += sourceRed; + Unsafe.Add(ref blueBinRef, currentIntensity) += sourceBlue; + Unsafe.Add(ref greenBinRef, currentIntensity) += sourceGreen; - targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); + if (Unsafe.Add(ref intensityBinRef, currentIntensity) > maxIntensity) + { + maxIntensity = Unsafe.Add(ref intensityBinRef, currentIntensity); + maxIndex = currentIntensity; } } - Span targetRowAreaPixelSpan = targetPixels.GetRowSpan(y).Slice(startX, rectangleWidth); + float red = MathF.Abs(Unsafe.Add(ref redBinRef, maxIndex) / maxIntensity); + float blue = MathF.Abs(Unsafe.Add(ref blueBinRef, maxIndex) / maxIntensity); + float green = MathF.Abs(Unsafe.Add(ref greenBinRef, maxIndex) / maxIntensity); + float alpha = sourceRowVector4Span[x].W; - PixelOperations.Instance.FromVector4Destructive(configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + targetRowVector4Span[x] = new Vector4(red, green, blue, alpha); } } - }); - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); + Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs index 5bdc0bc80..3721afee3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -33,7 +36,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new PixelRowDelegateProcessor(configuration, this, source, sourceRectangle); + where TPixel : unmanaged, IPixel + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } + + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; + + [MethodImpl(InliningOptions.ShortMethod)] + public PixelRowDelegate(PixelRowOperation pixelRowOperation) + { + this.pixelRowOperation = pixelRowOperation; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(Span span, Point offset) => this.pixelRowOperation(span); + } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs deleted file mode 100644 index 019509dc2..000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessorBase{TPixel}.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects -{ - /// - /// The base class for all processors that accept a user defined row processing delegate. - /// - /// The pixel format. - internal abstract class PixelRowDelegateProcessorBase : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// The to apply during the pixel conversions. - /// - private readonly PixelConversionModifiers modifiers; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The to apply during the pixel conversions. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - protected PixelRowDelegateProcessorBase(Configuration configuration, PixelConversionModifiers modifiers, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - => this.modifiers = modifiers; - - /// - protected override void OnFrameApply(ImageFrame source) - { - var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startX = interest.X; - Configuration configuration = this.Configuration; - PixelConversionModifiers modifiers = this.modifiers; - - ParallelHelper.IterateRowsWithTempBuffer( - interest, - this.Configuration, - (rows, vectorBuffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); - PixelOperations.Instance.ToVector4(configuration, rowSpan, vectorSpan, modifiers); - - // Run the user defined pixel shader to the current row of pixels - this.ApplyPixelRowDelegate(vectorSpan, new Point(startX, y)); - - PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan, modifiers); - } - }); - } - - /// - /// Applies the current pixel row delegate to a target row of preprocessed pixels. - /// - /// The target row of pixels to process. - /// The initial horizontal and vertical offset for the input pixels to process. - protected abstract void ApplyPixelRowDelegate(Span span, Point offset); - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs new file mode 100644 index 000000000..71259a618 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Effects +{ + /// + /// The base class for all processors that accept a user defined row processing delegate. + /// + /// The pixel format. + /// The row processor type. + internal sealed class PixelRowDelegateProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + where TDelegate : struct, IPixelRowDelegate + { + private readonly TDelegate rowDelegate; + + /// + /// The to apply during the pixel conversions. + /// + private readonly PixelConversionModifiers modifiers; + + /// + /// Initializes a new instance of the class. + /// + /// The row processor to use to process each pixel row + /// The configuration which allows altering default behaviour or extending the library. + /// The to apply during the pixel conversions. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public PixelRowDelegateProcessor( + in TDelegate rowDelegate, + Configuration configuration, + PixelConversionModifiers modifiers, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.rowDelegate = rowDelegate; + this.modifiers = modifiers; + } + + /// + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + var operation = new RowOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); + + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in operation); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly int startX; + private readonly ImageFrame source; + private readonly Configuration configuration; + private readonly PixelConversionModifiers modifiers; + private readonly TDelegate rowProcessor; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + ImageFrame source, + Configuration configuration, + PixelConversionModifiers modifiers, + in TDelegate rowProcessor) + { + this.startX = startX; + this.source = source; + this.configuration = configuration; + this.modifiers = modifiers; + this.rowProcessor = rowProcessor; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); + + // Run the user defined pixel shader to the current row of pixels + Unsafe.AsRef(this.rowProcessor).Invoke(span, new Point(this.startX, y)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, this.modifiers); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs deleted file mode 100644 index da917eaf3..000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel}.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects -{ - /// - /// Applies a user defined row processing delegate to the image. - /// - /// The pixel format. - internal sealed class PixelRowDelegateProcessor : PixelRowDelegateProcessorBase - where TPixel : struct, IPixel - { - /// - /// The user defined pixel row processing delegate. - /// - private readonly PixelRowOperation pixelRowOperation; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PixelRowDelegateProcessor(Configuration configuration, PixelRowDelegateProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition.Modifiers, source, sourceRectangle) - { - this.pixelRowOperation = definition.PixelRowOperation; - } - - /// - protected override void ApplyPixelRowDelegate(Span span, Point offset) => this.pixelRowOperation(span); - } -} diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index a71f8424f..ba43ca617 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new PixelateProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index df85afc5e..60f5d29b0 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -3,10 +3,9 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading.Tasks; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Common; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// /// The pixel format. internal class PixelateProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly PixelateProcessor definition; @@ -29,86 +28,76 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// The source area to process for the current processor instance. public PixelateProcessor(Configuration configuration, PixelateProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } + => this.definition = definition; private int Size => this.definition.Size; /// protected override void OnFrameApply(ImageFrame source) { - if (this.Size <= 0 || this.Size > source.Height || this.Size > source.Width) - { - throw new ArgumentOutOfRangeException(nameof(this.Size)); - } - - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); int size = this.Size; - int offset = this.Size / 2; - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Guard.MustBeBetweenOrEqualTo(size, 0, interest.Width, nameof(size)); + Guard.MustBeBetweenOrEqualTo(size, 0, interest.Height, nameof(size)); - // Reset offset if necessary. - if (minX > 0) + // Get the range on the y-plane to choose from. + // TODO: It would be nice to be able to pool this somehow but neither Memory nor Span + // implement IEnumerable. + IEnumerable range = EnumerableExtensions.SteppedRange(interest.Y, i => i < interest.Bottom, size); + Parallel.ForEach( + range, + this.Configuration.GetParallelOptions(), + new RowOperation(interest, size, source).Invoke); + } + + private readonly struct RowOperation + { + private readonly int minX; + private readonly int maxX; + private readonly int maxXIndex; + private readonly int maxY; + private readonly int maxYIndex; + private readonly int size; + private readonly int radius; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Rectangle bounds, + int size, + ImageFrame source) { - startX = 0; + this.minX = bounds.X; + this.maxX = bounds.Right; + this.maxXIndex = bounds.Right - 1; + this.maxY = bounds.Bottom; + this.maxYIndex = bounds.Bottom - 1; + this.size = size; + this.radius = size >> 1; + this.source = source; } - if (minY > 0) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - startY = 0; - } + Span rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex)); - // Get the range on the y-plane to choose from. - IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); + for (int x = this.minX; x < this.maxX; x += this.size) + { + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = rowSpan[Math.Min(x + this.radius, this.maxXIndex)]; - Parallel.ForEach( - range, - this.Configuration.GetParallelOptions(), - y => + // For each pixel in the pixelate size, set it to the centre color. + for (int oY = y; oY < y + this.size && oY < this.maxY; oY++) { - int offsetY = y - startY; - int offsetPy = offset; - - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) + for (int oX = x; oX < x + this.size && oX < this.maxX; oX++) { - offsetPy--; + this.source[oX, oY] = pixel; } - - Span row = source.GetPixelRowSpan(offsetY + offsetPy); - - for (int x = minX; x < maxX; x += size) - { - int offsetX = x - startX; - int offsetPx = offset; - - while (x + offsetPx >= maxX) - { - offsetPx--; - } - - // Get the pixel color in the centre of the soon to be pixelated area. - TPixel pixel = row[offsetX + offsetPx]; - - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) - { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) - { - source[k, l] = pixel; - } - } - } - }); + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs index bf21f5b9b..6822daa42 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -33,7 +36,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel - => new PositionAwarePixelRowDelegateProcessor(configuration, this, source, sourceRectangle); + where TPixel : unmanaged, IPixel + { + return new PixelRowDelegateProcessor( + new PixelRowDelegate(this.PixelRowOperation), + configuration, + this.Modifiers, + source, + sourceRectangle); + } + + /// + /// A implementing the row processing logic for . + /// + public readonly struct PixelRowDelegate : IPixelRowDelegate + { + private readonly PixelRowOperation pixelRowOperation; + + [MethodImpl(InliningOptions.ShortMethod)] + public PixelRowDelegate(PixelRowOperation pixelRowOperation) + { + this.pixelRowOperation = pixelRowOperation; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(Span span, Point offset) => this.pixelRowOperation(span, offset); + } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs deleted file mode 100644 index 901a3a985..000000000 --- a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelRowDelegateProcessor{TPixel}.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Effects -{ - /// - /// Applies a user defined, position aware, row processing delegate to the image. - /// - /// The pixel format. - internal sealed class PositionAwarePixelRowDelegateProcessor : PixelRowDelegateProcessorBase - where TPixel : struct, IPixel - { - private readonly PixelRowOperation pixelRowOperation; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public PositionAwarePixelRowDelegateProcessor(Configuration configuration, PositionAwarePixelRowDelegateProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, definition.Modifiers, source, sourceRectangle) - { - this.pixelRowOperation = definition.PixelRowOperation; - } - - /// - protected override void ApplyPixelRowDelegate(Span span, Point offset) => this.pixelRowOperation(span, offset); - } -} diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs index 1542c6836..5cae98568 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// public virtual IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new FilterProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 28a5837de..dee9d2ff6 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -3,8 +3,9 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Filters @@ -14,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// /// The pixel format. internal class FilterProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly FilterProcessor definition; @@ -35,27 +36,48 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - int startX = interest.X; + var operation = new RowOperation(interest.X, source, this.definition.Matrix, this.Configuration); - ColorMatrix matrix = this.definition.Matrix; - - ParallelHelper.IterateRowsWithTempBuffer( - interest, + ParallelRowIterator.IterateRows( this.Configuration, - (rows, vectorBuffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span vectorSpan = vectorBuffer.Span; - int length = vectorSpan.Length; - Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length); - PixelOperations.Instance.ToVector4(this.Configuration, rowSpan, vectorSpan); - - Vector4Utils.Transform(vectorSpan, ref matrix); - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, vectorSpan, rowSpan); - } - }); + interest, + in operation); + } + + /// + /// A implementing the convolution logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly int startX; + private readonly ImageFrame source; + private readonly ColorMatrix matrix; + private readonly Configuration configuration; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + int startX, + ImageFrame source, + ColorMatrix matrix, + Configuration configuration) + { + this.startX = startX; + this.source = source; + this.matrix = matrix; + this.configuration = configuration; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span); + + Vector4Utilities.Transform(span, ref Unsafe.AsRef(this.matrix)); + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs index 8e3759fba..5a19b7851 100644 --- a/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/LomographProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// Converts the colors of the image recreating an old Lomograph effect. /// internal class LomographProcessor : FilterProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private static readonly Color VeryDarkGreen = Color.FromRgba(0, 10, 0, 255); diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs new file mode 100644 index 000000000..95a099106 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Filters +{ + internal sealed class OpaqueProcessor : ImageProcessor + where TPixel : unmanaged, IPixel + { + public OpaqueProcessor( + Configuration configuration, + Image source, + Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + } + + protected override void OnFrameApply(ImageFrame source) + { + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + var operation = new OpaqueRowOperation(this.Configuration, source, interest); + ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); + } + + private readonly struct OpaqueRowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly ImageFrame target; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public OpaqueRowOperation( + Configuration configuration, + ImageFrame target, + Rectangle bounds) + { + this.configuration = configuration; + this.target = target; + this.bounds = bounds; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Span targetRowSpan = this.target.GetPixelRowSpan(y).Slice(this.bounds.X); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Scale); + ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref Vector4 v = ref Unsafe.Add(ref baseRef, x); + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs index 24ee16296..9f547be1c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters /// Converts the colors of the image recreating an old Polaroid effect. /// internal class PolaroidProcessor : FilterProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private static readonly Color LightOrange = Color.FromRgba(255, 153, 102, 128); private static readonly Color VeryDarkOrange = Color.FromRgb(102, 34, 0); diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs index ad8051e6b..2f12e065b 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor.cs @@ -22,6 +22,6 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs index 84b126229..988d6d866 100644 --- a/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ICloningImageProcessor{TPixel}.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public interface ICloningImageProcessor : IImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Clones the specified and executes the process against the clone. diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor.cs b/src/ImageSharp/Processing/Processors/IImageProcessor.cs index a9d5b20ec..9d2e301de 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor.cs @@ -25,6 +25,6 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; } } diff --git a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs index 1b874e4b9..4361936d1 100644 --- a/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/IImageProcessor{TPixel}.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public interface IImageProcessor : IDisposable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Executes the process against the specified . diff --git a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs index 6f486e74b..7e72c7bf0 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessorExtensions.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing.Processors } public void Visit(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (IImageProcessor processorImpl = this.processor.CreatePixelSpecificProcessor(this.configuration, image, this.sourceRectangle)) { diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index be8bc8e12..964a88d6a 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors /// /// The pixel format. public abstract class ImageProcessor : IImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index b5b8cfe56..1547de8ac 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -7,8 +7,7 @@ using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Threading.Tasks; - +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -20,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -81,56 +80,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } - Parallel.For( - 0, - tileYStartPositions.Count, - new ParallelOptions { MaxDegreeOfParallelism = this.Configuration.MaxDegreeOfParallelism }, - index => - { - int y = tileYStartPositions[index].y; - int cdfYY = tileYStartPositions[index].cdfY; - - // It's unfortunate that we have to do this per iteration. - ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - - int cdfX = 0; - int x = halfTileWidth; - for (int tile = 0; tile < tileCount - 1; tile++) - { - int tileY = 0; - int yEnd = Math.Min(y + tileHeight, sourceHeight); - int xEnd = Math.Min(x + tileWidth, sourceWidth); - for (int dy = y; dy < yEnd; dy++) - { - int dyOffSet = dy * sourceWidth; - int tileX = 0; - for (int dx = x; dx < xEnd; dx++) - { - ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); - float luminanceEqualized = InterpolateBetweenFourTiles( - pixel, - cdfData, - tileCount, - tileCount, - tileX, - tileY, - cdfX, - cdfYY, - tileWidth, - tileHeight, - luminanceLevels); - - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - tileX++; - } - - tileY++; - } - - cdfX++; - x += tileWidth; - } - }); + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); + ParallelRowIterator.IterateRowIntervals( + this.Configuration, + new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), + in operation); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); @@ -416,6 +370,95 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private static float LinearInterpolation(float left, float right, float t) => left + ((right - left) * t); + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly CdfTileData cdfData; + private readonly List<(int y, int cdfY)> tileYStartPositions; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int tileCount; + private readonly int halfTileWidth; + private readonly int luminanceLevels; + private readonly ImageFrame source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + CdfTileData cdfData, + List<(int y, int cdfY)> tileYStartPositions, + int tileWidth, + int tileHeight, + int tileCount, + int halfTileWidth, + int luminanceLevels, + ImageFrame source) + { + this.cdfData = cdfData; + this.tileYStartPositions = tileYStartPositions; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.tileCount = tileCount; + this.halfTileWidth = halfTileWidth; + this.luminanceLevels = luminanceLevels; + this.source = source; + this.sourceWidth = source.Width; + this.sourceHeight = source.Height; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + (int y, int cdfY) tileYStartPosition = this.tileYStartPositions[index]; + int y = tileYStartPosition.y; + int cdfYY = tileYStartPosition.cdfY; + + int cdfX = 0; + int x = this.halfTileWidth; + for (int tile = 0; tile < this.tileCount - 1; tile++) + { + int tileY = 0; + int yEnd = Math.Min(y + this.tileHeight, this.sourceHeight); + int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth); + for (int dy = y; dy < yEnd; dy++) + { + int dyOffSet = dy * this.sourceWidth; + int tileX = 0; + for (int dx = x; dx < xEnd; dx++) + { + ref TPixel pixel = ref Unsafe.Add(ref sourceBase, dyOffSet + dx); + float luminanceEqualized = InterpolateBetweenFourTiles( + pixel, + this.cdfData, + this.tileCount, + this.tileCount, + tileX, + tileY, + cdfX, + cdfYY, + this.tileWidth, + this.tileHeight, + this.luminanceLevels); + + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + tileX++; + } + + tileY++; + } + + cdfX++; + x += this.tileWidth; + } + } + } + } + /// /// Contains the results of the cumulative distribution function for all tiles. /// @@ -431,7 +474,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly Buffer2D cdfLutBuffer2D; private readonly int pixelsInTile; private readonly int sourceWidth; - private readonly int sourceHeight; private readonly int tileWidth; private readonly int tileHeight; private readonly int luminanceLevels; @@ -453,7 +495,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.cdfMinBuffer2D = this.memoryAllocator.Allocate2D(tileCountX, tileCountY); this.cdfLutBuffer2D = this.memoryAllocator.Allocate2D(tileCountX * luminanceLevels, tileCountY); this.sourceWidth = sourceWidth; - this.sourceHeight = sourceHeight; this.tileWidth = tileWidth; this.tileHeight = tileHeight; this.pixelsInTile = tileWidth * tileHeight; @@ -470,57 +511,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void CalculateLookupTables(ImageFrame source, HistogramEqualizationProcessor processor) { - int sourceWidth = this.sourceWidth; - int sourceHeight = this.sourceHeight; - int tileWidth = this.tileWidth; - int tileHeight = this.tileHeight; - int luminanceLevels = this.luminanceLevels; - - Parallel.For( - 0, - this.tileYStartPositions.Count, - new ParallelOptions { MaxDegreeOfParallelism = this.configuration.MaxDegreeOfParallelism }, - index => - { - int cdfX = 0; - int cdfY = this.tileYStartPositions[index].cdfY; - int y = this.tileYStartPositions[index].y; - int endY = Math.Min(y + tileHeight, sourceHeight); - ref TPixel sourceBase = ref source.GetPixelReference(0, 0); - ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); - - using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(luminanceLevels)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - for (int x = 0; x < sourceWidth; x += tileWidth) - { - histogram.Clear(); - ref int cdfBase = ref MemoryMarshal.GetReference(this.GetCdfLutSpan(cdfX, index)); - - int xlimit = Math.Min(x + tileWidth, sourceWidth); - for (int dy = y; dy < endY; dy++) - { - int dyOffset = dy * sourceWidth; - for (int dx = x; dx < xlimit; dx++) - { - int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), luminanceLevels); - histogram[luminance]++; - } - } - - if (processor.ClipHistogramEnabled) - { - processor.ClipHistogram(histogram, processor.ClipLimit); - } - - Unsafe.Add(ref cdfMinBase, cdfX) = processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - cdfX++; - } - } - }); + var operation = new RowIntervalOperation( + processor, + this.memoryAllocator, + this.cdfMinBuffer2D, + this.cdfLutBuffer2D, + this.tileYStartPositions, + this.tileWidth, + this.tileHeight, + this.luminanceLevels, + source); + + ParallelRowIterator.IterateRowIntervals( + this.configuration, + new Rectangle(0, 0, this.sourceWidth, this.tileYStartPositions.Count), + in operation); } [MethodImpl(InliningOptions.ShortMethod)] @@ -548,6 +553,93 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.cdfMinBuffer2D.Dispose(); this.cdfLutBuffer2D.Dispose(); } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly HistogramEqualizationProcessor processor; + private readonly MemoryAllocator allocator; + private readonly Buffer2D cdfMinBuffer2D; + private readonly Buffer2D cdfLutBuffer2D; + private readonly List<(int y, int cdfY)> tileYStartPositions; + private readonly int tileWidth; + private readonly int tileHeight; + private readonly int luminanceLevels; + private readonly ImageFrame source; + private readonly int sourceWidth; + private readonly int sourceHeight; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + HistogramEqualizationProcessor processor, + MemoryAllocator allocator, + Buffer2D cdfMinBuffer2D, + Buffer2D cdfLutBuffer2D, + List<(int y, int cdfY)> tileYStartPositions, + int tileWidth, + int tileHeight, + int luminanceLevels, + ImageFrame source) + { + this.processor = processor; + this.allocator = allocator; + this.cdfMinBuffer2D = cdfMinBuffer2D; + this.cdfLutBuffer2D = cdfLutBuffer2D; + this.tileYStartPositions = tileYStartPositions; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.luminanceLevels = luminanceLevels; + this.source = source; + this.sourceWidth = source.Width; + this.sourceHeight = source.Height; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ref TPixel sourceBase = ref this.source.GetPixelReference(0, 0); + + for (int index = rows.Min; index < rows.Max; index++) + { + int cdfX = 0; + int cdfY = this.tileYStartPositions[index].cdfY; + int y = this.tileYStartPositions[index].y; + int endY = Math.Min(y + this.tileHeight, this.sourceHeight); + ref int cdfMinBase = ref MemoryMarshal.GetReference(this.cdfMinBuffer2D.GetRowSpan(cdfY)); + + using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + for (int x = 0; x < this.sourceWidth; x += this.tileWidth) + { + histogram.Clear(); + Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); + ref int cdfBase = ref MemoryMarshal.GetReference(cdfLutSpan); + + int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); + for (int dy = y; dy < endY; dy++) + { + int dyOffset = dy * this.sourceWidth; + for (int dx = x; dx < xlimit; dx++) + { + int luminance = GetLuminance(Unsafe.Add(ref sourceBase, dyOffset + dx), this.luminanceLevels); + histogram[luminance]++; + } + } + + if (this.processor.ClipHistogramEnabled) + { + this.processor.ClipHistogram(histogram, this.processor.ClipLimit); + } + + Unsafe.Add(ref cdfMinBase, cdfX) = this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + cdfX++; + } + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index 987e4e392..cce527ad4 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : HistogramEqualizationProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -66,191 +66,101 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int halfTileWidth = halfTileHeight; var slidingWindowInfos = new SlidingWindowInfos(tileWidth, tileHeight, halfTileWidth, halfTileHeight, pixelInTile); - using (Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height)) - { - // Process the inner tiles, which do not require to check the borders. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: halfTileHeight, - yEnd: source.Height - halfTileHeight, - useFastPath: true, - this.Configuration)); - - // Process the left border of the image. - Parallel.For( - 0, - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - // Process the right border of the image. - Parallel.For( - source.Width - halfTileWidth, - source.Width, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - // Process the top border of the image. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: 0, - yEnd: halfTileHeight, - useFastPath: false, - this.Configuration)); - - // Process the bottom border of the image. - Parallel.For( - halfTileWidth, - source.Width - halfTileWidth, - parallelOptions, - this.ProcessSlidingWindow( - source, - memoryAllocator, - targetPixels, - slidingWindowInfos, - yStart: source.Height - halfTileHeight, - yEnd: source.Height, - useFastPath: false, - this.Configuration)); - - Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); - } - } - - /// - /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. - /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and - /// adding a new row at the bottom. - /// - /// The source image. - /// The memory allocator. - /// The target pixels. - /// about the sliding window dimensions. - /// The y start position. - /// The y end position. - /// if set to true the borders of the image will not be checked. - /// The configuration. - /// Action Delegate. - private Action ProcessSlidingWindow( - ImageFrame source, - MemoryAllocator memoryAllocator, - Buffer2D targetPixels, - SlidingWindowInfos swInfos, - int yStart, - int yEnd, - bool useFastPath, - Configuration configuration) - { - return x => - { - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner histogramBufferCopy = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner pixelRowBuffer = memoryAllocator.Allocate(swInfos.TileWidth, AllocationOptions.Clean)) - { - Span histogram = histogramBuffer.GetSpan(); - ref int histogramBase = ref MemoryMarshal.GetReference(histogram); - - Span histogramCopy = histogramBufferCopy.GetSpan(); - ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); - - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - - Span pixelRow = pixelRowBuffer.GetSpan(); - ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); - - // Build the initial histogram of grayscale values. - for (int dy = yStart - swInfos.HalfTileHeight; dy < yStart + swInfos.HalfTileHeight; dy++) - { - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, dy, swInfos.TileWidth, configuration); - } - - this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - } - - for (int y = yStart; y < yEnd; y++) - { - if (this.ClipHistogramEnabled) - { - // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. - histogram.CopyTo(histogramCopy); - this.ClipHistogram(histogramCopy, this.ClipLimit); - } - - // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. - int cdfMin = this.ClipHistogramEnabled - ? this.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) - : this.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = swInfos.PixelInTile - cdfMin; - - // Map the current pixel to the new equalized value. - int luminance = GetLuminance(source[x, y], this.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; - targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, source[x, y].ToVector4().W)); - - // Remove top most row from the histogram, mirroring rows which exceeds the borders. - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y - swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - - this.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - - // Add new bottom row to the histogram, mirroring rows which exceeds the borders. - if (useFastPath) - { - this.CopyPixelRowFast(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - else - { - this.CopyPixelRow(source, pixelRow, x - swInfos.HalfTileWidth, y + swInfos.HalfTileWidth, swInfos.TileWidth, configuration); - } - - this.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.LuminanceLevels, pixelRow.Length); - } - } - }; + // TODO: If the process was able to be switched to operate in parallel rows instead of columns + // then we could take advantage of batching and allocate per-row buffers only once per batch. + using Buffer2D targetPixels = this.Configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); + + // Process the inner tiles, which do not require to check the borders. + var innerOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: halfTileHeight, + yEnd: source.Height - halfTileHeight, + useFastPath: true); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + innerOperation.Invoke); + + // Process the left border of the image. + var leftBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + 0, + halfTileWidth, + parallelOptions, + leftBorderOperation.Invoke); + + // Process the right border of the image. + var rightBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + source.Width - halfTileWidth, + source.Width, + parallelOptions, + rightBorderOperation.Invoke); + + // Process the top border of the image. + var topBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: 0, + yEnd: halfTileHeight, + useFastPath: false); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + topBorderOperation.Invoke); + + // Process the bottom border of the image. + var bottomBorderOperation = new SlidingWindowOperation( + this.Configuration, + this, + source, + memoryAllocator, + targetPixels, + slidingWindowInfos, + yStart: source.Height - halfTileHeight, + yEnd: source.Height, + useFastPath: false); + + Parallel.For( + halfTileWidth, + source.Width - halfTileWidth, + parallelOptions, + bottomBorderOperation.Invoke); + + Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); } /// @@ -371,6 +281,141 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } } + /// + /// Applies the sliding window equalization to one column of the image. The window is moved from top to bottom. + /// Moving the window one pixel down requires to remove one row from the top of the window from the histogram and + /// adding a new row at the bottom. + /// + private readonly struct SlidingWindowOperation + { + private readonly Configuration configuration; + private readonly AdaptiveHistogramEqualizationSlidingWindowProcessor processor; + private readonly ImageFrame source; + private readonly MemoryAllocator memoryAllocator; + private readonly Buffer2D targetPixels; + private readonly SlidingWindowInfos swInfos; + private readonly int yStart; + private readonly int yEnd; + private readonly bool useFastPath; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration. + /// The histogram processor. + /// The source image. + /// The memory allocator. + /// The target pixels. + /// about the sliding window dimensions. + /// The y start position. + /// The y end position. + /// if set to true the borders of the image will not be checked. + [MethodImpl(InliningOptions.ShortMethod)] + public SlidingWindowOperation( + Configuration configuration, + AdaptiveHistogramEqualizationSlidingWindowProcessor processor, + ImageFrame source, + MemoryAllocator memoryAllocator, + Buffer2D targetPixels, + SlidingWindowInfos swInfos, + int yStart, + int yEnd, + bool useFastPath) + { + this.configuration = configuration; + this.processor = processor; + this.source = source; + this.memoryAllocator = memoryAllocator; + this.targetPixels = targetPixels; + this.swInfos = swInfos; + this.yStart = yStart; + this.yEnd = yEnd; + this.useFastPath = useFastPath; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int x) + { + using (IMemoryOwner histogramBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner histogramBufferCopy = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner cdfBuffer = this.memoryAllocator.Allocate(this.processor.LuminanceLevels, AllocationOptions.Clean)) + using (IMemoryOwner pixelRowBuffer = this.memoryAllocator.Allocate(this.swInfos.TileWidth, AllocationOptions.Clean)) + { + Span histogram = histogramBuffer.GetSpan(); + ref int histogramBase = ref MemoryMarshal.GetReference(histogram); + + Span histogramCopy = histogramBufferCopy.GetSpan(); + ref int histogramCopyBase = ref MemoryMarshal.GetReference(histogramCopy); + + ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); + + Span pixelRow = pixelRowBuffer.GetSpan(); + ref Vector4 pixelRowBase = ref MemoryMarshal.GetReference(pixelRow); + + // Build the initial histogram of grayscale values. + for (int dy = this.yStart - this.swInfos.HalfTileHeight; dy < this.yStart + this.swInfos.HalfTileHeight; dy++) + { + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + } + + this.processor.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } + + for (int y = this.yStart; y < this.yEnd; y++) + { + if (this.processor.ClipHistogramEnabled) + { + // Clipping the histogram, but doing it on a copy to keep the original un-clipped values for the next iteration. + histogram.CopyTo(histogramCopy); + this.processor.ClipHistogram(histogramCopy, this.processor.ClipLimit); + } + + // Calculate the cumulative distribution function, which will map each input pixel in the current tile to a new value. + int cdfMin = this.processor.ClipHistogramEnabled + ? this.processor.CalculateCdf(ref cdfBase, ref histogramCopyBase, histogram.Length - 1) + : this.processor.CalculateCdf(ref cdfBase, ref histogramBase, histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = this.swInfos.PixelInTile - cdfMin; + + // Map the current pixel to the new equalized value. + int luminance = GetLuminance(this.source[x, y], this.processor.LuminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; + this.targetPixels[x, y].FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, this.source[x, y].ToVector4().W)); + + // Remove top most row from the histogram, mirroring rows which exceeds the borders. + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.RemovePixelsFromHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + + // Add new bottom row to the histogram, mirroring rows which exceeds the borders. + if (this.useFastPath) + { + this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + else + { + this.processor.CopyPixelRow(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + } + + this.processor.AddPixelsToHistogram(ref pixelRowBase, ref histogramBase, this.processor.LuminanceLevels, pixelRow.Length); + } + } + } + } + private class SlidingWindowInfos { public SlidingWindowInfos(int tileWidth, int tileHeight, int halfTileWidth, int halfTileHeight, int pixelInTile) @@ -382,15 +427,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.PixelInTile = pixelInTile; } - public int TileWidth { get; private set; } + public int TileWidth { get; } - public int TileHeight { get; private set; } + public int TileHeight { get; } - public int PixelInTile { get; private set; } + public int PixelInTile { get; } - public int HalfTileWidth { get; private set; } + public int HalfTileWidth { get; } - public int HalfTileHeight { get; private set; } + public int HalfTileHeight { get; } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index ff34457fb..209135deb 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -6,9 +6,7 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -19,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal class GlobalHistogramEqualizationProcessor : HistogramEqualizationProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. @@ -49,64 +47,119 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; - var workingRect = new Rectangle(0, 0, source.Width, source.Height); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Build the histogram of the grayscale levels + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source, this.LuminanceLevels); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in grayscaleOperation); + + Span histogram = histogramBuffer.GetSpan(); + if (this.ClipHistogramEnabled) + { + this.ClipHistogram(histogram, this.ClipLimit); + } + + using IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); + + // Calculate the cumulative distribution function, which will map each input pixel to a new value. + int cdfMin = this.CalculateCdf( + ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), + ref MemoryMarshal.GetReference(histogram), + histogram.Length - 1); + + float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; + + // Apply the cdf to each pixel of the image + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + ParallelRowIterator.IterateRows( + this.Configuration, + interest, + in cdfOperation); + } + + /// + /// A implementing the grayscale levels logic for . + /// + private readonly struct GrayscaleLevelsRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner histogramBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + + [MethodImpl(InliningOptions.ShortMethod)] + public GrayscaleLevelsRowOperation( + Rectangle bounds, + IMemoryOwner histogramBuffer, + ImageFrame source, + int luminanceLevels) + { + this.bounds = bounds; + this.histogramBuffer = histogramBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + } - using (IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) - using (IMemoryOwner cdfBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean)) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - // Build the histogram of the grayscale levels. - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - ref int histogramBase = ref MemoryMarshal.GetReference(histogramBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - - for (int x = 0; x < workingRect.Width; x++) - { - int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.LuminanceLevels); - Unsafe.Add(ref histogramBase, luminance)++; - } - } - }); - - Span histogram = histogramBuffer.GetSpan(); - if (this.ClipHistogramEnabled) + ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) { - this.ClipHistogram(histogram, this.ClipLimit); + int luminance = GetLuminance(Unsafe.Add(ref pixelBase, x), this.luminanceLevels); + Unsafe.Add(ref histogramBase, luminance)++; } + } + } + + /// + /// A implementing the cdf application levels logic for . + /// + private readonly struct CdfApplicationRowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly IMemoryOwner cdfBuffer; + private readonly ImageFrame source; + private readonly int luminanceLevels; + private readonly float numberOfPixelsMinusCdfMin; + + [MethodImpl(InliningOptions.ShortMethod)] + public CdfApplicationRowOperation( + Rectangle bounds, + IMemoryOwner cdfBuffer, + ImageFrame source, + int luminanceLevels, + float numberOfPixelsMinusCdfMin) + { + this.bounds = bounds; + this.cdfBuffer = cdfBuffer; + this.source = source; + this.luminanceLevels = luminanceLevels; + this.numberOfPixelsMinusCdfMin = numberOfPixelsMinusCdfMin; + } - // Calculate the cumulative distribution function, which will map each input pixel to a new value. - int cdfMin = this.CalculateCdf( - ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), - ref MemoryMarshal.GetReference(histogram), - histogram.Length - 1); - - float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; - - // Apply the cdf to each pixel of the image - ParallelHelper.IterateRows( - workingRect, - this.Configuration, - rows => - { - ref int cdfBase = ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()); - for (int y = rows.Min; y < rows.Max; y++) - { - ref TPixel pixelBase = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - - for (int x = 0; x < workingRect.Width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); - int luminance = GetLuminance(pixel, this.LuminanceLevels); - float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / numberOfPixelsMinusCdfMin; - pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); - } - } - }); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); + ref TPixel pixelBase = ref MemoryMarshal.GetReference(this.source.GetPixelRowSpan(y)); + + for (int x = 0; x < this.bounds.Width; x++) + { + ref TPixel pixel = ref Unsafe.Add(ref pixelBase, x); + int luminance = GetLuminance(pixel, this.luminanceLevels); + float luminanceEqualized = Unsafe.Add(ref cdfBase, luminance) / this.numberOfPixelsMinusCdfMin; + pixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs index 8bd619095..031b121b9 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// public abstract IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; /// /// Creates the that implements the algorithm diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index d7d72d4c8..ed0968f7c 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// /// The pixel format. internal abstract class HistogramEqualizationProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly float luminanceLevelsFloat; diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs index c9123bbbf..241ec1ebe 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new BackgroundColorProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index c4fabead2..727e72469 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -3,9 +3,8 @@ using System; using System.Buffers; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// The pixel format. internal class BackgroundColorProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly BackgroundColorProcessor definition; @@ -29,9 +28,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// The source area to process for the current processor instance. public BackgroundColorProcessor(Configuration configuration, BackgroundColorProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) - { - this.definition = definition; - } + => this.definition = definition; /// protected override void OnFrameApply(ImageFrame source) @@ -39,65 +36,67 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays TPixel color = this.definition.Color.ToPixel(); GraphicsOptions graphicsOptions = this.definition.GraphicsOptions; - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; - - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - int width = maxX - minX; + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); Configuration configuration = this.Configuration; MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - using (IMemoryOwner colors = memoryAllocator.Allocate(width)) - using (IMemoryOwner amount = memoryAllocator.Allocate(width)) - { - // Be careful! Do not capture colorSpan & amountSpan in the lambda below! - Span colorSpan = colors.GetSpan(); - Span amountSpan = amount.GetSpan(); + using IMemoryOwner colors = memoryAllocator.Allocate(interest.Width); + using IMemoryOwner amount = memoryAllocator.Allocate(interest.Width); - colorSpan.Fill(color); - amountSpan.Fill(graphicsOptions.BlendPercentage); + colors.GetSpan().Fill(color); + amount.GetSpan().Fill(graphicsOptions.BlendPercentage); - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - ParallelHelper.IterateRows( - workingRect, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destination = - source.GetPixelRowSpan(y - startY).Slice(minX - startX, width); + var operation = new RowOperation(configuration, interest, blender, amount, colors, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - // This switched color & destination in the 2nd and 3rd places because we are applying the target color under the current one - blender.Blend( - configuration, - destination, - colors.GetSpan(), - destination, - amount.GetSpan()); - } - }); + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly IMemoryOwner amount; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + PixelBlender blender, + IMemoryOwner amount, + IMemoryOwner colors, + ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.blender = blender; + this.amount = amount; + this.colors = colors; + this.source = source; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destination = + this.source.GetPixelRowSpan(y) + .Slice(this.bounds.X, this.bounds.Width); + + // Switch color & destination in the 2nd and 3rd places because we are + // applying the target color under the current one. + this.blender.Blend( + this.configuration, + destination, + this.colors.GetSpan(), + destination, + this.amount.GetSpan()); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 4f7ce7ba7..87e93ca21 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new GlowProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 363e670d0..fbecbc37c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -4,9 +4,8 @@ using System; using System.Buffers; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -17,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// The pixel format. internal class GlowProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly PixelBlender blender; private readonly GlowProcessor definition; @@ -39,78 +38,81 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source) { - // TODO: can we simplify the rectangle calculation? - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; TPixel glowColor = this.definition.GlowColor.ToPixel(); - Vector2 center = Rectangle.Center(this.SourceRectangle); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; - float finalRadius = this.definition.Radius.Calculate(source.Size()); + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + Vector2 center = Rectangle.Center(interest); + float finalRadius = this.definition.Radius.Calculate(interest.Size); float maxDistance = finalRadius > 0 - ? MathF.Min(finalRadius, this.SourceRectangle.Width * .5F) - : this.SourceRectangle.Width * .5F; + ? MathF.Min(finalRadius, interest.Width * .5F) + : interest.Width * .5F; - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(glowColor); - if (minY > 0) + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly Vector2 center; + private readonly float maxDistance; + private readonly float blendPercent; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame source) { - startY = 0; + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - - float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - - using (IMemoryOwner rowColors = memoryAllocator.Allocate(width)) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - rowColors.GetSpan().Fill(glowColor); - - ParallelHelper.IterateRowsWithTempBuffer( - workingRect, - configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(center, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = - (blendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); + Span colorSpan = this.colors.GetSpan(); + + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (1 - (.95F * (distance / this.maxDistance)))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 9915a5f52..5654eccfa 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new VignetteProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index 3e037189d..378009c40 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -4,9 +4,8 @@ using System; using System.Buffers; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -17,10 +16,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// /// The pixel format. internal class VignetteProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly PixelBlender blender; - private readonly VignetteProcessor definition; /// @@ -40,80 +38,89 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays /// protected override void OnFrameApply(ImageFrame source) { - int startY = this.SourceRectangle.Y; - int endY = this.SourceRectangle.Bottom; - int startX = this.SourceRectangle.X; - int endX = this.SourceRectangle.Right; TPixel vignetteColor = this.definition.VignetteColor.ToPixel(); - Vector2 centre = Rectangle.Center(this.SourceRectangle); + float blendPercent = this.definition.GraphicsOptions.BlendPercentage; + + var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); + + Vector2 center = Rectangle.Center(interest); + float finalRadiusX = this.definition.RadiusX.Calculate(interest.Size); + float finalRadiusY = this.definition.RadiusY.Calculate(interest.Size); - Size sourceSize = source.Size(); - float finalRadiusX = this.definition.RadiusX.Calculate(sourceSize); - float finalRadiusY = this.definition.RadiusY.Calculate(sourceSize); float rX = finalRadiusX > 0 - ? MathF.Min(finalRadiusX, this.SourceRectangle.Width * .5F) - : this.SourceRectangle.Width * .5F; + ? MathF.Min(finalRadiusX, interest.Width * .5F) + : interest.Width * .5F; + float rY = finalRadiusY > 0 - ? MathF.Min(finalRadiusY, this.SourceRectangle.Height * .5F) - : this.SourceRectangle.Height * .5F; + ? MathF.Min(finalRadiusY, interest.Height * .5F) + : interest.Height * .5F; + float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); - // Align start/end positions. - int minX = Math.Max(0, startX); - int maxX = Math.Min(source.Width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(source.Height, endY); + Configuration configuration = this.Configuration; + MemoryAllocator allocator = configuration.MemoryAllocator; - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } + using IMemoryOwner rowColors = allocator.Allocate(interest.Width); + rowColors.GetSpan().Fill(vignetteColor); + + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } - if (minY > 0) + private readonly struct RowOperation : IRowOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly PixelBlender blender; + private readonly Vector2 center; + private readonly float maxDistance; + private readonly float blendPercent; + private readonly IMemoryOwner colors; + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation( + Configuration configuration, + Rectangle bounds, + IMemoryOwner colors, + PixelBlender blender, + Vector2 center, + float maxDistance, + float blendPercent, + ImageFrame source) { - startY = 0; + this.configuration = configuration; + this.bounds = bounds; + this.colors = colors; + this.blender = blender; + this.center = center; + this.maxDistance = maxDistance; + this.blendPercent = blendPercent; + this.source = source; } - int width = maxX - minX; - int offsetX = minX - startX; - - var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); - float blendPercentage = this.definition.GraphicsOptions.BlendPercentage; - Configuration configuration = this.Configuration; - MemoryAllocator memoryAllocator = configuration.MemoryAllocator; - - using (IMemoryOwner rowColors = memoryAllocator.Allocate(width)) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) { - rowColors.GetSpan().Fill(vignetteColor); - - ParallelHelper.IterateRowsWithTempBuffer( - workingRect, - configuration, - (rows, amounts) => - { - Span amountsSpan = amounts.Span; - - for (int y = rows.Min; y < rows.Max; y++) - { - int offsetY = y - startY; - - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amountsSpan[i] = (blendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); - } - - Span destination = source.GetPixelRowSpan(offsetY).Slice(offsetX, width); - - this.blender.Blend( - configuration, - destination, - destination, - rowColors.GetSpan(), - amountsSpan); - } - }); + Span colorSpan = this.colors.GetSpan(); + + for (int i = 0; i < this.bounds.Width; i++) + { + float distance = Vector2.Distance(this.center, new Vector2(i + this.bounds.X, y)); + span[i] = (this.blendPercent * (.9F * (distance / this.maxDistance))).Clamp(0, 1); + } + + Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + + this.blender.Blend( + this.configuration, + destination, + destination, + colorSpan, + span); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs new file mode 100644 index 000000000..775e0aa23 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Concurrent; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Gets the closest color to the supplied color based upon the Euclidean distance. + /// + /// The pixel format. + internal readonly struct EuclideanPixelMap + where TPixel : unmanaged, IPixel + { + private readonly Vector4[] vectorCache; + private readonly ConcurrentDictionary distanceCache; + + /// + /// Initializes a new instance of the struct. + /// + /// The configuration. + /// The color palette to map from. + [MethodImpl(InliningOptions.ShortMethod)] + public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory palette) + { + this.Palette = palette; + this.vectorCache = new Vector4[palette.Length]; + + // Use the same rules across all target frameworks. + this.distanceCache = new ConcurrentDictionary(Environment.ProcessorCount, 31); + PixelOperations.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache); + } + + /// + /// Gets the color palette of this . + /// The palette memory is owned by the palette source that created it. + /// + public ReadOnlyMemory Palette + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } + + /// + /// Returns the closest color in the palette and the index of that pixel. + /// The palette contents must match the one used in the constructor. + /// + /// The color to match. + /// The matched color. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + public int GetClosestColor(TPixel color, out TPixel match) + { + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); + + // Check if the color is in the lookup table + if (!this.distanceCache.TryGetValue(color, out int index)) + { + return this.GetClosestColorSlow(color, ref paletteRef, out match); + } + + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match) + { + // Loop through the palette and find the nearest match. + int index = 0; + float leastDistance = float.MaxValue; + var vector = color.ToVector4(); + ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference(this.vectorCache); + for (int i = 0; i < this.Palette.Length; i++) + { + Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); + float distance = Vector4.DistanceSquared(vector, candidate); + + // If it's an exact match, exit the loop + if (distance == 0) + { + index = i; + break; + } + + if (distance < leastDistance) + { + // Less than... assign. + index = i; + leastDistance = distance; + } + } + + // Now I have the index, pop it into the cache for next time + this.distanceCache[color] = index; + match = Unsafe.Add(ref paletteRef, index); + return index; + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs new file mode 100644 index 000000000..4d75042ea --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizerUtilities.cs @@ -0,0 +1,151 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Contains utility methods for instances. + /// + public static class FrameQuantizerUtilities + { + /// + /// Helper method for throwing an exception when a frame quantizer palette has + /// been requested but not built yet. + /// + /// The pixel format. + /// The frame quantizer palette. + /// + /// The palette has not been built via + /// + public static void CheckPaletteState(in ReadOnlyMemory palette) + where TPixel : unmanaged, IPixel + { + if (palette.Equals(default)) + { + throw new InvalidOperationException("Frame Quantizer palette has not been built."); + } + } + + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The type of frame quantizer. + /// The pixel format. + /// The frame quantizer. + /// The source image frame to quantize. + /// The bounds within the frame to quantize. + /// + /// A representing a quantized version of the source frame pixels. + /// + public static IndexedImageFrame QuantizeFrame( + ref TFrameQuantizer quantizer, + ImageFrame source, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + Guard.NotNull(source, nameof(source)); + var interest = Rectangle.Intersect(source.Bounds(), bounds); + + // Collect the palette. Required before the second pass runs. + quantizer.BuildPalette(source, interest); + + var destination = new IndexedImageFrame( + quantizer.Configuration, + interest.Width, + interest.Height, + quantizer.Palette); + + if (quantizer.Options.Dither is null) + { + SecondPass(ref quantizer, source, destination, interest); + } + else + { + // We clone the image as we don't want to alter the original via error diffusion based dithering. + using ImageFrame clone = source.Clone(); + SecondPass(ref quantizer, clone, destination, interest); + } + + return destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void SecondPass( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + IDither dither = quantizer.Options.Dither; + + if (dither is null) + { + var operation = new RowIntervalOperation( + ref quantizer, + source, + destination, + bounds); + + ParallelRowIterator.IterateRowIntervals( + quantizer.Configuration, + bounds, + in operation); + + return; + } + + dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + where TFrameQuantizer : struct, IFrameQuantizer + where TPixel : unmanaged, IPixel + { + private readonly TFrameQuantizer quantizer; + private readonly ImageFrame source; + private readonly IndexedImageFrame destination; + private readonly Rectangle bounds; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + ref TFrameQuantizer quantizer, + ImageFrame source, + IndexedImageFrame destination, + Rectangle bounds) + { + this.quantizer = quantizer; + this.source = source; + this.destination = destination; + this.bounds = bounds; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); + + for (int x = this.bounds.Left; x < this.bounds.Right; x++) + { + destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); + } + } + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs deleted file mode 100644 index eb3838d21..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/FrameQuantizer{TPixel}.cs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// The base class for all implementations - /// - /// The pixel format. - public abstract class FrameQuantizer : IFrameQuantizer - where TPixel : struct, IPixel - { - /// - /// A lookup table for colors - /// - private readonly Dictionary distanceCache = new Dictionary(); - - /// - /// Flag used to indicate whether a single pass or two passes are needed for quantization. - /// - private readonly bool singlePass; - - /// - /// The vector representation of the image palette. - /// - private IMemoryOwner paletteVector; - - private bool isDisposed; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The quantizer - /// - /// If true, the quantization process only needs to loop through the source pixels once - /// - /// - /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . - /// - protected FrameQuantizer(Configuration configuration, IQuantizer quantizer, bool singlePass) - { - Guard.NotNull(quantizer, nameof(quantizer)); - - this.Configuration = configuration; - this.Diffuser = quantizer.Diffuser; - this.Dither = this.Diffuser != null; - this.singlePass = singlePass; - } - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The diffuser - /// - /// If true, the quantization process only needs to loop through the source pixels once - /// - /// - /// If you construct this class with a true for , then the code will - /// only call the method. - /// If two passes are required, the code will also call . - /// - protected FrameQuantizer(Configuration configuration, IErrorDiffuser diffuser, bool singlePass) - { - this.Configuration = configuration; - this.Diffuser = diffuser; - this.Dither = this.Diffuser != null; - this.singlePass = singlePass; - } - - /// - public IErrorDiffuser Diffuser { get; } - - /// - public bool Dither { get; } - - /// - /// Gets the configuration which allows altering default behaviour or extending the library. - /// - protected Configuration Configuration { get; } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - public IQuantizedFrame QuantizeFrame(ImageFrame image) - { - Guard.NotNull(image, nameof(image)); - - // Get the size of the source image - int height = image.Height; - int width = image.Width; - - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(image, width, height); - } - - // Collect the palette. Required before the second pass runs. - ReadOnlyMemory palette = this.GetPalette(); - MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; - - this.paletteVector = memoryAllocator.Allocate(palette.Length); - PixelOperations.Instance.ToVector4( - this.Configuration, - palette.Span, - this.paletteVector.Memory.Span, - PixelConversionModifiers.Scale); - - var quantizedFrame = new QuantizedFrame(memoryAllocator, width, height, palette); - - Span pixelSpan = quantizedFrame.GetWritablePixelSpan(); - if (this.Dither) - { - // We clone the image as we don't want to alter the original via dithering. - using (ImageFrame clone = image.Clone()) - { - this.SecondPass(clone, pixelSpan, palette.Span, width, height); - } - } - else - { - this.SecondPass(image, pixelSpan, palette.Span, width, height); - } - - return quantizedFrame; - } - - /// - /// Disposes the object and frees resources for the Garbage Collector. - /// - /// Whether to dispose managed and unmanaged objects. - protected virtual void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - this.paletteVector?.Dispose(); - } - - this.paletteVector = null; - - this.isDisposed = true; - } - - /// - /// Execute the first pass through the pixels in the image to create the palette. - /// - /// The source data. - /// The width in pixels of the image. - /// The height in pixels of the image. - protected virtual void FirstPass(ImageFrame source, int width, int height) - { - } - - /// - /// Returns the closest color from the palette to the given color by calculating the - /// Euclidean distance in the Rgba colorspace. - /// - /// The color. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected byte GetClosestPixel(ref TPixel pixel) - { - // Check if the color is in the lookup table - if (this.distanceCache.TryGetValue(pixel, out byte value)) - { - return value; - } - - return this.GetClosestPixelSlow(ref pixel); - } - - /// - /// Retrieve the palette for the quantized image. - /// - /// - /// - /// - protected abstract ReadOnlyMemory GetPalette(); - - /// - /// Returns the index of the first instance of the transparent color in the palette. - /// - /// The . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected byte GetTransparentIndex() - { - // Transparent pixels are much more likely to be found at the end of a palette. - Span paletteVectorSpan = this.paletteVector.Memory.Span; - ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan); - - int paletteVectorLengthMinus1 = paletteVectorSpan.Length - 1; - - int index = paletteVectorLengthMinus1; - for (int i = paletteVectorLengthMinus1; i >= 0; i--) - { - ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, i); - if (candidate.Equals(default)) - { - index = i; - } - } - - return (byte)index; - } - - /// - /// Execute a second pass through the image to assign the pixels to a palette entry. - /// - /// The source image. - /// The output pixel array. - /// The output color palette. - /// The width in pixels of the image. - /// The height in pixels of the image. - protected abstract void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height); - - [MethodImpl(MethodImplOptions.NoInlining)] - private byte GetClosestPixelSlow(ref TPixel pixel) - { - // Loop through the palette and find the nearest match. - int colorIndex = 0; - float leastDistance = float.MaxValue; - Vector4 vector = pixel.ToScaledVector4(); - float epsilon = Constants.EpsilonSquared; - Span paletteVectorSpan = this.paletteVector.Memory.Span; - ref Vector4 paletteVectorSpanBase = ref MemoryMarshal.GetReference(paletteVectorSpan); - - for (int index = 0; index < paletteVectorSpan.Length; index++) - { - ref Vector4 candidate = ref Unsafe.Add(ref paletteVectorSpanBase, index); - float distance = Vector4.DistanceSquared(vector, candidate); - - // Greater... Move on. - if (!(distance < leastDistance)) - { - continue; - } - - colorIndex = index; - leastDistance = distance; - - // And if it's an exact match, exit the loop - if (distance < epsilon) - { - break; - } - } - - // Now I have the index, pop it into the cache for next time - byte result = (byte)colorIndex; - this.distanceCache.Add(pixel, result); - return result; - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs index 54dabab0a..cc87715eb 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IFrameQuantizer{TPixel}.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -12,25 +11,52 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The pixel format. public interface IFrameQuantizer : IDisposable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// - /// Gets a value indicating whether to apply dithering to the output image. + /// Gets the configuration. /// - bool Dither { get; } + Configuration Configuration { get; } /// - /// Gets the error diffusion algorithm to apply to the output image. + /// Gets the quantizer options defining quantization rules. /// - IErrorDiffuser Diffuser { get; } + QuantizerOptions Options { get; } /// - /// Quantize an image frame and return the resulting output pixels. + /// Gets the quantized color palette. /// - /// The image to quantize. + /// + /// The palette has not been built via . + /// + ReadOnlyMemory Palette { get; } + + /// + /// Builds the quantized palette from the given image frame and bounds. + /// + /// The source image frame. + /// The region of interest bounds. + void BuildPalette(ImageFrame source, Rectangle bounds); + + /// + /// Quantizes an image frame and return the resulting output pixels. + /// + /// The source image frame to quantize. + /// The bounds within the frame to quantize. /// - /// A representing a quantized version of the image pixels. + /// A representing a quantized version of the source frame pixels. /// - IQuantizedFrame QuantizeFrame(ImageFrame image); + IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds); + + /// + /// Returns the index and color from the quantized palette corresponding to the given color. + /// + /// The color to match. + /// The matched color. + /// The index. + byte GetQuantizedColor(TPixel color, out TPixel match); + + // TODO: Enable bulk operations. + // void GetQuantizedColors(ReadOnlySpan colors, ReadOnlySpan palette, Span indices, Span matches); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs deleted file mode 100644 index 42016459b..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Defines an abstraction to represent a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public interface IQuantizedFrame : IDisposable - where TPixel : struct, IPixel - { - /// - /// Gets the width of this . - /// - int Width { get; } - - /// - /// Gets the height of this . - /// - int Height { get; } - - /// - /// Gets the color palette of this . - /// - ReadOnlyMemory Palette { get; } - - /// - /// Gets the pixels of this . - /// - /// The The pixel span. - ReadOnlySpan GetPixelSpan(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs index f1490a6d2..01e4b5e8a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs @@ -1,8 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -12,27 +11,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public interface IQuantizer { /// - /// Gets the error diffusion algorithm to apply to the output image. + /// Gets the quantizer options defining quantization rules. /// - IErrorDiffuser Diffuser { get; } + QuantizerOptions Options { get; } /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The to configure internal operations. /// The pixel format. - /// The + /// The . IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel; + where TPixel : unmanaged, IPixel; /// - /// Creates the generic frame quantizer + /// Creates the generic frame quantizer. /// /// The pixel format. /// The to configure internal operations. - /// The maximum number of colors to hold in the color palette. - /// The - IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel; + /// The options to create the quantizer with. + /// The . + IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs new file mode 100644 index 000000000..ac737f452 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/IndexedImageFrame{TPixel}.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// A pixel-specific image frame where each pixel buffer value represents an index in a color palette. + /// + /// The pixel format. + public sealed class IndexedImageFrame : IDisposable + where TPixel : unmanaged, IPixel + { + private IMemoryOwner pixelsOwner; + private IMemoryOwner paletteOwner; + private bool isDisposed; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The configuration which allows altering default behaviour or extending the library. + /// + /// The frame width. + /// The frame height. + /// The color palette. + internal IndexedImageFrame(Configuration configuration, int width, int height, ReadOnlyMemory palette) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.MustBeLessThanOrEqualTo(palette.Length, QuantizerConstants.MaxColors, nameof(palette)); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Configuration = configuration; + this.Width = width; + this.Height = height; + this.pixelsOwner = configuration.MemoryAllocator.AllocateManagedByteBuffer(width * height); + + // Copy the palette over. We want the lifetime of this frame to be independant of any palette source. + this.paletteOwner = configuration.MemoryAllocator.Allocate(palette.Length); + palette.Span.CopyTo(this.paletteOwner.GetSpan()); + this.Palette = this.paletteOwner.Memory.Slice(0, palette.Length); + } + + /// + /// Gets the configuration which allows altering default behaviour or extending the library. + /// + public Configuration Configuration { get; } + + /// + /// Gets the width of this . + /// + public int Width { get; } + + /// + /// Gets the height of this . + /// + public int Height { get; } + + /// + /// Gets the color palette of this . + /// + public ReadOnlyMemory Palette { get; } + + /// + /// Gets the pixels of this . + /// + /// The + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelBufferSpan() => this.pixelsOwner.GetSpan(); // TODO: Buffer2D + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public ReadOnlySpan GetPixelRowSpan(int rowIndex) + => this.GetWritablePixelRowSpanUnsafe(rowIndex); + + /// + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the the first pixel on that row. + /// + /// + /// Note: Values written to this span are not sanitized against the palette length. + /// Care should be taken during assignment to prevent out-of-bounds errors. + /// + /// + /// The row index in the pixel buffer. + /// The pixel row as a . + [MethodImpl(InliningOptions.ShortMethod)] + public Span GetWritablePixelRowSpanUnsafe(int rowIndex) + => this.pixelsOwner.GetSpan().Slice(rowIndex * this.Width, this.Width); + + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.pixelsOwner.Dispose(); + this.paletteOwner.Dispose(); + this.pixelsOwner = null; + this.paletteOwner = null; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs index 4b94c14be..ce2e406d4 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Generic; +using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -16,169 +17,128 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class OctreeFrameQuantizer : FrameQuantizer - where TPixel : struct, IPixel + public struct OctreeFrameQuantizer : IFrameQuantizer + where TPixel : unmanaged, IPixel { - /// - /// Maximum allowed color depth - /// - private readonly int colors; - - /// - /// Stores the tree - /// + private readonly int maxColors; private readonly Octree octree; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; + private bool isDisposed; /// - /// The transparent index - /// - private byte transparentIndex; - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. - /// The octree quantizer - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// - public OctreeFrameQuantizer(Configuration configuration, OctreeQuantizer quantizer) - : this(configuration, quantizer, quantizer.MaxColors) + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public OctreeFrameQuantizer(Configuration configuration, QuantizerOptions options) { + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); + + this.Configuration = configuration; + this.Options = options; + + this.maxColors = this.Options.MaxColors; + this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.maxColors).Clamp(1, 8)); + this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; + this.pixelMap = default; + this.isDithering = !(this.Options.Dither is null); + this.isDisposed = false; } - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The octree quantizer. - /// The maximum number of colors to hold in the color palette. - /// - /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree, - /// the second pass quantizes a color based on the nodes in the tree - /// - public OctreeFrameQuantizer(Configuration configuration, OctreeQuantizer quantizer, int maxColors) - : base(configuration, quantizer, false) - { - this.colors = maxColors; - this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8)); - } + /// + public Configuration Configuration { get; } + + /// + public QuantizerOptions Options { get; } /// - protected override void FirstPass(ImageFrame source, int width, int height) + public ReadOnlyMemory Palette { - // Loop through each row - for (int y = 0; y < height; y++) + get { - Span row = source.GetPixelRowSpan(y); - ref TPixel scanBaseRef = ref MemoryMarshal.GetReference(row); - - // And loop through each column - for (int x = 0; x < width; x++) - { - ref TPixel pixel = ref Unsafe.Add(ref scanBaseRef, x); - - // Add the color to the Octree - this.octree.AddColor(ref pixel); - } + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; } } /// - protected override void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height) + [MethodImpl(InliningOptions.ShortMethod)] + public void BuildPalette(ImageFrame source, Rectangle bounds) { - // Load up the values for the first pixel. We can use these to speed up the second - // pass of the algorithm by avoiding transforming rows of identical color. - TPixel sourcePixel = source[0, 0]; - TPixel previousPixel = sourcePixel; - this.transparentIndex = this.GetTransparentIndex(); - byte pixelValue = this.QuantizePixel(ref sourcePixel); - TPixel transformedPixel = palette[pixelValue]; - - for (int y = 0; y < height; y++) + using IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); + + // Loop through each row + for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetPixelRowSpan(y); + Span row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - // And loop through each column - for (int x = 0; x < width; x++) + for (int x = 0; x < bufferSpan.Length; x++) { - // Get the pixel. - sourcePixel = row[x]; + Rgba32 rgba = bufferSpan[x]; - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - // Quantize the pixel - pixelValue = this.QuantizePixel(ref sourcePixel); + // Add the color to the Octree + this.octree.AddColor(rgba); + } + } - // And setup the previous pointer - previousPixel = sourcePixel; + Span paletteSpan = this.paletteOwner.GetSpan(); + int paletteIndex = 0; + this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); - if (this.Dither) - { - transformedPixel = palette[pixelValue]; - } - } + // Length of reduced palette + transparency. + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); - if (this.Dither) - { - // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); - } - - output[(y * source.Width) + x] = pixelValue; - } - } + this.palette = result; } - internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); - /// - protected override ReadOnlyMemory GetPalette() => this.octree.Palletize(this.colors); + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); - /// - /// Process the pixel in the second pass of the algorithm. - /// - /// The pixel to quantize. - /// The - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(ref TPixel pixel) + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - if (this.Dither) + // Octree only maps the RGB component of a color + // so cannot tell the difference between a fully transparent + // pixel and a black one. + if (this.isDithering || color.Equals(default)) { - // The colors have changed so we need to use Euclidean distance calculation to - // find the closest value. - return this.GetClosestPixel(ref pixel); + return (byte)this.pixelMap.GetClosestColor(color, out match); } - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - if (rgba.Equals(default)) + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + var index = (byte)this.octree.GetPaletteIndex(color); + match = Unsafe.Add(ref paletteRef, index); + return index; + } + + /// + public void Dispose() + { + if (!this.isDisposed) { - return this.transparentIndex; + this.isDisposed = true; + this.paletteOwner.Dispose(); + this.paletteOwner = null; } - - return (byte)this.octree.GetPaletteIndex(ref pixel); } /// - /// Class which does the actual quantization + /// Class which does the actual quantization. /// - private class Octree + private sealed class Octree { - /// - /// Mask used when getting the appropriate pixels for a given node - /// - // ReSharper disable once StaticMemberInGenericType - private static readonly int[] Mask = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; - /// /// The root of the Octree /// @@ -197,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Cache the previous color quantized /// - private TPixel previousColor; + private Rgba32 previousColor; /// /// Initializes a new instance of the class. @@ -215,15 +175,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.previousNode = null; } + /// + /// Gets the mask used when getting the appropriate pixels for a given node. + /// + private static ReadOnlySpan Mask => new byte[] + { + 0b10000000, + 0b1000000, + 0b100000, + 0b10000, + 0b1000, + 0b100, + 0b10, + 0b1 + }; + /// /// Gets or sets the number of leaves in the tree /// public int Leaves { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get; - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] set; } @@ -232,73 +207,71 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private OctreeNode[] ReducibleNodes { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get; } /// /// Add a given color value to the Octree /// - /// The pixel data. - public void AddColor(ref TPixel pixel) + /// The color to add. + public void AddColor(Rgba32 color) { // Check if this request is for the same color as the last - if (this.previousColor.Equals(pixel)) + if (this.previousColor.Equals(color)) { - // If so, check if I have a previous node setup. This will only occur if the first color in the image + // If so, check if I have a previous node setup. + // This will only occur if the first color in the image // happens to be black, with an alpha component of zero. if (this.previousNode is null) { - this.previousColor = pixel; - this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } else { // Just update the previous node - this.previousNode.Increment(ref pixel); + this.previousNode.Increment(ref color); } } else { - this.previousColor = pixel; - this.root.AddColor(ref pixel, this.maxColorBits, 0, this); + this.previousColor = color; + this.root.AddColor(ref color, this.maxColorBits, 0, this); } } /// /// Convert the nodes in the Octree to a palette with a maximum of colorCount colors /// + /// The palette to fill. /// The maximum number of colors - /// - /// An with the palletized colors - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TPixel[] Palletize(int colorCount) + /// The palette index, used to calculate the final size of the palette. + [MethodImpl(InliningOptions.ShortMethod)] + public void Palletize(Span palette, int colorCount, ref int paletteIndex) { while (this.Leaves > colorCount - 1) { this.Reduce(); } - // Now palletize the nodes - var palette = new TPixel[colorCount]; - - int paletteIndex = 0; this.root.ConstructPalette(palette, ref paletteIndex); - - // And return the palette - return palette; } /// /// Get the palette index for the passed color /// - /// The pixel data. + /// The color to match. /// - /// The . + /// The index. /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetPaletteIndex(ref TPixel pixel) => this.root.GetPaletteIndex(ref pixel, 0); + [MethodImpl(InliningOptions.ShortMethod)] + public int GetPaletteIndex(TPixel color) + { + Rgba32 rgba = default; + color.ToRgba32(ref rgba); + return this.root.GetPaletteIndex(ref rgba, 0); + } /// /// Keep track of the previous node that was quantized @@ -306,8 +279,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The node last quantized /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void TrackPrevious(OctreeNode node) => this.previousNode = node; + [MethodImpl(InliningOptions.ShortMethod)] + public void TrackPrevious(OctreeNode node) => this.previousNode = node; /// /// Reduce the depth of the tree @@ -336,7 +309,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Class which encapsulates each node in the tree /// - protected class OctreeNode + public sealed class OctreeNode { /// /// Pointers to any child nodes @@ -376,15 +349,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// - /// The level in the tree = 0 - 7 - /// - /// - /// The number of significant color bits in the image - /// - /// - /// The tree to which this node belongs - /// + /// The level in the tree = 0 - 7. + /// The number of significant color bits in the image. + /// The tree to which this node belongs. public OctreeNode(int level, int colorBits, Octree octree) { // Construct the new node @@ -414,23 +381,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public OctreeNode NextReducible { - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] get; } /// /// Add a color into the tree /// - /// The pixel color - /// The number of significant color bits - /// The level in the tree - /// The tree to which this node belongs - public void AddColor(ref TPixel pixel, int colorBits, int level, Octree octree) + /// The color to add. + /// The number of significant color bits. + /// The level in the tree. + /// The tree to which this node belongs. + public void AddColor(ref Rgba32 color, int colorBits, int level, Octree octree) { // Update the color information if this is a leaf if (this.leaf) { - this.Increment(ref pixel); + this.Increment(ref color); // Setup the previous node octree.TrackPrevious(this); @@ -438,13 +405,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization else { // Go to the next level down in the tree - int shift = 7 - level; - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); - - int index = ((rgba.B & Mask[level]) >> (shift - 2)) - | ((rgba.G & Mask[level]) >> (shift - 1)) - | ((rgba.R & Mask[level]) >> shift); + int index = GetColorIndex(ref color, level); OctreeNode child = this.children[index]; if (child is null) @@ -455,7 +416,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } // Add the color to the child node - child.AddColor(ref pixel, colorBits, level + 1, octree); + child.AddColor(ref color, colorBits, level + 1, octree); } } @@ -495,13 +456,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The palette /// The current palette index - [MethodImpl(MethodImplOptions.NoInlining)] - public void ConstructPalette(TPixel[] palette, ref int index) + [MethodImpl(InliningOptions.ColdPath)] + public void ConstructPalette(Span palette, ref int index) { if (this.leaf) { // Set the color of the palette entry - var vector = Vector3.Clamp(new Vector3(this.red, this.green, this.blue) / this.pixelCount, Vector3.Zero, new Vector3(255)); + var vector = Vector3.Clamp( + new Vector3(this.red, this.green, this.blue) / this.pixelCount, + Vector3.Zero, + new Vector3(255)); + TPixel pixel = default; pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); palette[index] = pixel; @@ -527,29 +492,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The representing the index of the pixel in the palette. /// - [MethodImpl(MethodImplOptions.NoInlining)] - public int GetPaletteIndex(ref TPixel pixel, int level) + [MethodImpl(InliningOptions.ColdPath)] + public int GetPaletteIndex(ref Rgba32 pixel, int level) { - int index = this.paletteIndex; - - if (!this.leaf) + if (this.leaf) { - int shift = 7 - level; - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); + return this.paletteIndex; + } - int pixelIndex = ((rgba.B & Mask[level]) >> (shift - 2)) - | ((rgba.G & Mask[level]) >> (shift - 1)) - | ((rgba.R & Mask[level]) >> shift); + int colorIndex = GetColorIndex(ref pixel, level); + OctreeNode child = this.children[colorIndex]; - OctreeNode child = this.children[pixelIndex]; - if (child != null) - { - index = child.GetPaletteIndex(ref pixel, level + 1); - } - else + int index = 0; + if (child != null) + { + index = child.GetPaletteIndex(ref pixel, level + 1); + } + else + { + // Check other children. + for (int i = 0; i < this.children.Length; i++) { - throw new Exception($"Cannot retrieve a pixel at the given index {pixelIndex}."); + child = this.children[i]; + if (child != null) + { + var childIndex = child.GetPaletteIndex(ref pixel, level + 1); + if (childIndex != 0) + { + return childIndex; + } + } } } @@ -557,18 +529,36 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization } /// - /// Increment the pixel count and add to the color information + /// Gets the color index at the given level. + /// + /// The color. + /// The node level. + /// The index. + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetColorIndex(ref Rgba32 color, int level) + { + DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level)); + + int shift = 7 - level; + ref byte maskRef = ref MemoryMarshal.GetReference(Mask); + byte mask = Unsafe.Add(ref maskRef, level); + + return ((color.R & mask) >> shift) + | ((color.G & mask) >> (shift - 1)) + | ((color.B & mask) >> (shift - 2)); + } + + /// + /// Increment the color count and add to the color information /// - /// The pixel to add. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Increment(ref TPixel pixel) + /// The pixel to add. + [MethodImpl(InliningOptions.ShortMethod)] + public void Increment(ref Rgba32 color) { - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); this.pixelCount++; - this.red += rgba.R; - this.green += rgba.G; - this.blue += rgba.B; + this.red += color.R; + this.green += color.G; + this.blue += color.B; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index aaf2c42cb..9e04edef0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -2,97 +2,47 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using Octrees. /// - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class OctreeQuantizer : IQuantizer { - /// - /// Initializes a new instance of the class. - /// - public OctreeQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(int maxColors) - : this(GetDiffuser(true), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image. - public OctreeQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum number of colors to hold in the color palette. - /// Whether to apply dithering to the output image. - public OctreeQuantizer(bool dither, int maxColors) - : this(GetDiffuser(dither), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffusion algorithm, if any, to apply to the output image. - public OctreeQuantizer(IErrorDiffuser diffuser) - : this(diffuser, QuantizerConstants.MaxColors) + public OctreeQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image. - /// The maximum number of colors to hold in the color palette. - public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) + /// The quantizer options defining quantization rules. + public OctreeQuantizer(QuantizerOptions options) { - this.Diffuser = diffuser; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the maximum number of colors to hold in the color palette. - /// - public int MaxColors { get; } + public QuantizerOptions Options { get; } - /// /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel - => new OctreeFrameQuantizer(configuration, this); + where TPixel : unmanaged, IPixel + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel - { - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new OctreeFrameQuantizer(configuration, this, maxColors); - } - - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new OctreeFrameQuantizer(configuration, options); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs index 825eb6bee..ade73e2d0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs @@ -3,10 +3,7 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { @@ -15,88 +12,59 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class PaletteFrameQuantizer : FrameQuantizer - where TPixel : struct, IPixel + internal struct PaletteFrameQuantizer : IFrameQuantizer + where TPixel : unmanaged, IPixel { - /// - /// The reduced image palette. - /// - private readonly ReadOnlyMemory palette; + private readonly EuclideanPixelMap pixelMap; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. - /// The palette quantizer. - /// An array of all colors in the palette. - public PaletteFrameQuantizer(Configuration configuration, IErrorDiffuser diffuser, ReadOnlyMemory colors) - : base(configuration, diffuser, true) => this.palette = colors; - - /// - protected override void SecondPass( - ImageFrame source, - Span output, - ReadOnlySpan palette, - int width, - int height) + /// The quantizer options defining quantization rules. + /// The pixel map for looking up color matches from a predefined palette. + [MethodImpl(InliningOptions.ShortMethod)] + public PaletteFrameQuantizer( + Configuration configuration, + QuantizerOptions options, + EuclideanPixelMap pixelMap) { - // Load up the values for the first pixel. We can use these to speed up the second - // pass of the algorithm by avoiding transforming rows of identical color. - TPixel sourcePixel = source[0, 0]; - TPixel previousPixel = sourcePixel; - byte pixelValue = this.QuantizePixel(ref sourcePixel); - ref TPixel paletteRef = ref MemoryMarshal.GetReference(palette); - TPixel transformedPixel = Unsafe.Add(ref paletteRef, pixelValue); - - for (int y = 0; y < height; y++) - { - ref TPixel rowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); - // And loop through each column - for (int x = 0; x < width; x++) - { - // Get the pixel. - sourcePixel = Unsafe.Add(ref rowRef, x); + this.Configuration = configuration; + this.Options = options; + this.pixelMap = pixelMap; + } - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - // Quantize the pixel - pixelValue = this.QuantizePixel(ref sourcePixel); + /// + public Configuration Configuration { get; } - // And setup the previous pointer - previousPixel = sourcePixel; + /// + public QuantizerOptions Options { get; } - if (this.Dither) - { - transformedPixel = Unsafe.Add(ref paletteRef, pixelValue); - } - } + /// + public ReadOnlyMemory Palette => this.pixelMap.Palette; - if (this.Dither) - { - // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); - output[(y * source.Width) + x] = pixelValue; - } - } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void BuildPalette(ImageFrame source, Rectangle bounds) + { } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected override ReadOnlyMemory GetPalette() => this.palette; + [MethodImpl(InliningOptions.ShortMethod)] + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) + => (byte)this.pixelMap.GetClosestColor(color, out match); - /// - /// Process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(ref TPixel pixel) => this.GetClosestPixel(ref pixel); + /// + public void Dispose() + { + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs index a493e6f88..c14ea6153 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs @@ -2,80 +2,65 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using color palettes. - /// Override this class to provide your own palette. - /// - /// By default the quantizer uses dithering. - /// /// public class PaletteQuantizer : IQuantizer { + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); + private readonly ReadOnlyMemory colorPalette; + /// /// Initializes a new instance of the class. /// - /// The palette. + /// The color palette. public PaletteQuantizer(ReadOnlyMemory palette) - : this(palette, true) + : this(palette, DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The palette. - /// Whether to apply dithering to the output image - public PaletteQuantizer(ReadOnlyMemory palette, bool dither) - : this(palette, GetDiffuser(dither)) + /// The color palette. + /// The quantizer options defining quantization rules. + public PaletteQuantizer(ReadOnlyMemory palette, QuantizerOptions options) { - } + Guard.MustBeGreaterThan(palette.Length, 0, nameof(palette)); + Guard.NotNull(options, nameof(options)); - /// - /// Initializes a new instance of the class. - /// - /// The palette. - /// The error diffusion algorithm, if any, to apply to the output image - public PaletteQuantizer(ReadOnlyMemory palette, IErrorDiffuser diffuser) - { - this.Palette = palette; - this.Diffuser = diffuser; + this.colorPalette = palette; + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the palette. - /// - public ReadOnlyMemory Palette { get; } + public QuantizerOptions Options { get; } /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel - { - var palette = new TPixel[this.Palette.Length]; - Color.ToPixel(configuration, this.Palette.Span, palette.AsSpan()); - return new PaletteFrameQuantizer(configuration, this.Diffuser, palette); - } + where TPixel : unmanaged, IPixel + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel { - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - int max = Math.Min(maxColors, this.Palette.Length); + Guard.NotNull(options, nameof(options)); - var palette = new TPixel[max]; - Color.ToPixel(configuration, this.Palette.Span.Slice(0, max), palette.AsSpan()); - return new PaletteFrameQuantizer(configuration, this.Diffuser, palette); - } + // The palette quantizer can reuse the same pixel map across multiple frames + // since the palette is unchanging. This allows a reduction of memory usage across + // multi frame gifs using a global palette. + int length = Math.Min(this.colorPalette.Length, options.MaxColors); + var palette = new TPixel[length]; - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); + + var pixelMap = new EuclideanPixelMap(configuration, palette); + return new PaletteFrameQuantizer(configuration, options, pixelMap); + } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs index 8e1dffeed..bdaeb57f6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor.cs @@ -15,9 +15,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The quantizer used to reduce the color palette. public QuantizeProcessor(IQuantizer quantizer) - { - this.Quantizer = quantizer; - } + => this.Quantizer = quantizer; /// /// Gets the quantizer. @@ -26,7 +24,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new QuantizeProcessor(configuration, this.Quantizer, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 5e732982c..4583b7cff 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization @@ -13,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The pixel format. internal class QuantizeProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly IQuantizer quantizer; @@ -34,28 +35,54 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// protected override void OnFrameApply(ImageFrame source) { + var interest = Rectangle.Intersect(source.Bounds(), this.SourceRectangle); + Configuration configuration = this.Configuration; - using (IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration)) - using (IQuantizedFrame quantized = frameQuantizer.QuantizeFrame(source)) - { - int paletteCount = quantized.Palette.Length - 1; + using IFrameQuantizer frameQuantizer = this.quantizer.CreateFrameQuantizer(configuration); + using IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(source, interest); - // Not parallel to remove "quantized" closure allocation. - // We can operate directly on the source here as we've already read it to get the - // quantized result - for (int y = 0; y < source.Height; y++) - { - Span row = source.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = quantized.GetPixelSpan(); + var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); + ParallelRowIterator.IterateRowIntervals( + configuration, + interest, + in operation); + } + + private readonly struct RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly IndexedImageFrame quantized; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowIntervalOperation( + Rectangle bounds, + ImageFrame source, + IndexedImageFrame quantized) + { + this.bounds = bounds; + this.source = source; + this.quantized = quantized; + } - ReadOnlySpan paletteSpan = quantized.Palette.Span; + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + ReadOnlySpan quantizedPixelSpan = this.quantized.GetPixelBufferSpan(); + ReadOnlySpan paletteSpan = this.quantized.Palette.Span; + int offsetY = this.bounds.Top; + int offsetX = this.bounds.Left; + int width = this.bounds.Width; - int yy = y * source.Width; + for (int y = rows.Min; y < rows.Max; y++) + { + Span row = this.source.GetPixelRowSpan(y); + int rowStart = (y - offsetY) * width; - for (int x = 0; x < source.Width; x++) + for (int x = this.bounds.Left; x < this.bounds.Right; x++) { - int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; + int i = rowStart + x - offsetX; + row[x] = paletteSpan[quantizedPixelSpan[i]]; } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs deleted file mode 100644 index fa3d36e10..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrameExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; - -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Contains extension methods for . - /// - public static class QuantizedFrameExtensions - { - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. - /// - /// The . - /// The row. - /// The pixel type. - /// The pixel row as a . - [MethodImpl(InliningOptions.ShortMethod)] - public static ReadOnlySpan GetRowSpan(this IQuantizedFrame frame, int rowIndex) - where TPixel : struct, IPixel - => frame.GetPixelSpan().Slice(rowIndex * frame.Width, frame.Width); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs deleted file mode 100644 index 4938f0e12..000000000 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Quantization -{ - /// - /// Represents a quantized image frame where the pixels indexed by a color palette. - /// - /// The pixel format. - public class QuantizedFrame : IQuantizedFrame - where TPixel : struct, IPixel - { - private IMemoryOwner pixels; - - /// - /// Initializes a new instance of the class. - /// - /// Used to allocated memory for image processing operations. - /// The image width. - /// The image height. - /// The color palette. - internal QuantizedFrame(MemoryAllocator memoryAllocator, int width, int height, ReadOnlyMemory palette) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Width = width; - this.Height = height; - this.Palette = palette; - this.pixels = memoryAllocator.AllocateManagedByteBuffer(width * height, AllocationOptions.Clean); - } - - /// - /// Gets the width of this . - /// - public int Width { get; } - - /// - /// Gets the height of this . - /// - public int Height { get; } - - /// - /// Gets the color palette of this . - /// - public ReadOnlyMemory Palette { get; private set; } - - /// - /// Gets the pixels of this . - /// - /// The - [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPixelSpan() => this.pixels.GetSpan(); - - /// - public void Dispose() - { - this.pixels?.Dispose(); - this.pixels = null; - this.Palette = null; - } - - /// - /// Get the non-readonly span of pixel data so can fill it. - /// - internal Span GetWritablePixelSpan() => this.pixels.GetSpan(); - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs index d79a91c30..ece3777e0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerConstants.cs @@ -1,12 +1,14 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing.Processors.Dithering; + namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Contains color quantization specific constants. /// - internal static class QuantizerConstants + public static class QuantizerConstants { /// /// The minimum number of colors to use when quantizing an image. @@ -17,5 +19,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The maximum number of colors to use when quantizing an image. /// public const int MaxColors = 256; + + /// + /// The minumim dithering scale used to adjust the amount of dither. + /// + public const float MinDitherScale = 0; + + /// + /// The max dithering scale used to adjust the amount of dither. + /// + public const float MaxDitherScale = 1F; + + /// + /// Gets the default dithering algorithm to use. + /// + public static IDither DefaultDither { get; } = KnownDitherings.FloydSteinberg; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs new file mode 100644 index 000000000..5c1daf183 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerOptions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Processing.Processors.Dithering; + +namespace SixLabors.ImageSharp.Processing.Processors.Quantization +{ + /// + /// Defines options for quantization. + /// + public class QuantizerOptions + { + private float ditherScale = QuantizerConstants.MaxDitherScale; + private int maxColors = QuantizerConstants.MaxColors; + + /// + /// Gets or sets the algorithm to apply to the output image. + /// Defaults to ; set to for no dithering. + /// + public IDither Dither { get; set; } = QuantizerConstants.DefaultDither; + + /// + /// Gets or sets the dithering scale used to adjust the amount of dither. Range 0..1. + /// Defaults to . + /// + public float DitherScale + { + get { return this.ditherScale; } + set { this.ditherScale = value.Clamp(QuantizerConstants.MinDitherScale, QuantizerConstants.MaxDitherScale); } + } + + /// + /// Gets or sets the maximum number of colors to hold in the color palette. Range 0..256. + /// Defaults to . + /// + public int MaxColors + { + get { return this.maxColors; } + set { this.maxColors = value.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs index c912572f0..d95ed5aab 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WebSafePaletteQuantizer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -10,30 +10,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WebSafePaletteQuantizer : PaletteQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WebSafePaletteQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WebSafePaletteQuantizer(bool dither) - : base(Color.WebSafePalette, dither) + public WebSafePaletteQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - public WebSafePaletteQuantizer(IErrorDiffuser diffuser) - : base(Color.WebSafePalette, diffuser) + /// The quantizer options defining quantization rules. + public WebSafePaletteQuantizer(QuantizerOptions options) + : base(Color.WebSafePalette, options) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs index cd320a9a3..8f8e38dd9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WernerPaletteQuantizer.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.Processing.Processors.Dithering; - namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -11,30 +9,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// public class WernerPaletteQuantizer : PaletteQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WernerPaletteQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image - public WernerPaletteQuantizer(bool dither) - : base(Color.WernerPalette, dither) + public WernerPaletteQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - public WernerPaletteQuantizer(IErrorDiffuser diffuser) - : base(Color.WernerPalette, diffuser) + /// The quantizer options defining quantization rules. + public WernerPaletteQuantizer(QuantizerOptions options) + : base(Color.WernerPalette, options) { } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs index 2de02ebb3..d15db74e6 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs @@ -10,8 +10,6 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -// TODO: Isn't an AOS ("array of structures") layout more efficient & more readable than SOA ("structure of arrays") for this particular use case? -// (T, R, G, B, A, M2) could be grouped together! Investigate a ColorMoment struct. namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// @@ -34,8 +32,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// /// The pixel format. - internal sealed class WuFrameQuantizer : FrameQuantizer - where TPixel : struct, IPixel + internal struct WuFrameQuantizer : IFrameQuantizer + where TPixel : unmanaged, IPixel { private readonly MemoryAllocator memoryAllocator; @@ -68,217 +66,124 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private const int TableLength = IndexCount * IndexCount * IndexCount * IndexAlphaCount; - /// - /// Moment of P(c). - /// - private IMemoryOwner vwt; - - /// - /// Moment of r*P(c). - /// - private IMemoryOwner vmr; - - /// - /// Moment of g*P(c). - /// - private IMemoryOwner vmg; - - /// - /// Moment of b*P(c). - /// - private IMemoryOwner vmb; - - /// - /// Moment of a*P(c). - /// - private IMemoryOwner vma; - - /// - /// Moment of c^2*P(c). - /// - private IMemoryOwner m2; - - /// - /// Color space tag. - /// - private IMemoryOwner tag; - - /// - /// Maximum allowed color depth - /// - private int colors; - - /// - /// The reduced image palette - /// - private TPixel[] palette; - - /// - /// The color cube representing the image palette - /// - private Box[] colorCube; - + private IMemoryOwner momentsOwner; + private IMemoryOwner tagsOwner; + private IMemoryOwner paletteOwner; + private ReadOnlyMemory palette; + private int maxColors; + private readonly Box[] colorCube; + private EuclideanPixelMap pixelMap; + private readonly bool isDithering; private bool isDisposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The configuration which allows altering default behaviour or extending the library. - /// The Wu quantizer - /// - /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, - /// the second pass quantizes a color based on the position in the histogram. - /// - public WuFrameQuantizer(Configuration configuration, WuQuantizer quantizer) - : this(configuration, quantizer, quantizer.MaxColors) + /// The quantizer options defining quantization rules. + [MethodImpl(InliningOptions.ShortMethod)] + public WuFrameQuantizer(Configuration configuration, QuantizerOptions options) { - } + Guard.NotNull(configuration, nameof(configuration)); + Guard.NotNull(options, nameof(options)); - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The Wu quantizer. - /// The maximum number of colors to hold in the color palette. - /// - /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram, - /// the second pass quantizes a color based on the position in the histogram. - /// - public WuFrameQuantizer(Configuration configuration, WuQuantizer quantizer, int maxColors) - : base(configuration, quantizer, false) - { + this.Configuration = configuration; + this.Options = options; + this.maxColors = this.Options.MaxColors; this.memoryAllocator = this.Configuration.MemoryAllocator; + this.momentsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.tagsOwner = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + this.paletteOwner = this.memoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); + this.palette = default; + this.colorCube = new Box[this.maxColors]; + this.isDisposed = false; + this.pixelMap = default; + this.isDithering = this.isDithering = !(this.Options.Dither is null); + } - this.vwt = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vmr = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vmg = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vmb = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.vma = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.m2 = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); - this.tag = this.memoryAllocator.Allocate(TableLength, AllocationOptions.Clean); + /// + public Configuration Configuration { get; } - this.colors = maxColors; - } + /// + public QuantizerOptions Options { get; } /// - protected override void Dispose(bool disposing) + public ReadOnlyMemory Palette { - if (this.isDisposed) - { - return; - } - - if (disposing) + get { - this.vwt?.Dispose(); - this.vmr?.Dispose(); - this.vmg?.Dispose(); - this.vmb?.Dispose(); - this.vma?.Dispose(); - this.m2?.Dispose(); - this.tag?.Dispose(); + FrameQuantizerUtilities.CheckPaletteState(in this.palette); + return this.palette; } - - this.vwt = null; - this.vmr = null; - this.vmg = null; - this.vmb = null; - this.vma = null; - this.m2 = null; - this.tag = null; - - this.isDisposed = true; - base.Dispose(true); } - internal ReadOnlyMemory AotGetPalette() => this.GetPalette(); - /// - protected override ReadOnlyMemory GetPalette() + public void BuildPalette(ImageFrame source, Rectangle bounds) { - if (this.palette is null) - { - this.palette = new TPixel[this.colors]; - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - - for (int k = 0; k < this.colors; k++) - { - this.Mark(ref this.colorCube[k], (byte)k); + this.Build3DHistogram(source, bounds); + this.Get3DMoments(this.memoryAllocator); + this.BuildCube(); - float weight = Volume(ref this.colorCube[k], vwtSpan); + ReadOnlySpan momentsSpan = this.momentsOwner.GetSpan(); + Span paletteSpan = this.paletteOwner.GetSpan(); + for (int k = 0; k < this.maxColors; k++) + { + this.Mark(ref this.colorCube[k], (byte)k); - if (MathF.Abs(weight) > Constants.Epsilon) - { - float r = Volume(ref this.colorCube[k], vmrSpan); - float g = Volume(ref this.colorCube[k], vmgSpan); - float b = Volume(ref this.colorCube[k], vmbSpan); - float a = Volume(ref this.colorCube[k], vmaSpan); + Moment moment = Volume(ref this.colorCube[k], momentsSpan); - ref TPixel color = ref this.palette[k]; - color.FromScaledVector4(new Vector4(r, g, b, a) / weight / 255F); - } + if (moment.Weight > 0) + { + ref TPixel color = ref paletteSpan[k]; + color.FromScaledVector4(moment.Normalize()); } } - return this.palette; + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, this.maxColors); + this.pixelMap = new EuclideanPixelMap(this.Configuration, result); + this.palette = result; } /// - protected override void FirstPass(ImageFrame source, int width, int height) - { - this.Build3DHistogram(source, width, height); - this.Get3DMoments(this.memoryAllocator); - this.BuildCube(); - } + [MethodImpl(InliningOptions.ShortMethod)] + public readonly IndexedImageFrame QuantizeFrame(ImageFrame source, Rectangle bounds) + => FrameQuantizerUtilities.QuantizeFrame(ref Unsafe.AsRef(this), source, bounds); /// - protected override void SecondPass(ImageFrame source, Span output, ReadOnlySpan palette, int width, int height) + public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - // Load up the values for the first pixel. We can use these to speed up the second - // pass of the algorithm by avoiding transforming rows of identical color. - TPixel sourcePixel = source[0, 0]; - TPixel previousPixel = sourcePixel; - byte pixelValue = this.QuantizePixel(ref sourcePixel); - TPixel transformedPixel = palette[pixelValue]; - - for (int y = 0; y < height; y++) + if (this.isDithering) { - Span row = source.GetPixelRowSpan(y); - - // And loop through each column - for (int x = 0; x < width; x++) - { - // Get the pixel. - sourcePixel = row[x]; - - // Check if this is the same as the last pixel. If so use that value - // rather than calculating it again. This is an inexpensive optimization. - if (!previousPixel.Equals(sourcePixel)) - { - // Quantize the pixel - pixelValue = this.QuantizePixel(ref sourcePixel); + return (byte)this.pixelMap.GetClosestColor(color, out match); + } - // And setup the previous pointer - previousPixel = sourcePixel; + Rgba32 rgba = default; + color.ToRgba32(ref rgba); - if (this.Dither) - { - transformedPixel = palette[pixelValue]; - } - } + int r = rgba.R >> (8 - IndexBits); + int g = rgba.G >> (8 - IndexBits); + int b = rgba.B >> (8 - IndexBits); + int a = rgba.A >> (8 - IndexAlphaBits); - if (this.Dither) - { - // Apply the dithering matrix. We have to reapply the value now as the original has changed. - this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, 0, width, height); - } + ReadOnlySpan tagSpan = this.tagsOwner.GetSpan(); + byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); + match = Unsafe.Add(ref paletteRef, index); + return index; + } - output[(y * source.Width) + x] = pixelValue; - } + /// + public void Dispose() + { + if (!this.isDisposed) + { + this.isDisposed = true; + this.momentsOwner?.Dispose(); + this.tagsOwner?.Dispose(); + this.paletteOwner?.Dispose(); + this.momentsOwner = null; + this.tagsOwner = null; + this.paletteOwner = null; } } @@ -290,7 +195,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The blue value. /// The alpha value. /// The index. - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] private static int GetPaletteIndex(int r, int g, int b, int a) { return (r << ((IndexBits * 2) + IndexAlphaBits)) @@ -307,26 +212,26 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Computes sum over a box of any given statistic. /// /// The cube. - /// The moment. + /// The moment. /// The result. - private static float Volume(ref Box cube, Span moment) + private static Moment Volume(ref Box cube, ReadOnlySpan moments) { - return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; } /// @@ -334,55 +239,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// The cube. /// The direction. - /// The moment. + /// The moment. /// The result. - private static long Bottom(ref Box cube, int direction, Span moment) + private static Moment Bottom(ref Box cube, int direction, ReadOnlySpan moments) { switch (direction) { // Red case 3: - return -moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Green case 2: - return -moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Blue case 1: - return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; // Alpha case 0: - return -moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + return -moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -395,55 +300,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The cube. /// The direction. /// The position. - /// The moment. + /// The moment. /// The result. - private static long Top(ref Box cube, int direction, int position, Span moment) + private static Moment Top(ref Box cube, int direction, int position, ReadOnlySpan moments) { switch (direction) { // Red case 3: - return moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)]; + return moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(position, cube.GMax, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(position, cube.GMax, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(position, cube.GMin, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(position, cube.GMin, cube.BMin, cube.AMin)]; // Green case 2: - return moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)]; + return moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, position, cube.BMax, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, position, cube.BMin, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, position, cube.BMax, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, position, cube.BMin, cube.AMin)]; // Blue case 1: - return moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)]; + return moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMax)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, position, cube.AMin)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMax)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, position, cube.AMin)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMax)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, position, cube.AMin)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMax)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, position, cube.AMin)]; // Alpha case 0: - return moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] - - moment[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] - - moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] - + moment[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] - - moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] - + moment[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] - + moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] - - moment[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)]; + return moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, position)] + - moments[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, position)] + - moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, position)] + + moments[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, position)] + - moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, position)] + + moments[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, position)] + + moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, position)] + - moments[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, position)]; default: throw new ArgumentOutOfRangeException(nameof(direction)); @@ -454,49 +359,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Builds a 3-D color histogram of counts, r/g/b, c^2. /// /// The source data. - /// The width in pixels of the image. - /// The height in pixels of the image. - private void Build3DHistogram(ImageFrame source, int width, int height) + /// The bounds within the source image to quantize. + private void Build3DHistogram(ImageFrame source, Rectangle bounds) { - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); + Span momentSpan = this.momentsOwner.GetSpan(); // Build up the 3-D color histogram - // Loop through each row - using (IMemoryOwner rgbaBuffer = this.memoryAllocator.Allocate(source.Width)) - { - for (int y = 0; y < height; y++) - { - Span row = source.GetPixelRowSpan(y); - Span rgbaSpan = rgbaBuffer.GetSpan(); - PixelOperations.Instance.ToRgba32(source.GetConfiguration(), row, rgbaSpan); - ref Rgba32 scanBaseRef = ref MemoryMarshal.GetReference(rgbaSpan); - - // And loop through each column - for (int x = 0; x < width; x++) - { - ref Rgba32 rgba = ref Unsafe.Add(ref scanBaseRef, x); + using IMemoryOwner buffer = this.memoryAllocator.Allocate(bounds.Width); + Span bufferSpan = buffer.GetSpan(); - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + Span row = source.GetPixelRowSpan(y).Slice(bounds.Left, bounds.Width); + PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); - int index = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1); + for (int x = 0; x < bufferSpan.Length; x++) + { + Rgba32 rgba = bufferSpan[x]; - vwtSpan[index]++; - vmrSpan[index] += rgba.R; - vmgSpan[index] += rgba.G; - vmbSpan[index] += rgba.B; - vmaSpan[index] += rgba.A; + int r = (rgba.R >> (8 - IndexBits)) + 1; + int g = (rgba.G >> (8 - IndexBits)) + 1; + int b = (rgba.B >> (8 - IndexBits)) + 1; + int a = (rgba.A >> (8 - IndexAlphaBits)) + 1; - var vector = new Vector4(rgba.R, rgba.G, rgba.B, rgba.A); - m2Span[index] += Vector4.Dot(vector, vector); - } + momentSpan[GetPaletteIndex(r, g, b, a)] += rgba; } } } @@ -504,106 +390,41 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Converts the histogram into moments so that we can rapidly calculate the sums of the above quantities over any desired box. /// - /// The memory allocator used for allocating buffers. - private void Get3DMoments(MemoryAllocator memoryAllocator) + /// The memory allocator used for allocating buffers. + private void Get3DMoments(MemoryAllocator allocator) { - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - Span m2Span = this.m2.GetSpan(); - - using (IMemoryOwner volume = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeR = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeG = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeB = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volumeA = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner volume2 = memoryAllocator.Allocate(IndexCount * IndexAlphaCount)) - using (IMemoryOwner area = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaR = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaG = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaB = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner areaA = memoryAllocator.Allocate(IndexAlphaCount)) - using (IMemoryOwner area2 = memoryAllocator.Allocate(IndexAlphaCount)) + using IMemoryOwner volume = allocator.Allocate(IndexCount * IndexAlphaCount); + using IMemoryOwner area = allocator.Allocate(IndexAlphaCount); + + Span momentSpan = this.momentsOwner.GetSpan(); + Span volumeSpan = volume.GetSpan(); + Span areaSpan = area.GetSpan(); + int baseIndex = GetPaletteIndex(1, 0, 0, 0); + + for (int r = 1; r < IndexCount; r++) { - Span volumeSpan = volume.GetSpan(); - Span volumeRSpan = volumeR.GetSpan(); - Span volumeGSpan = volumeG.GetSpan(); - Span volumeBSpan = volumeB.GetSpan(); - Span volumeASpan = volumeA.GetSpan(); - Span volume2Span = volume2.GetSpan(); - - Span areaSpan = area.GetSpan(); - Span areaRSpan = areaR.GetSpan(); - Span areaGSpan = areaG.GetSpan(); - Span areaBSpan = areaB.GetSpan(); - Span areaASpan = areaA.GetSpan(); - Span area2Span = area2.GetSpan(); - - for (int r = 1; r < IndexCount; r++) + volumeSpan.Clear(); + + for (int g = 1; g < IndexCount; g++) { - volume.Clear(); - volumeR.Clear(); - volumeG.Clear(); - volumeB.Clear(); - volumeA.Clear(); - volume2.Clear(); - - for (int g = 1; g < IndexCount; g++) + areaSpan.Clear(); + + for (int b = 1; b < IndexCount; b++) { - area.Clear(); - areaR.Clear(); - areaG.Clear(); - areaB.Clear(); - areaA.Clear(); - area2.Clear(); - - for (int b = 1; b < IndexCount; b++) + Moment line = default; + + for (int a = 1; a < IndexAlphaCount; a++) { - long line = 0; - long lineR = 0; - long lineG = 0; - long lineB = 0; - long lineA = 0; - double line2 = 0; - - for (int a = 1; a < IndexAlphaCount; a++) - { - int ind1 = GetPaletteIndex(r, g, b, a); - - line += vwtSpan[ind1]; - lineR += vmrSpan[ind1]; - lineG += vmgSpan[ind1]; - lineB += vmbSpan[ind1]; - lineA += vmaSpan[ind1]; - line2 += m2Span[ind1]; - - areaSpan[a] += line; - areaRSpan[a] += lineR; - areaGSpan[a] += lineG; - areaBSpan[a] += lineB; - areaASpan[a] += lineA; - area2Span[a] += line2; - - int inv = (b * IndexAlphaCount) + a; - - volumeSpan[inv] += areaSpan[a]; - volumeRSpan[inv] += areaRSpan[a]; - volumeGSpan[inv] += areaGSpan[a]; - volumeBSpan[inv] += areaBSpan[a]; - volumeASpan[inv] += areaASpan[a]; - volume2Span[inv] += area2Span[a]; - - int ind2 = ind1 - GetPaletteIndex(1, 0, 0, 0); - - vwtSpan[ind1] = vwtSpan[ind2] + volumeSpan[inv]; - vmrSpan[ind1] = vmrSpan[ind2] + volumeRSpan[inv]; - vmgSpan[ind1] = vmgSpan[ind2] + volumeGSpan[inv]; - vmbSpan[ind1] = vmbSpan[ind2] + volumeBSpan[inv]; - vmaSpan[ind1] = vmaSpan[ind2] + volumeASpan[inv]; - m2Span[ind1] = m2Span[ind2] + volume2Span[inv]; - } + int ind1 = GetPaletteIndex(r, g, b, a); + line += momentSpan[ind1]; + + areaSpan[a] += line; + + int inv = (b * IndexAlphaCount) + a; + volumeSpan[inv] += areaSpan[a]; + + int ind2 = ind1 - baseIndex; + momentSpan[ind1] = momentSpan[ind2] + volumeSpan[inv]; } } } @@ -617,33 +438,29 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The . private double Variance(ref Box cube) { - float dr = Volume(ref cube, this.vmr.GetSpan()); - float dg = Volume(ref cube, this.vmg.GetSpan()); - float db = Volume(ref cube, this.vmb.GetSpan()); - float da = Volume(ref cube, this.vma.GetSpan()); - - Span m2Span = this.m2.GetSpan(); - - double moment = - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] - + m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] - - m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] - + m2Span[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; - - var vector = new Vector4(dr, dg, db, da); - return moment - (Vector4.Dot(vector, vector) / Volume(ref cube, this.vwt.GetSpan())); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + + Moment volume = Volume(ref cube, momentSpan); + Moment variance = + momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMax, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMax, cube.GMax, cube.BMin, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMax, cube.AMin)] + + momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMax, cube.GMin, cube.BMin, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMax, cube.AMin)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMax, cube.BMin, cube.AMin)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMax)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMax, cube.AMin)] + - momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMax)] + + momentSpan[GetPaletteIndex(cube.RMin, cube.GMin, cube.BMin, cube.AMin)]; + + var vector = new Vector4(volume.R, volume.G, volume.B, volume.A); + return variance.Moment2 - (Vector4.Dot(vector, vector) / volume.Weight); } /// @@ -658,60 +475,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// The first position. /// The last position. /// The cutting point. - /// The whole red. - /// The whole green. - /// The whole blue. - /// The whole alpha. - /// The whole weight. + /// The whole moment. /// The . - private float Maximize(ref Box cube, int direction, int first, int last, out int cut, float wholeR, float wholeG, float wholeB, float wholeA, float wholeW) + private float Maximize(ref Box cube, int direction, int first, int last, out int cut, Moment whole) { - Span vwtSpan = this.vwt.GetSpan(); - Span vmrSpan = this.vmr.GetSpan(); - Span vmgSpan = this.vmg.GetSpan(); - Span vmbSpan = this.vmb.GetSpan(); - Span vmaSpan = this.vma.GetSpan(); - - long baseR = Bottom(ref cube, direction, vmrSpan); - long baseG = Bottom(ref cube, direction, vmgSpan); - long baseB = Bottom(ref cube, direction, vmbSpan); - long baseA = Bottom(ref cube, direction, vmaSpan); - long baseW = Bottom(ref cube, direction, vwtSpan); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + Moment bottom = Bottom(ref cube, direction, momentSpan); float max = 0F; cut = -1; for (int i = first; i < last; i++) { - float halfR = baseR + Top(ref cube, direction, i, vmrSpan); - float halfG = baseG + Top(ref cube, direction, i, vmgSpan); - float halfB = baseB + Top(ref cube, direction, i, vmbSpan); - float halfA = baseA + Top(ref cube, direction, i, vmaSpan); - float halfW = baseW + Top(ref cube, direction, i, vwtSpan); + Moment half = bottom + Top(ref cube, direction, i, momentSpan); - if (MathF.Abs(halfW) < Constants.Epsilon) + if (half.Weight == 0) { continue; } - var vector = new Vector4(halfR, halfG, halfB, halfA); - float temp = Vector4.Dot(vector, vector) / halfW; + var vector = new Vector4(half.R, half.G, half.B, half.A); + float temp = Vector4.Dot(vector, vector) / half.Weight; - halfW = wholeW - halfW; + half = whole - half; - if (MathF.Abs(halfW) < Constants.Epsilon) + if (half.Weight == 0) { continue; } - halfR = wholeR - halfR; - halfG = wholeG - halfG; - halfB = wholeB - halfB; - halfA = wholeA - halfA; - - vector = new Vector4(halfR, halfG, halfB, halfA); - - temp += Vector4.Dot(vector, vector) / halfW; + vector = new Vector4(half.R, half.G, half.B, half.A); + temp += Vector4.Dot(vector, vector) / half.Weight; if (temp > max) { @@ -731,33 +525,30 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// Returns a value indicating whether the box has been split. private bool Cut(ref Box set1, ref Box set2) { - float wholeR = Volume(ref set1, this.vmr.GetSpan()); - float wholeG = Volume(ref set1, this.vmg.GetSpan()); - float wholeB = Volume(ref set1, this.vmb.GetSpan()); - float wholeA = Volume(ref set1, this.vma.GetSpan()); - float wholeW = Volume(ref set1, this.vwt.GetSpan()); + ReadOnlySpan momentSpan = this.momentsOwner.GetSpan(); + Moment whole = Volume(ref set1, momentSpan); - float maxr = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutr, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxg = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutg, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxb = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutb, wholeR, wholeG, wholeB, wholeA, wholeW); - float maxa = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cuta, wholeR, wholeG, wholeB, wholeA, wholeW); + float maxR = this.Maximize(ref set1, 3, set1.RMin + 1, set1.RMax, out int cutR, whole); + float maxG = this.Maximize(ref set1, 2, set1.GMin + 1, set1.GMax, out int cutG, whole); + float maxB = this.Maximize(ref set1, 1, set1.BMin + 1, set1.BMax, out int cutB, whole); + float maxA = this.Maximize(ref set1, 0, set1.AMin + 1, set1.AMax, out int cutA, whole); int dir; - if ((maxr >= maxg) && (maxr >= maxb) && (maxr >= maxa)) + if ((maxR >= maxG) && (maxR >= maxB) && (maxR >= maxA)) { dir = 3; - if (cutr < 0) + if (cutR < 0) { return false; } } - else if ((maxg >= maxr) && (maxg >= maxb) && (maxg >= maxa)) + else if ((maxG >= maxR) && (maxG >= maxB) && (maxG >= maxA)) { dir = 2; } - else if ((maxb >= maxr) && (maxb >= maxg) && (maxb >= maxa)) + else if ((maxB >= maxR) && (maxB >= maxG) && (maxB >= maxA)) { dir = 1; } @@ -775,7 +566,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization { // Red case 3: - set2.RMin = set1.RMax = cutr; + set2.RMin = set1.RMax = cutR; set2.GMin = set1.GMin; set2.BMin = set1.BMin; set2.AMin = set1.AMin; @@ -783,7 +574,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Green case 2: - set2.GMin = set1.GMax = cutg; + set2.GMin = set1.GMax = cutG; set2.RMin = set1.RMin; set2.BMin = set1.BMin; set2.AMin = set1.AMin; @@ -791,7 +582,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Blue case 1: - set2.BMin = set1.BMax = cutb; + set2.BMin = set1.BMax = cutB; set2.RMin = set1.RMin; set2.GMin = set1.GMin; set2.AMin = set1.AMin; @@ -799,7 +590,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Alpha case 0: - set2.AMin = set1.AMax = cuta; + set2.AMin = set1.AMax = cutA; set2.RMin = set1.RMin; set2.GMin = set1.GMin; set2.BMin = set1.BMin; @@ -819,7 +610,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// A label. private void Mark(ref Box cube, byte label) { - Span tagSpan = this.tag.GetSpan(); + Span tagSpan = this.tagsOwner.GetSpan(); for (int r = cube.RMin + 1; r <= cube.RMax; r++) { @@ -841,8 +632,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// private void BuildCube() { - this.colorCube = new Box[this.colors]; - Span vv = stackalloc double[this.colors]; + // Store the volume variance. + using IMemoryOwner vvOwner = this.Configuration.MemoryAllocator.Allocate(this.maxColors); + Span vv = vvOwner.GetSpan(); ref Box cube = ref this.colorCube[0]; cube.RMin = cube.GMin = cube.BMin = cube.AMin = 0; @@ -851,14 +643,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization int next = 0; - for (int i = 1; i < this.colors; i++) + for (int i = 1; i < this.maxColors; i++) { ref Box nextCube = ref this.colorCube[next]; ref Box currentCube = ref this.colorCube[i]; if (this.Cut(ref nextCube, ref currentCube)) { - vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0F; - vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0F; + vv[next] = nextCube.Volume > 1 ? this.Variance(ref nextCube) : 0D; + vv[i] = currentCube.Volume > 1 ? this.Variance(ref currentCube) : 0D; } else { @@ -880,41 +672,98 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization if (temp <= 0D) { - this.colors = i + 1; + this.maxColors = i + 1; break; } } } - /// - /// Process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private byte QuantizePixel(ref TPixel pixel) + private struct Moment { - if (this.Dither) + /// + /// Moment of r*P(c). + /// + public long R; + + /// + /// Moment of g*P(c). + /// + public long G; + + /// + /// Moment of b*P(c). + /// + public long B; + + /// + /// Moment of a*P(c). + /// + public long A; + + /// + /// Moment of P(c). + /// + public long Weight; + + /// + /// Moment of c^2*P(c). + /// + public double Moment2; + + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator +(Moment x, Moment y) { - // The colors have changed so we need to use Euclidean distance calculation to - // find the closest value. - return this.GetClosestPixel(ref pixel); + x.R += y.R; + x.G += y.G; + x.B += y.B; + x.A += y.A; + x.Weight += y.Weight; + x.Moment2 += y.Moment2; + return x; } - // Expected order r->g->b->a - Rgba32 rgba = default; - pixel.ToRgba32(ref rgba); + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator -(Moment x, Moment y) + { + x.R -= y.R; + x.G -= y.G; + x.B -= y.B; + x.A -= y.A; + x.Weight -= y.Weight; + x.Moment2 -= y.Moment2; + return x; + } - int r = rgba.R >> (8 - IndexBits); - int g = rgba.G >> (8 - IndexBits); - int b = rgba.B >> (8 - IndexBits); - int a = rgba.A >> (8 - IndexAlphaBits); + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator -(Moment x) + { + x.R = -x.R; + x.G = -x.G; + x.B = -x.B; + x.A = -x.A; + x.Weight = -x.Weight; + x.Moment2 = -x.Moment2; + return x; + } - Span tagSpan = this.tag.GetSpan(); + [MethodImpl(InliningOptions.ShortMethod)] + public static Moment operator +(Moment x, Rgba32 y) + { + x.R += y.R; + x.G += y.G; + x.B += y.B; + x.A += y.A; + x.Weight++; + + var vector = new Vector4(y.R, y.G, y.B, y.A); + x.Moment2 += Vector4.Dot(vector, vector); + + return x; + } - return tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 Normalize() + => new Vector4(this.R, this.G, this.B, this.A) / this.Weight / 255F; } /// @@ -968,10 +817,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization public int Volume; /// - public override bool Equals(object obj) => obj is Box box && this.Equals(box); + public readonly override bool Equals(object obj) + => obj is Box box + && this.Equals(box); /// - public bool Equals(Box other) => + public readonly bool Equals(Box other) => this.RMin == other.RMin && this.RMax == other.RMax && this.GMin == other.GMin @@ -983,7 +834,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization && this.Volume == other.Volume; /// - public override int GetHashCode() + public readonly override int GetHashCode() { HashCode hash = default; hash.Add(this.RMin); diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs index 3f2deaec0..d2e33aa1f 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs @@ -2,89 +2,46 @@ // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Dithering; namespace SixLabors.ImageSharp.Processing.Processors.Quantization { /// /// Allows the quantization of images pixels using Xiaolin Wu's Color Quantizer - /// - /// By default the quantizer uses dithering and a color palette of a maximum length of 255 - /// /// public class WuQuantizer : IQuantizer { - /// - /// Initializes a new instance of the class. - /// - public WuQuantizer() - : this(true) - { - } + private static readonly QuantizerOptions DefaultOptions = new QuantizerOptions(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// using the default . /// - /// The maximum number of colors to hold in the color palette - public WuQuantizer(int maxColors) - : this(GetDiffuser(true), maxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Whether to apply dithering to the output image - public WuQuantizer(bool dither) - : this(GetDiffuser(dither), QuantizerConstants.MaxColors) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The error diffusion algorithm, if any, to apply to the output image - public WuQuantizer(IErrorDiffuser diffuser) - : this(diffuser, QuantizerConstants.MaxColors) + public WuQuantizer() + : this(DefaultOptions) { } /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - /// The maximum number of colors to hold in the color palette - public WuQuantizer(IErrorDiffuser diffuser, int maxColors) + /// The quantizer options defining quantization rules. + public WuQuantizer(QuantizerOptions options) { - this.Diffuser = diffuser; - this.MaxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); + Guard.NotNull(options, nameof(options)); + this.Options = options; } /// - public IErrorDiffuser Diffuser { get; } - - /// - /// Gets the maximum number of colors to hold in the color palette. - /// - public int MaxColors { get; } + public QuantizerOptions Options { get; } /// public IFrameQuantizer CreateFrameQuantizer(Configuration configuration) - where TPixel : struct, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - return new WuFrameQuantizer(configuration, this); - } + where TPixel : unmanaged, IPixel + => this.CreateFrameQuantizer(configuration, this.Options); - /// - public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, int maxColors) - where TPixel : struct, IPixel - { - Guard.NotNull(configuration, nameof(configuration)); - maxColors = maxColors.Clamp(QuantizerConstants.MinColors, QuantizerConstants.MaxColors); - return new WuFrameQuantizer(configuration, this, maxColors); - } - - private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null; + /// + public IFrameQuantizer CreateFrameQuantizer(Configuration configuration, QuantizerOptions options) + where TPixel : unmanaged, IPixel + => new WuFrameQuantizer(configuration, options); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs deleted file mode 100644 index 1b9ff82bf..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Provides the base methods to perform affine transforms on an image. - /// - /// The pixel format. - internal class AffineTransformProcessor : TransformProcessor - where TPixel : struct, IPixel - { - private Size targetSize; - private Matrix3x2 transformMatrix; - private readonly IResampler resampler; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.targetSize = definition.TargetDimensions; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetTargetSize() => this.targetSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Handle transforms that result in output identical to the original. - if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix3x2.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.targetSize.Width; - Rectangle sourceBounds = this.SourceRectangle; - var targetBounds = new Rectangle(Point.Empty, this.targetSize); - Configuration configuration = this.Configuration; - - // Convert from screen to world space. - Matrix3x2.Invert(this.transformMatrix, out Matrix3x2 matrix); - - if (this.resampler is NearestNeighborResampler) - { - ParallelHelper.IterateRows( - targetBounds, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - var point = Point.Transform(new Point(x, y), matrix); - if (sourceBounds.Contains(point.X, point.Y)) - { - destRow[x] = source[point.X, point.Y]; - } - } - } - }); - - return; - } - - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - - try - { - ParallelHelper.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref kernel.GetYStartReference(y); - ref float xSpanRef = ref kernel.GetXStartReference(y); - - for (int x = 0; x < width; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - var point = Vector2.Transform(new Vector2(x, y), matrix); - kernel.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - source.PixelBuffer, - vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive( - configuration, - vectorSpan, - targetRowSpan); - } - }); - } - finally - { - kernel.Dispose(); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index a286e8fa2..a366fd51d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -2,8 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -13,9 +14,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class CropProcessor : TransformProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - private Rectangle cropRectangle; + private readonly Rectangle cropRectangle; /// /// Initializes a new instance of the class. @@ -29,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms => this.cropRectangle = definition.CropRectangle; /// - protected override Size GetTargetSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); + protected override Size GetDestinationSize() => new Size(this.cropRectangle.Width, this.cropRectangle.Height); /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) @@ -40,28 +41,55 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms && this.SourceRectangle == this.cropRectangle) { // the cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return; } Rectangle bounds = this.cropRectangle; // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration) - .MultiplyMinimumPixelsPerTask(4); + ParallelExecutionSettings parallelSettings = + ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - ParallelHelper.IterateRows( + var operation = new RowOperation(bounds, source, destination); + + ParallelRowIterator.IterateRows( bounds, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y).Slice(bounds.Left); - Span targetRow = destination.GetPixelRowSpan(y - bounds.Top); - sourceRow.Slice(0, bounds.Width).CopyTo(targetRow); - } - }); + in parallelSettings, + in operation); + } + + /// + /// A implementing the processor logic for . + /// + private readonly struct RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + /// + /// Initializes a new instance of the struct. + /// + /// The target processing bounds for the current instance. + /// The source for the current instance. + /// The destination for the current instance. + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) + { + this.bounds = bounds; + this.source = source; + this.destination = destination; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index d5aaaf515..b2a50a988 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new EntropyCropProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs index 74d719fbe..8c91e1953 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class EntropyCropProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly EntropyCropProcessor definition; diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs index 6db03d5b4..55eebba4f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/IResampler.cs @@ -1,6 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -21,5 +23,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The /// float GetValue(float x); + + /// + /// Applies a transformation upon an image. + /// + /// The pixel format. + /// The transforming image processor. + void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel; } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs new file mode 100644 index 000000000..02df8282f --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/IResamplingTransformImageProcessor{TPixel}.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Implements an algorithm to alter the pixels of an image via resampling transforms. + /// + /// The pixel format. + public interface IResamplingTransformImageProcessor : IImageProcessor + where TPixel : unmanaged, IPixel + { + /// + /// Applies a resampling transform with the given sampler. + /// + /// The type of sampler. + /// The sampler to use. + void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler; + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs similarity index 87% rename from src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs index 849f06166..fec41dbff 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs @@ -19,9 +19,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions) { Guard.NotNull(sampler, nameof(sampler)); + Guard.MustBeValueType(sampler, nameof(sampler)); + this.Sampler = sampler; this.TransformMatrix = matrix; - this.TargetDimensions = targetDimensions; + this.DestinationSize = targetDimensions; } /// @@ -35,9 +37,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Matrix3x2 TransformMatrix { get; } /// - /// Gets the target dimensions to constrain the transformed image to. + /// Gets the destination size to constrain the transformed image to. /// - public Size TargetDimensions { get; } + public Size DestinationSize { get; } /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs new file mode 100644 index 000000000..a3c8f7108 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform affine transforms on an image. + /// + /// The pixel format. + internal class AffineTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly Size destinationSize; + private readonly Matrix3x2 transformMatrix; + private readonly IResampler resampler; + private ImageFrame source; + private ImageFrame destination; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public AffineTransformProcessor(Configuration configuration, AffineTransformProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } + + protected override Size GetDestinationSize() => this.destinationSize; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + { + Configuration configuration = this.Configuration; + ImageFrame source = this.source; + ImageFrame destination = this.destination; + Matrix3x2 matrix = this.transformMatrix; + + // Handle transforms that result in output identical to the original. + if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } + + // Convert from screen to world space. + Matrix3x2.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNAffineOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + + var operation = new AffineOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNAffineOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix3x2 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNAffineOperation( + ImageFrame source, + ImageFrame destination, + Matrix3x2 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = source.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + + private readonly struct AffineOperation : IRowOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Buffer2D yKernelBuffer; + private readonly Buffer2D xKernelBuffer; + private readonly TResampler sampler; + private readonly Matrix3x2 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public AffineOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix3x2 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Buffer2D sourceBuffer = this.source.PixelBuffer; + + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); + + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + var point = Vector2.Transform(new Vector2(x, y), this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs similarity index 93% rename from src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs index a059fb819..534832d13 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new AutoOrientProcessor(configuration, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs similarity index 99% rename from src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs index 90edcfac5..be1388dce 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/AutoOrientProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AutoOrientProcessor{TPixel}.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class AutoOrientProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs similarity index 95% rename from src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs index 60e22e2d0..b154aba88 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new FlipProcessor(configuration, this, source, sourceRectangle); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs similarity index 71% rename from src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index d3afc7205..470eafcd8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -3,9 +3,8 @@ using System; using System.Buffers; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class FlipProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private readonly FlipProcessor definition; @@ -55,20 +54,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void FlipX(ImageFrame source, Configuration configuration) { int height = source.Height; + using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); + Span temp = tempBuffer.Memory.Span; - using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width)) + for (int yTop = 0; yTop < height / 2; yTop++) { - Span temp = tempBuffer.Memory.Span; - - for (int yTop = 0; yTop < height / 2; yTop++) - { - int yBottom = height - yTop - 1; - Span topRow = source.GetPixelRowSpan(yBottom); - Span bottomRow = source.GetPixelRowSpan(yTop); - topRow.CopyTo(temp); - bottomRow.CopyTo(topRow); - temp.CopyTo(bottomRow); - } + int yBottom = height - yTop - 1; + Span topRow = source.GetPixelRowSpan(yBottom); + Span bottomRow = source.GetPixelRowSpan(yTop); + topRow.CopyTo(temp); + bottomRow.CopyTo(topRow); + temp.CopyTo(bottomRow); } } @@ -79,16 +75,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new RowOperation(source); + ParallelRowIterator.IterateRows( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - source.GetPixelRowSpan(y).Reverse(); - } - }); + source.Bounds(), + in operation); + } + + private readonly struct RowOperation : IRowOperation + { + private readonly ImageFrame source; + + [MethodImpl(InliningOptions.ShortMethod)] + public RowOperation(ImageFrame source) => this.source = source; + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) => this.source.GetPixelRowSpan(y).Reverse(); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs new file mode 100644 index 000000000..0a00cf8e9 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/LinearTransformUtilities.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Utility methods for affine and projective transforms. + /// + internal static class LinearTransformUtilities + { + [MethodImpl(InliningOptions.ShortMethod)] + internal static int GetSamplingRadius(in TResampler sampler, int sourceSize, int destinationSize) + where TResampler : struct, IResampler + { + double scale = sourceSize / destinationSize; + if (scale < 1) + { + scale = 1; + } + + return (int)Math.Ceiling(scale * sampler.Radius); + } + + [MethodImpl(InliningOptions.ShortMethod)] + internal static void Convolve( + in TResampler sampler, + Vector2 transformedPoint, + Buffer2D sourcePixels, + Span targetRow, + int column, + ref float yKernelSpanRef, + ref float xKernelSpanRef, + Vector2 radialExtents, + Vector4 maxSourceExtents) + where TResampler : struct, IResampler + where TPixel : unmanaged, IPixel + { + // Clamp sampling pixel radial extents to the source image edges + Vector2 minXY = transformedPoint - radialExtents; + Vector2 maxXY = transformedPoint + radialExtents; + + // left, top, right, bottom + var sourceExtents = new Vector4( + MathF.Ceiling(minXY.X), + MathF.Ceiling(minXY.Y), + MathF.Floor(maxXY.X), + MathF.Floor(maxXY.Y)); + + sourceExtents = Vector4Utilities.FastClamp(sourceExtents, Vector4.Zero, maxSourceExtents); + + int left = (int)sourceExtents.X; + int top = (int)sourceExtents.Y; + int right = (int)sourceExtents.Z; + int bottom = (int)sourceExtents.W; + + if (left == right || top == bottom) + { + return; + } + + CalculateWeights(in sampler, top, bottom, transformedPoint.Y, ref yKernelSpanRef); + CalculateWeights(in sampler, left, right, transformedPoint.X, ref xKernelSpanRef); + + Vector4 sum = Vector4.Zero; + for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) + { + float yWeight = Unsafe.Add(ref yKernelSpanRef, kernelY); + + for (int kernelX = 0, x = left; x <= right; x++, kernelX++) + { + float xWeight = Unsafe.Add(ref xKernelSpanRef, kernelX); + + // Values are first premultiplied to prevent darkening of edge pixels. + var current = sourcePixels[x, y].ToVector4(); + Vector4Utilities.Premultiply(ref current); + sum += current * xWeight * yWeight; + } + } + + // Reverse the premultiplication + Vector4Utilities.UnPremultiply(ref sum); + targetRow[column] = sum; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void CalculateWeights(in TResampler sampler, int min, int max, float point, ref float weightsRef) + where TResampler : struct, IResampler + { + float sum = 0; + for (int x = 0, i = min; i <= max; i++, x++) + { + float weight = sampler.GetValue(i - point); + sum += weight; + Unsafe.Add(ref weightsRef, x) = weight; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs similarity index 87% rename from src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs index d8a9c3ed9..f716ba701 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs @@ -19,9 +19,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions) { Guard.NotNull(sampler, nameof(sampler)); + Guard.MustBeValueType(sampler, nameof(sampler)); + this.Sampler = sampler; this.TransformMatrix = matrix; - this.TargetDimensions = targetDimensions; + this.DestinationSize = targetDimensions; } /// @@ -35,9 +37,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public Matrix4x4 TransformMatrix { get; } /// - /// Gets the target dimensions to constrain the transformed image to. + /// Gets the destination size to constrain the transformed image to. /// - public Size TargetDimensions { get; } + public Size DestinationSize { get; } /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs new file mode 100644 index 000000000..f348721d7 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -0,0 +1,229 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Provides the base methods to perform non-affine transforms on an image. + /// + /// The pixel format. + internal class ProjectiveTransformProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel + { + private readonly Size destinationSize; + private readonly IResampler resampler; + private readonly Matrix4x4 transformMatrix; + private ImageFrame source; + private ImageFrame destination; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration which allows altering default behaviour or extending the library. + /// The defining the processor parameters. + /// The source for the current processor instance. + /// The source area to process for the current processor instance. + public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) + : base(configuration, source, sourceRectangle) + { + this.destinationSize = definition.DestinationSize; + this.transformMatrix = definition.TransformMatrix; + this.resampler = definition.Sampler; + } + + protected override Size GetDestinationSize() => this.destinationSize; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) + { + this.source = source; + this.destination = destination; + this.resampler.ApplyTransform(this); + } + + /// + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + { + Configuration configuration = this.Configuration; + ImageFrame source = this.source; + ImageFrame destination = this.destination; + Matrix4x4 matrix = this.transformMatrix; + + // Handle transforms that result in output identical to the original. + if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + { + // The clone will be blank here copy all the pixel data over + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + return; + } + + // Convert from screen to world space. + Matrix4x4.Invert(matrix, out matrix); + + if (sampler is NearestNeighborResampler) + { + var nnOperation = new NNProjectiveOperation(source, destination, matrix); + ParallelRowIterator.IterateRows( + configuration, + destination.Bounds(), + in nnOperation); + + return; + } + + int yRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Height, destination.Height); + int xRadius = LinearTransformUtilities.GetSamplingRadius(in sampler, source.Width, destination.Width); + var radialExtents = new Vector2(xRadius, yRadius); + int yLength = (yRadius * 2) + 1; + int xLength = (xRadius * 2) + 1; + + // We use 2D buffers so that we can access the weight spans per row in parallel. + using Buffer2D yKernelBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); + using Buffer2D xKernelBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); + + int maxX = source.Width - 1; + int maxY = source.Height - 1; + var maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); + + var operation = new ProjectiveOperation( + configuration, + source, + destination, + yKernelBuffer, + xKernelBuffer, + in sampler, + matrix, + radialExtents, + maxSourceExtents); + + ParallelRowIterator.IterateRows, Vector4>( + configuration, + destination.Bounds(), + in operation); + } + + private readonly struct NNProjectiveOperation : IRowOperation + { + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Rectangle bounds; + private readonly Matrix4x4 matrix; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNProjectiveOperation( + ImageFrame source, + ImageFrame destination, + Matrix4x4 matrix) + { + this.source = source; + this.destination = destination; + this.bounds = source.Bounds(); + this.matrix = matrix; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span destRow = this.destination.GetPixelRowSpan(y); + + for (int x = 0; x < this.maxX; x++) + { + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + int px = (int)MathF.Round(point.X); + int py = (int)MathF.Round(point.Y); + + if (this.bounds.Contains(px, py)) + { + destRow[x] = this.source[px, py]; + } + } + } + } + + private readonly struct ProjectiveOperation : IRowOperation + where TResampler : struct, IResampler + { + private readonly Configuration configuration; + private readonly ImageFrame source; + private readonly ImageFrame destination; + private readonly Buffer2D yKernelBuffer; + private readonly Buffer2D xKernelBuffer; + private readonly TResampler sampler; + private readonly Matrix4x4 matrix; + private readonly Vector2 radialExtents; + private readonly Vector4 maxSourceExtents; + private readonly int maxX; + + [MethodImpl(InliningOptions.ShortMethod)] + public ProjectiveOperation( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Buffer2D yKernelBuffer, + Buffer2D xKernelBuffer, + in TResampler sampler, + Matrix4x4 matrix, + Vector2 radialExtents, + Vector4 maxSourceExtents) + { + this.configuration = configuration; + this.source = source; + this.destination = destination; + this.yKernelBuffer = yKernelBuffer; + this.xKernelBuffer = xKernelBuffer; + this.sampler = sampler; + this.matrix = matrix; + this.radialExtents = radialExtents; + this.maxSourceExtents = maxSourceExtents; + this.maxX = destination.Width; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y, Span span) + { + Buffer2D sourceBuffer = this.source.PixelBuffer; + + PixelOperations.Instance.ToVector4( + this.configuration, + this.destination.GetPixelRowSpan(y), + span); + + ref float yKernelSpanRef = ref MemoryMarshal.GetReference(this.yKernelBuffer.GetRowSpan(y)); + ref float xKernelSpanRef = ref MemoryMarshal.GetReference(this.xKernelBuffer.GetRowSpan(y)); + + for (int x = 0; x < this.maxX; x++) + { + // Use the single precision position to calculate correct bounding pixels + // otherwise we get rogue pixels outside of the bounds. + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); + LinearTransformUtilities.Convolve( + in this.sampler, + point, + sourceBuffer, + span, + x, + ref yKernelSpanRef, + ref xKernelSpanRef, + this.radialExtents, + this.maxSourceExtents); + } + + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + span, + this.destination.GetPixelRowSpan(y)); + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs similarity index 90% rename from src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs index aae66e9ea..b53e7b5c0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs @@ -28,14 +28,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source image size public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateRotationMatrixDegrees(degrees, sourceSize), + TransformUtilities.CreateRotationMatrixDegrees(degrees, sourceSize), sampler, sourceSize) => this.Degrees = degrees; // Helper constructor private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) - : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, rotationMatrix)) + : base(rotationMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, rotationMatrix)) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs similarity index 53% rename from src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index 8f1cf28ce..43f67f791 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -15,8 +15,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal class RotateProcessor : AffineTransformProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + private readonly float degrees; + /// /// Initializes a new instance of the class. /// @@ -26,11 +28,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source area to process for the current processor instance. public RotateProcessor(Configuration configuration, RotateProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, definition, source, sourceRectangle) + => this.degrees = definition.Degrees; + + /// + protected override void OnFrameApply(ImageFrame source, ImageFrame destination) { - this.Degrees = definition.Degrees; - } + if (this.OptimizedApply(source, destination, this.Configuration)) + { + return; + } - private float Degrees { get; } + base.OnFrameApply(source, destination); + } /// protected override void AfterImageApply(Image destination) @@ -41,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return; } - if (MathF.Abs(WrapDegrees(this.Degrees)) < Constants.Epsilon) + if (MathF.Abs(WrapDegrees(this.degrees)) < Constants.Epsilon) { // No need to do anything so return. return; @@ -52,17 +61,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms base.AfterImageApply(destination); } - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - if (this.OptimizedApply(source, destination, this.Configuration)) - { - return; - } - - base.OnFrameApply(source, destination); - } - /// /// Wraps a given angle in degrees so that it falls withing the 0-360 degree range /// @@ -95,12 +93,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Configuration configuration) { // Wrap the degrees to keep within 0-360 so we can apply optimizations when possible. - float degrees = WrapDegrees(this.Degrees); + float degrees = WrapDegrees(this.degrees); if (MathF.Abs(degrees) < Constants.Epsilon) { // The destination will be blank here so copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); return true; } @@ -133,25 +131,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - int width = source.Width; - int height = source.Height; - - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new Rotate180RowOperation(source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - Span targetRow = destination.GetPixelRowSpan(height - y - 1); - - for (int x = 0; x < width; x++) - { - targetRow[width - x - 1] = sourceRow[x]; - } - } - }); + source.Bounds(), + in operation); } /// @@ -162,31 +146,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRowIntervals( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span sourceRow = source.GetPixelRowSpan(y); - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - newX = height - newX - 1; - int newY = width - x - 1; - - if (destinationBounds.Contains(newX, newY)) - { - destination[newX, newY] = sourceRow[x]; - } - } - } - }); + source.Bounds(), + in operation); } /// @@ -197,28 +161,126 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - int width = source.Width; - int height = source.Height; - Rectangle destinationBounds = destination.Bounds(); - - ParallelHelper.IterateRows( - source.Bounds(), + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source, destination); + ParallelRowIterator.IterateRows( configuration, - rows => + source.Bounds(), + in operation); + } + + private readonly struct Rotate180RowOperation : IRowOperation + { + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate180RowOperation( + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + + for (int x = 0; x < this.width; x++) + { + targetRow[this.width - x - 1] = sourceRow[x]; + } + } + } + + private readonly struct Rotate270RowIntervalOperation : IRowIntervalOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate270RowIntervalOperation( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(in RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + for (int x = 0; x < this.width; x++) { - for (int y = rows.Min; y < rows.Max; y++) + int newX = this.height - y - 1; + newX = this.height - newX - 1; + int newY = this.width - x - 1; + + if (this.bounds.Contains(newX, newY)) { - Span sourceRow = source.GetPixelRowSpan(y); - int newX = height - y - 1; - for (int x = 0; x < width; x++) - { - if (destinationBounds.Contains(newX, x)) - { - destination[newX, x] = sourceRow[x]; - } - } + this.destination[newX, newY] = sourceRow[x]; } - }); + } + } + } + } + + private readonly struct Rotate90RowOperation : IRowOperation + { + private readonly Rectangle bounds; + private readonly int width; + private readonly int height; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public Rotate90RowOperation( + Rectangle bounds, + int width, + int height, + ImageFrame source, + ImageFrame destination) + { + this.bounds = bounds; + this.width = width; + this.height = height; + this.source = source; + this.destination = destination; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) + { + Span sourceRow = this.source.GetPixelRowSpan(y); + int newX = this.height - y - 1; + for (int x = 0; x < this.width; x++) + { + if (this.bounds.Contains(newX, x)) + { + this.destination[newX, x] = sourceRow[x]; + } + } + } } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs similarity index 91% rename from src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs rename to src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs index 4d0733334..1bcfa5fd2 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source image size public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), + TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, sourceSize), sampler, sourceSize) { @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms // Helper constructor: private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) - : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(sourceSize, skewMatrix)) + : base(skewMatrix, sampler, TransformUtilities.GetTransformedSize(sourceSize, skewMatrix)) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs deleted file mode 100644 index 56df606a7..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor{TPixel}.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Provides the base methods to perform non-affine transforms on an image. - /// - /// The pixel format. - internal class ProjectiveTransformProcessor : TransformProcessor - where TPixel : struct, IPixel - { - private Size targetSize; - private readonly IResampler resampler; - private Matrix4x4 transformMatrix; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration which allows altering default behaviour or extending the library. - /// The defining the processor parameters. - /// The source for the current processor instance. - /// The source area to process for the current processor instance. - public ProjectiveTransformProcessor(Configuration configuration, ProjectiveTransformProcessor definition, Image source, Rectangle sourceRectangle) - : base(configuration, source, sourceRectangle) - { - this.targetSize = definition.TargetDimensions; - this.transformMatrix = definition.TransformMatrix; - this.resampler = definition.Sampler; - } - - protected override Size GetTargetSize() => this.targetSize; - - /// - protected override void OnFrameApply(ImageFrame source, ImageFrame destination) - { - // Handle transforms that result in output identical to the original. - if (this.transformMatrix.Equals(default) || this.transformMatrix.Equals(Matrix4x4.Identity)) - { - // The clone will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); - return; - } - - int width = this.targetSize.Width; - Rectangle sourceBounds = this.SourceRectangle; - var targetBounds = new Rectangle(Point.Empty, this.targetSize); - Configuration configuration = this.Configuration; - - // Convert from screen to world space. - Matrix4x4.Invert(this.transformMatrix, out Matrix4x4 matrix); - - if (this.resampler is NearestNeighborResampler) - { - ParallelHelper.IterateRows( - targetBounds, - configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - Span destRow = destination.GetPixelRowSpan(y); - - for (int x = 0; x < width; x++) - { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - int px = (int)MathF.Round(point.X); - int py = (int)MathF.Round(point.Y); - - if (sourceBounds.Contains(px, py)) - { - destRow[x] = source[px, py]; - } - } - } - }); - - return; - } - - var kernel = new TransformKernelMap(configuration, source.Size(), destination.Size(), this.resampler); - - try - { - ParallelHelper.IterateRowsWithTempBuffer( - targetBounds, - configuration, - (rows, vectorBuffer) => - { - Span vectorSpan = vectorBuffer.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span targetRowSpan = destination.GetPixelRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, targetRowSpan, vectorSpan); - ref float ySpanRef = ref kernel.GetYStartReference(y); - ref float xSpanRef = ref kernel.GetXStartReference(y); - - for (int x = 0; x < width; x++) - { - // Use the single precision position to calculate correct bounding pixels - // otherwise we get rogue pixels outside of the bounds. - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); - kernel.Convolve( - point, - x, - ref ySpanRef, - ref xSpanRef, - source.PixelBuffer, - vectorSpan); - } - - PixelOperations.Instance.FromVector4Destructive( - configuration, - vectorSpan, - targetRowSpan); - } - }); - } - finally - { - kernel.Dispose(); - } - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs index 199563bc7..b0a79766f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BicubicResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Wikipedia /// A commonly used algorithm within image processing that preserves sharpness better than triangle interpolation. /// - public class BicubicResampler : IResampler + public readonly struct BicubicResampler : IResampler { /// public float Radius => 2; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -21,21 +25,25 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms x = -x; } - float result = 0; - // Given the coefficient "a" as -0.5F. if (x <= 1F) { // Below simplified result = ((a + 2F) * (x * x * x)) - ((a + 3F) * (x * x)) + 1; - result = (((1.5F * x) - 2.5F) * x * x) + 1; + return (((1.5F * x) - 2.5F) * x * x) + 1; } else if (x < 2F) { // Below simplified result = (a * (x * x * x)) - ((5F * a) * (x * x)) + ((8F * a) * x) - (4F * a); - result = (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; + return (((((-0.5F * x) + 2.5F) * x) - 4) * x) + 2; } - return result; + return 0; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs index 0667226d9..590d292e0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/BoxResampler.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the box algorithm. Similar to nearest neighbor when upscaling. /// When downscaling the pixels will average, merging together. /// - public class BoxResampler : IResampler + public readonly struct BoxResampler : IResampler { /// public float Radius => 0.5F; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x > -0.5F && x <= 0.5F) @@ -22,5 +26,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs deleted file mode 100644 index 8995d2d8a..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CatmullRomResampler.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. - /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large - /// scale image enlargements that a 'Lagrange' filter can produce. - /// - /// - public class CatmullRomResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0; - const float C = 0.5F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs new file mode 100644 index 000000000..8cdfcd882 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -0,0 +1,112 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// Cubic filters contain a collection of different filters of varying B-Spline and + /// Cardinal values. With these two values you can generate any smoothly fitting + /// (continuious first derivative) piece-wise cubic filter. + /// + /// + /// + public readonly struct CubicResampler : IResampler + { + private readonly float bspline; + private readonly float cardinal; + + /// + /// The Catmull-Rom filter is a well known standard Cubic Filter often used as a interpolation function. + /// This filter produces a reasonably sharp edge, but without a the pronounced gradient change on large + /// scale image enlargements that a 'Lagrange' filter can produce. + /// + public static CubicResampler CatmullRom = new CubicResampler(2, 0, .5F); + + /// + /// The Hermite filter is type of smoothed triangular interpolation Filter, + /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. + /// + public static CubicResampler Hermite = new CubicResampler(2, 0, 0); + + /// + /// The function implements the Mitchell-Netravali algorithm as described on + /// Wikipedia + /// + public static CubicResampler MitchellNetravali = new CubicResampler(2, .3333333F, .3333333F); + + /// + /// The function implements the Robidoux algorithm. + /// + /// + public static CubicResampler Robidoux = new CubicResampler(2, .37821575509399867F, .31089212245300067F); + + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler RobidouxSharp = new CubicResampler(2, .2620145123990142F, .3689927438004929F); + + /// + /// The function implements the spline algorithm. + /// + /// + /// + /// The function implements the Robidoux Sharp algorithm. + /// + /// + public static CubicResampler Spline = new CubicResampler(2, 1, 0); + + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + /// The B-Spline value. + /// The Cardinal cubic value. + public CubicResampler(float radius, float bspline, float cardinal) + { + this.Radius = radius; + this.bspline = bspline; + this.cardinal = cardinal; + } + + /// + public float Radius { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + float b = this.bspline; + float c = this.cardinal; + + if (x < 0F) + { + x = -x; + } + + float temp = x * x; + if (x < 1F) + { + x = ((12 - (9 * b) - (6 * c)) * (x * temp)) + ((-18 + (12 * b) + (6 * c)) * temp) + (6 - (2 * b)); + return x / 6F; + } + + if (x < 2F) + { + x = ((-b - (6 * c)) * (x * temp)) + (((6 * b) + (30 * c)) * temp) + (((-12 * b) - (48 * c)) * x) + ((8 * b) + (24 * c)); + return x / 6F; + } + + return 0F; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs deleted file mode 100644 index 18c3fda7c..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/HermiteResampler.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The Hermite filter is type of smoothed triangular interpolation Filter, - /// This filter rounds off strong edges while preserving flat 'color levels' in the original image. - /// - /// - public class HermiteResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs deleted file mode 100644 index 2294696de..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos2Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 2 pixels. - /// - public class Lanczos2Resampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 2F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 2F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs deleted file mode 100644 index 95fb206a9..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos3Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 3 pixels. - /// - public class Lanczos3Resampler : IResampler - { - /// - public float Radius => 3; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 3F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 3F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs deleted file mode 100644 index c99ed1e85..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos5Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 5 pixels. - /// - public class Lanczos5Resampler : IResampler - { - /// - public float Radius => 5; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 5F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 5F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs deleted file mode 100644 index 4efdb882b..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/Lanczos8Resampler.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Lanczos kernel algorithm as described on - /// Wikipedia - /// with a radius of 8 pixels. - /// - public class Lanczos8Resampler : IResampler - { - /// - public float Radius => 8; - - /// - public float GetValue(float x) - { - if (x < 0F) - { - x = -x; - } - - if (x < 8F) - { - return ImageMaths.SinC(x) * ImageMaths.SinC(x / 8F); - } - - return 0F; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs new file mode 100644 index 000000000..7eb6d111e --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/LanczosResampler.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms +{ + /// + /// The function implements the Lanczos kernel algorithm as described on + /// Wikipedia. + /// + public readonly struct LanczosResampler : IResampler + { + /// + /// Implements the Lanczos kernel algorithm with a radius of 2. + /// + public static LanczosResampler Lanczos2 = new LanczosResampler(2); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 3. + /// + public static LanczosResampler Lanczos3 = new LanczosResampler(3); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 5. + /// + public static LanczosResampler Lanczos5 = new LanczosResampler(5); + + /// + /// Implements the Lanczos kernel algorithm with a radius of 8. + /// + public static LanczosResampler Lanczos8 = new LanczosResampler(8); + + /// + /// Initializes a new instance of the struct. + /// + /// The sampling radius. + public LanczosResampler(float radius) => this.Radius = radius; + + /// + public float Radius { get; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) + { + if (x < 0F) + { + x = -x; + } + + float radius = this.Radius; + if (x < radius) + { + return ImageMaths.SinC(x) * ImageMaths.SinC(x / radius); + } + + return 0F; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs deleted file mode 100644 index d4ba954f2..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/MitchellNetravaliResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the mitchell algorithm as described on - /// Wikipedia - /// - public class MitchellNetravaliResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.3333333F; - const float C = 0.3333333F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs index 1f12334f4..9a78af82b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/NearestNeighborResampler.cs @@ -1,21 +1,28 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the nearest neighbor algorithm. This uses an unscaled filter /// which will select the closest pixel to the new pixels position. /// - public class NearestNeighborResampler : IResampler + public readonly struct NearestNeighborResampler : IResampler { /// public float Radius => 1; /// - public float GetValue(float x) - { - return x; - } + [MethodImpl(InliningOptions.ShortMethod)] + public float GetValue(float x) => x; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs deleted file mode 100644 index 51938566c..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Robidoux algorithm. - /// - /// - public class RobidouxResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.37821575509399867F; - const float C = 0.31089212245300067F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs deleted file mode 100644 index 015b7f0af..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/RobidouxSharpResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the Robidoux Sharp algorithm. - /// - /// - public class RobidouxSharpResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 0.2620145123990142F; - const float C = 0.3689927438004929F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs deleted file mode 100644 index df6c2a338..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/SplineResampler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// The function implements the spline algorithm. - /// - /// - public class SplineResampler : IResampler - { - /// - public float Radius => 2; - - /// - public float GetValue(float x) - { - const float B = 1F; - const float C = 0F; - - return ImageMaths.GetBcValue(x, B, C); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs index 57d1fa11d..345e56790 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/TriangleResampler.cs @@ -1,6 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// @@ -8,12 +11,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Bilinear interpolation can be used where perfect image transformation with pixel matching is impossible, /// so that one can calculate and assign appropriate intensity values to pixels. /// - public class TriangleResampler : IResampler + public readonly struct TriangleResampler : IResampler { /// public float Radius => 1; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -28,5 +32,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index edce5fcf9..82f58a7c9 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -1,18 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// /// The function implements the welch algorithm. /// /// - public class WelchResampler : IResampler + public readonly struct WelchResampler : IResampler { /// public float Radius => 3; /// + [MethodImpl(InliningOptions.ShortMethod)] public float GetValue(float x) { if (x < 0F) @@ -27,5 +31,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return 0F; } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ApplyTransform(IResamplingTransformImageProcessor processor) + where TPixel : unmanaged, IPixel + => processor.ApplyTransform(in this); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 14bf552b9..f3521ebed 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -28,12 +28,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Gets the start index for the destination row. /// - public int StartIndex { get; } + public int StartIndex + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// Gets the the length of the kernel. /// - public int Length { get; } + public int Length + { + [MethodImpl(InliningOptions.ShortMethod)] + get; + } /// /// Gets the span representing the portion of the that this window covers. @@ -81,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Copy the contents of altering /// to the value . /// + [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) { return new ResizeKernel(left, this.bufferPtr, this.Length); @@ -96,4 +105,4 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index be2546369..a79f60339 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -1,13 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { - /// - /// Contains - /// internal partial class ResizeKernelMap { /// @@ -21,7 +18,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public PeriodicKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, int sourceLength, int destinationLength, double ratio, @@ -31,7 +27,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int cornerInterval) : base( memoryAllocator, - sampler, sourceLength, destinationLength, (cornerInterval * 2) + period, @@ -45,15 +40,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; - protected override void Initialize() + protected internal override void Initialize(in TResampler sampler) { // Build top corner data + one period of the mosaic data: int startOfFirstRepeatedMosaic = this.cornerInterval + this.period; for (int i = 0; i < startOfFirstRepeatedMosaic; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } // Copy the mosaics: @@ -70,10 +64,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int bottomStartData = this.cornerInterval + this.period; for (int i = 0; i < this.cornerInterval; i++) { - ResizeKernel kernel = this.BuildKernel(bottomStartDest + i, bottomStartData + i); - this.kernels[bottomStartDest + i] = kernel; + this.kernels[bottomStartDest + i] = this.BuildKernel(in sampler, bottomStartDest + i, bottomStartData + i); } } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 1b653a92c..5390cbbd1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -1,25 +1,22 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Processing.Processors.Transforms { /// - /// Provides values from an optimized, - /// contiguous memory region. + /// Provides resize kernel values from an optimized contiguous memory region. /// internal partial class ResizeKernelMap : IDisposable { private static readonly TolerantMath TolerantMath = TolerantMath.Default; - private readonly IResampler sampler; - private readonly int sourceLength; private readonly double ratio; @@ -34,12 +31,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly ResizeKernel[] kernels; + private bool isDisposed; + // To avoid both GC allocations, and MemoryAllocator ceremony: private readonly double[] tempValues; private ResizeKernelMap( MemoryAllocator memoryAllocator, - IResampler sampler, int sourceLength, int destinationLength, int bufferHeight, @@ -47,7 +45,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms double scale, int radius) { - this.sampler = sampler; this.ratio = ratio; this.scale = scale; this.radius = radius; @@ -55,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); - this.pinHandle = this.data.GetMemory().Pin(); + this.pinHandle = this.data.GetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; } @@ -80,30 +77,47 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// Disposes instance releasing it's backing buffer. /// public void Dispose() + => this.Dispose(true); + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// Whether to dispose of managed and unmanaged objects. + protected virtual void Dispose(bool disposing) { - this.pinHandle.Dispose(); - this.data.Dispose(); + if (!this.isDisposed) + { + this.isDisposed = true; + + if (disposing) + { + this.pinHandle.Dispose(); + this.data.Dispose(); + } + } } /// /// Returns a for an index value between 0 and DestinationSize - 1. /// [MethodImpl(InliningOptions.ShortMethod)] - public ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; + internal ref ResizeKernel GetKernel(int destIdx) => ref this.kernels[destIdx]; /// /// Computes the weights to apply at each pixel when resizing. /// + /// The type of sampler. /// The /// The destination size /// The source size /// The to use for buffer allocations /// The - public static ResizeKernelMap Calculate( - IResampler sampler, + public static ResizeKernelMap Calculate( + in TResampler sampler, int destinationSize, int sourceSize, MemoryAllocator memoryAllocator) + where TResampler : struct, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -144,7 +158,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ResizeKernelMap result = hasAtLeast2Periods ? new PeriodicKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, ratio, @@ -154,7 +167,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms cornerInterval) : new ResizeKernelMap( memoryAllocator, - sampler, sourceSize, destinationSize, destinationSize, @@ -162,17 +174,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms scale, radius); - result.Initialize(); + result.Initialize(in sampler); return result; } - protected virtual void Initialize() + /// + /// Initializes the kernel map. + /// + protected internal virtual void Initialize(in TResampler sampler) + where TResampler : struct, IResampler { for (int i = 0; i < this.DestinationLength; i++) { - ResizeKernel kernel = this.BuildKernel(i, i); - this.kernels[i] = kernel; + this.kernels[i] = this.BuildKernel(in sampler, i, i); } } @@ -181,7 +196,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// referencing the data at row within , /// so the data reusable by other data rows. /// - private ResizeKernel BuildKernel(int destRowIndex, int dataRowIndex) + private ResizeKernel BuildKernel(in TResampler sampler, int destRowIndex, int dataRowIndex) + where TResampler : struct, IResampler { double center = ((destRowIndex + .5) * this.ratio) - .5; @@ -205,7 +221,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int j = left; j <= right; j++) { - double value = this.sampler.GetValue((float)((j - center) / this.scale)); + double value = sampler.GetValue((float)((j - center) / this.scale)); sum += value; kernelValues[j - left] = value; @@ -234,12 +250,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private unsafe ResizeKernel CreateKernel(int dataRowIndex, int left, int right) { int length = right - left + 1; - - if (length > this.data.Width) - { - throw new InvalidOperationException( - $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); - } + this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); Span rowSpan = this.data.GetRowSpan(dataRowIndex); @@ -247,5 +258,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); return new ResizeKernel(left, rowPtr, length); } + + [Conditional("DEBUG")] + private void ValidateSizesForCreateKernel(int length, int dataRowIndex, int left, int right) + { + if (length > this.data.Width) + { + throw new InvalidOperationException( + $"Error in KernelMap.CreateKernel({dataRowIndex},{left},{right}): left > this.data.Width"); + } + } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index ec1f94c14..4e6e7a48c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -17,13 +17,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Guard.NotNull(options, nameof(options)); Guard.NotNull(options.Sampler, nameof(options.Sampler)); + Guard.MustBeValueType(options.Sampler, nameof(options.Sampler)); (Size size, Rectangle rectangle) = ResizeHelper.CalculateTargetLocationAndBounds(sourceSize, options); this.Sampler = options.Sampler; - this.TargetWidth = size.Width; - this.TargetHeight = size.Height; - this.TargetRectangle = rectangle; + this.DestinationWidth = size.Width; + this.DestinationHeight = size.Height; + this.DestinationRectangle = rectangle; this.Compand = options.Compand; } @@ -33,19 +34,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public IResampler Sampler { get; } /// - /// Gets the target width. + /// Gets the destination width. /// - public int TargetWidth { get; } + public int DestinationWidth { get; } /// - /// Gets the target height. + /// Gets the destination height. /// - public int TargetHeight { get; } + public int DestinationHeight { get; } /// /// Gets the resize rectangle. /// - public Rectangle TargetRectangle { get; } + public Rectangle DestinationRectangle { get; } /// /// Gets a value indicating whether to compress or expand individual pixel color values on processing. diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 2e94f88ac..6eafbda89 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -2,9 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -13,59 +12,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Implements resizing of images using various resamplers. /// - /// - /// The original code has been adapted from . - /// /// The pixel format. - internal class ResizeProcessor : TransformProcessor - where TPixel : struct, IPixel + internal class ResizeProcessor : TransformProcessor, IResamplingTransformImageProcessor + where TPixel : unmanaged, IPixel { - private bool isDisposed; - private readonly int targetWidth; - private readonly int targetHeight; + private readonly int destinationWidth; + private readonly int destinationHeight; private readonly IResampler resampler; - private Rectangle targetRectangle; + private readonly Rectangle destinationRectangle; private readonly bool compand; - - // The following fields are not immutable but are optionally created on demand. - private ResizeKernelMap horizontalKernelMap; - private ResizeKernelMap verticalKernelMap; + private Image destination; public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - this.targetWidth = definition.TargetWidth; - this.targetHeight = definition.TargetHeight; - this.targetRectangle = definition.TargetRectangle; + this.destinationWidth = definition.DestinationWidth; + this.destinationHeight = definition.DestinationHeight; + this.destinationRectangle = definition.DestinationRectangle; this.resampler = definition.Sampler; this.compand = definition.Compand; } /// - protected override Size GetTargetSize() => new Size(this.targetWidth, this.targetHeight); + protected override Size GetDestinationSize() => new Size(this.destinationWidth, this.destinationHeight); /// protected override void BeforeImageApply(Image destination) { - if (!(this.resampler is NearestNeighborResampler)) - { - Image source = this.Source; - Rectangle sourceRectangle = this.SourceRectangle; - - // Since all image frame dimensions have to be the same we can calculate this for all frames. - MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); - this.horizontalKernelMap = ResizeKernelMap.Calculate( - this.resampler, - this.targetRectangle.Width, - sourceRectangle.Width, - memoryAllocator); - - this.verticalKernelMap = ResizeKernelMap.Calculate( - this.resampler, - this.targetRectangle.Height, - sourceRectangle.Height, - memoryAllocator); - } + this.destination = destination; + this.resampler.ApplyTransform(this); base.BeforeImageApply(destination); } @@ -73,98 +48,197 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// protected override void OnFrameApply(ImageFrame source, ImageFrame destination) { - Rectangle sourceRectangle = this.SourceRectangle; + // Everything happens in BeforeImageApply. + } + + public void ApplyTransform(in TResampler sampler) + where TResampler : struct, IResampler + { Configuration configuration = this.Configuration; + Image source = this.Source; + Image destination = this.destination; + Rectangle sourceRectangle = this.SourceRectangle; + Rectangle destinationRectangle = this.destinationRectangle; + bool compand = this.compand; // Handle resize dimensions identical to the original - if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.targetRectangle) + if (source.Width == destination.Width + && source.Height == destination.Height + && sourceRectangle == destinationRectangle) { - // The cloned will be blank here copy all the pixel data over - source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + // The cloned will be blank here copy all the pixel data over + sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); + } + return; } - int width = this.targetWidth; - int height = this.targetHeight; - int sourceX = sourceRectangle.X; - int sourceY = sourceRectangle.Y; - int startY = this.targetRectangle.Y; - int startX = this.targetRectangle.X; + var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); + + if (sampler is NearestNeighborResampler) + { + for (int i = 0; i < source.Frames.Count; i++) + { + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; + + ApplyNNResizeFrameTransform( + configuration, + sourceFrame, + destinationFrame, + sourceRectangle, + destinationRectangle, + interest); + } - var targetWorkingRect = Rectangle.Intersect( - this.targetRectangle, - new Rectangle(0, 0, width, height)); + return; + } - if (this.resampler is NearestNeighborResampler) + // Since all image frame dimensions have to be the same we can calculate + // the kernel maps and reuse for all frames. + MemoryAllocator allocator = configuration.MemoryAllocator; + using var horizontalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Width, + sourceRectangle.Width, + allocator); + + using var verticalKernelMap = ResizeKernelMap.Calculate( + in sampler, + destinationRectangle.Height, + sourceRectangle.Height, + allocator); + + for (int i = 0; i < source.Frames.Count; i++) { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.targetRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.targetRectangle.Height; + ImageFrame sourceFrame = source.Frames[i]; + ImageFrame destinationFrame = destination.Frames[i]; - ParallelHelper.IterateRows( - targetWorkingRect, + ApplyResizeFrameTransform( configuration, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - // Y coordinates of source points - Span sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); - Span targetRow = destination.GetPixelRowSpan(y); - - for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) - { - // X coordinates of source points - targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; - } - } - }); - - return; + sourceFrame, + destinationFrame, + horizontalKernelMap, + verticalKernelMap, + sourceRectangle, + destinationRectangle, + interest, + compand); } + } + private static void ApplyNNResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest) + { + // Scaling factors + float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; + float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; + + var operation = new NNRowOperation( + sourceRectangle, + destinationRectangle, + widthFactor, + heightFactor, + source, + destination); + + ParallelRowIterator.IterateRows( + configuration, + interest, + in operation); + } + + private static void ApplyResizeFrameTransform( + Configuration configuration, + ImageFrame source, + ImageFrame destination, + ResizeKernelMap horizontalKernelMap, + ResizeKernelMap verticalKernelMap, + Rectangle sourceRectangle, + Rectangle destinationRectangle, + Rectangle interest, + bool compand) + { PixelConversionModifiers conversionModifiers = - PixelConversionModifiers.Premultiply.ApplyCompanding(this.compand); + PixelConversionModifiers.Premultiply.ApplyCompanding(compand); BufferArea sourceArea = source.PixelBuffer.GetArea(sourceRectangle); - // To reintroduce parallel processing, we to launch multiple workers + // To reintroduce parallel processing, we would launch multiple workers // for different row intervals of the image. using (var worker = new ResizeWorker( configuration, sourceArea, conversionModifiers, - this.horizontalKernelMap, - this.verticalKernelMap, - width, - targetWorkingRect, - this.targetRectangle.Location)) + horizontalKernelMap, + verticalKernelMap, + destination.Width, + interest, + destinationRectangle.Location)) { worker.Initialize(); - var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); + var workingInterval = new RowInterval(interest.Top, interest.Bottom); worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); } } - /// - protected override void Dispose(bool disposing) + private readonly struct NNRowOperation : IRowOperation { - if (this.isDisposed) + private readonly Rectangle sourceBounds; + private readonly Rectangle destinationBounds; + private readonly float widthFactor; + private readonly float heightFactor; + private readonly ImageFrame source; + private readonly ImageFrame destination; + + [MethodImpl(InliningOptions.ShortMethod)] + public NNRowOperation( + Rectangle sourceBounds, + Rectangle destinationBounds, + float widthFactor, + float heightFactor, + ImageFrame source, + ImageFrame destination) { - return; + this.sourceBounds = sourceBounds; + this.destinationBounds = destinationBounds; + this.widthFactor = widthFactor; + this.heightFactor = heightFactor; + this.source = source; + this.destination = destination; } - if (disposing) + [MethodImpl(InliningOptions.ShortMethod)] + public void Invoke(int y) { - this.horizontalKernelMap?.Dispose(); - this.horizontalKernelMap = null; - this.verticalKernelMap?.Dispose(); - this.verticalKernelMap = null; + int sourceX = this.sourceBounds.X; + int sourceY = this.sourceBounds.Y; + int destX = this.destinationBounds.X; + int destY = this.destinationBounds.Y; + int destLeft = this.destinationBounds.Left; + int destRight = this.destinationBounds.Right; + + // Y coordinates of source points + Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.GetPixelRowSpan(y); + + for (int x = destLeft; x < destRight; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; + } } - - this.isDisposed = true; - base.Dispose(disposing); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 4f5faa38e..898809d5a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; - using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -19,8 +18,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// When sliding the window, the contents of the bottom window band are copied to the new top band. /// For more details, and visual explanation, see "ResizeWorker.pptx". /// - internal class ResizeWorker : IDisposable - where TPixel : struct, IPixel + internal sealed class ResizeWorker : IDisposable + where TPixel : unmanaged, IPixel { private readonly Buffer2D transposedFirstPassBuffer; @@ -74,10 +73,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.windowBandHeight = verticalKernelMap.MaxDiameter; + // We need to make sure the working buffer is contiguous: + int workingBufferLimitHintInBytes = Math.Min( + configuration.WorkingBufferSizeHintInBytes, + configuration.MemoryAllocator.GetBufferCapacityInBytes()); + int numberOfWindowBands = ResizeHelper.CalculateResizeWorkerHeightInWindowBands( this.windowBandHeight, destWidth, - configuration.WorkingBufferSizeHintInBytes); + workingBufferLimitHintInBytes); this.workerHeight = Math.Min(this.sourceRectangle.Height, numberOfWindowBands * this.windowBandHeight); @@ -99,7 +103,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.tempColumnBuffer.Dispose(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) { return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); @@ -113,7 +117,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) { Span tempColSpan = this.tempColumnBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan(); + + // When creating transposedFirstPassBuffer, we made sure it's contiguous: + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); for (int y = rowInterval.Min; y < rowInterval.Max; y++) { @@ -165,7 +171,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private void CalculateFirstPassValues(RowInterval calculationInterval) { Span tempRowSpan = this.tempRowBuffer.GetSpan(); - Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSpan(); + Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.GetSingleSpan(); for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs deleted file mode 100644 index a0d44cb7a..000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformKernelMap.cs +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Processing.Processors.Transforms -{ - /// - /// Contains the methods required to calculate transform kernel convolution. - /// - internal class TransformKernelMap : IDisposable - { - private readonly Buffer2D yBuffer; - private readonly Buffer2D xBuffer; - private readonly Vector2 extents; - private Vector4 maxSourceExtents; - private readonly IResampler sampler; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration. - /// The source size. - /// The destination size. - /// The sampler. - public TransformKernelMap(Configuration configuration, Size source, Size destination, IResampler sampler) - { - this.sampler = sampler; - float yRadius = this.GetSamplingRadius(source.Height, destination.Height); - float xRadius = this.GetSamplingRadius(source.Width, destination.Width); - - this.extents = new Vector2(xRadius, yRadius); - int xLength = (int)MathF.Ceiling((this.extents.X * 2) + 2); - int yLength = (int)MathF.Ceiling((this.extents.Y * 2) + 2); - - // We use 2D buffers so that we can access the weight spans per row in parallel. - this.yBuffer = configuration.MemoryAllocator.Allocate2D(yLength, destination.Height); - this.xBuffer = configuration.MemoryAllocator.Allocate2D(xLength, destination.Height); - - int maxX = source.Width - 1; - int maxY = source.Height - 1; - this.maxSourceExtents = new Vector4(maxX, maxY, maxX, maxY); - } - - /// - /// Gets a reference to the first item of the y window. - /// - /// The reference to the first item of the window. - [MethodImpl(InliningOptions.ShortMethod)] - public ref float GetYStartReference(int y) - => ref MemoryMarshal.GetReference(this.yBuffer.GetRowSpan(y)); - - /// - /// Gets a reference to the first item of the x window. - /// - /// The reference to the first item of the window. - [MethodImpl(InliningOptions.ShortMethod)] - public ref float GetXStartReference(int y) - => ref MemoryMarshal.GetReference(this.xBuffer.GetRowSpan(y)); - - public void Convolve( - Vector2 transformedPoint, - int column, - ref float ySpanRef, - ref float xSpanRef, - Buffer2D sourcePixels, - Span targetRow) - where TPixel : struct, IPixel - { - // Clamp sampling pixel radial extents to the source image edges - Vector2 minXY = transformedPoint - this.extents; - Vector2 maxXY = transformedPoint + this.extents; - - // left, top, right, bottom - var extents = new Vector4( - MathF.Ceiling(minXY.X - .5F), - MathF.Ceiling(minXY.Y - .5F), - MathF.Floor(maxXY.X + .5F), - MathF.Floor(maxXY.Y + .5F)); - - extents = Vector4.Clamp(extents, Vector4.Zero, this.maxSourceExtents); - - int left = (int)extents.X; - int top = (int)extents.Y; - int right = (int)extents.Z; - int bottom = (int)extents.W; - - if (left == right || top == bottom) - { - return; - } - - this.CalculateWeights(top, bottom, transformedPoint.Y, ref ySpanRef); - this.CalculateWeights(left, right, transformedPoint.X, ref xSpanRef); - - Vector4 sum = Vector4.Zero; - for (int kernelY = 0, y = top; y <= bottom; y++, kernelY++) - { - float yWeight = Unsafe.Add(ref ySpanRef, kernelY); - - for (int kernelX = 0, x = left; x <= right; x++, kernelX++) - { - float xWeight = Unsafe.Add(ref xSpanRef, kernelX); - - // Values are first premultiplied to prevent darkening of edge pixels. - var current = sourcePixels[x, y].ToVector4(); - Vector4Utils.Premultiply(ref current); - sum += current * xWeight * yWeight; - } - } - - // Reverse the premultiplication - Vector4Utils.UnPremultiply(ref sum); - targetRow[column] = sum; - } - - /// - /// Calculated the normalized weights for the given point. - /// - /// The minimum sampling offset - /// The maximum sampling offset - /// The transformed point dimension - /// The reference to the collection of weights - [MethodImpl(InliningOptions.ShortMethod)] - private void CalculateWeights(int min, int max, float point, ref float weightsRef) - { - float sum = 0; - for (int x = 0, i = min; i <= max; i++, x++) - { - float weight = this.sampler.GetValue(i - point); - sum += weight; - Unsafe.Add(ref weightsRef, x) = weight; - } - } - - [MethodImpl(InliningOptions.ShortMethod)] - private float GetSamplingRadius(int sourceSize, int destinationSize) - { - float scale = (float)sourceSize / destinationSize; - - if (scale < 1F) - { - scale = 1F; - } - - return MathF.Ceiling(scale * this.sampler.Radius); - } - - public void Dispose() - { - this.yBuffer?.Dispose(); - this.xBuffer?.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs index 3a0a7e54e..5423eea88 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The pixel format. internal abstract class TransformProcessor : CloningImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs index 14eeca7cc..c1dce02be 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessorHelpers.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The pixel format. /// The image to update public static void UpdateDimensionalMetadata(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ExifProfile profile = image.Metadata.ExifProfile; if (profile is null) diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs similarity index 96% rename from src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs rename to src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs index e0fb55438..0760d2e3e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// Contains utility methods for working with transforms. /// - internal static class TransformUtils + internal static class TransformUtilities { /// /// Applies the projective transform against the given coordinates flattened into the 2D space. @@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The amount of rotation, in degrees. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateRotationMatrixDegrees(float degrees, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -44,6 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The amount of rotation, in radians. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateRotationMatrixRadians(float radians, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -56,6 +58,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The Y angle, in degrees. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateSkewMatrixDegrees(float degreesX, float degreesY, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -68,6 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The Y angle, in radians. /// The source image size. /// The . + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateSkewMatrixRadians(float radiansX, float radiansY, Size size) => CreateCenteredTransformMatrix( new Rectangle(Point.Empty, size), @@ -79,6 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source image bounds. /// The transformation matrix. /// The + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) { Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); @@ -105,6 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// An enumeration that indicates on which corners to taper the rectangle. /// The amount to taper. /// The + [MethodImpl(InliningOptions.ShortMethod)] public static Matrix4x4 CreateTaperMatrix(Size size, TaperSide side, TaperCorner corner, float fraction) { Matrix4x4 matrix = Matrix4x4.Identity; @@ -225,6 +231,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// + [MethodImpl(InliningOptions.ShortMethod)] public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) { Rectangle transformed = GetTransformedRectangle(rectangle, matrix); @@ -284,6 +291,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// + [MethodImpl(InliningOptions.ShortMethod)] public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) { if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) @@ -307,6 +315,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The . /// + [MethodImpl(InliningOptions.ShortMethod)] public static Size GetTransformedSize(Size size, Matrix4x4 matrix) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); @@ -321,6 +330,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return ConstrainSize(rectangle); } + [MethodImpl(InliningOptions.ShortMethod)] private static Size ConstrainSize(Rectangle rectangle) { // We want to resize the canvas here taking into account any translations. @@ -342,6 +352,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms return new Size(width, height); } + [MethodImpl(InliningOptions.ShortMethod)] private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) { // Find the minimum and maximum "corners" based on the given vectors diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 0ff693d81..ef44dc16d 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Prepend(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Appends a matrix that performs a tapering projective transform. @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount to taper. /// The . public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Append(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Prepends a centered rotation matrix using the given rotation in degrees. @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationMatrixRadians(radians, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationMatrixRadians(radians, size))); /// /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -205,7 +205,7 @@ namespace SixLabors.ImageSharp.Processing /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewMatrixRadians(radiansX, radiansY, size))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateSkewMatrixRadians(radiansX, radiansY, size))); /// /// Appends a skew matrix using the given angles in degrees at the given origin. diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index bacaaa709..3c3b3b5ec 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -26,6 +26,7 @@ + diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs index 527b6bb8b..3c8f45edb 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs @@ -18,7 +18,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public class DecodeTga : BenchmarkBase { private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private readonly PfimConfig pfimConfig = new PfimConfig(allocator: new PfimAllocator()); + private byte[] data; [Params(TestImages.Tga.Bit24)] @@ -77,15 +79,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public int Rented => this.rented; } - // RESULTS (07/01/2020) - //| Method | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | - //|------------------ |-------------- |-------------------- |-------------:|-------------:|-----------:|------:|-------:|------:|------:|----------:| - //| 'ImageMagick Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 1,778.965 us | 1,711.088 us | 93.7905 us | 1.000 | 1.9531 | - | - | 13668 B | - //| 'ImageSharp Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 38.659 us | 6.886 us | 0.3774 us | 0.022 | 0.3052 | - | - | 1316 B | - //| 'Pfim Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 6.752 us | 10.268 us | 0.5628 us | 0.004 | 0.0687 | - | - | 313 B | - //| | | | | | | | | | | | - //| 'ImageMagick Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 1,407.585 us | 124.215 us | 6.8087 us | 1.000 | 1.9531 | - | - | 13307 B | - //| 'ImageSharp Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 17.958 us | 9.352 us | 0.5126 us | 0.013 | 0.2747 | - | - | 1256 B | - //| 'Pfim Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 5.645 us | 2.279 us | 0.1249 us | 0.004 | 0.0610 | - | - | 280 B | + /* RESULTS (07/01/2020) + | Method | Runtime | TestImage | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | + |------------------ |-------------- |-------------------- |-------------:|-------------:|-----------:|------:|-------:|------:|------:|----------:| + | 'ImageMagick Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 1,778.965 us | 1,711.088 us | 93.7905 us | 1.000 | 1.9531 | - | - | 13668 B | + | 'ImageSharp Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 38.659 us | 6.886 us | 0.3774 us | 0.022 | 0.3052 | - | - | 1316 B | + | 'Pfim Tga' | .NET 4.7.2 | Tga/targa_24bit.tga | 6.752 us | 10.268 us | 0.5628 us | 0.004 | 0.0687 | - | - | 313 B | + | | | | | | | | | | | | + | 'ImageMagick Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 1,407.585 us | 124.215 us | 6.8087 us | 1.000 | 1.9531 | - | - | 13307 B | + | 'ImageSharp Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 17.958 us | 9.352 us | 0.5126 us | 0.013 | 0.2747 | - | - | 1256 B | + | 'Pfim Tga' | .NET Core 2.1 | Tga/targa_24bit.tga | 5.645 us | 2.279 us | 0.1249 us | 0.004 | 0.0610 | - | - | 280 B | + */ } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs index 379f8aa8b..58e3e01e3 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -16,13 +16,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] public void EncodeBmpImageSharp() { - this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new BmpEncoder()); return null; }); + this.ForEachImageSharpImage((img, ms) => + { + img.Save(ms, new BmpEncoder()); + return null; + }); } [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] public void EncodeBmpSystemDrawing() { - this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Bmp); return null; }); + this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Bmp); + return null; + }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index 89eb63d62..70c85ef02 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; @@ -6,6 +6,7 @@ using System.IO; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; @@ -20,12 +21,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private SDImage bmpDrawing; private Image bmpCore; + // Try to get as close to System.Drawing's output as possible + private readonly GifEncoder encoder = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + + [Params(TestImages.Bmp.Car, TestImages.Png.Rgb48Bpp)] + public string TestImage { get; set; } + [GlobalSetup] public void ReadImages() { if (this.bmpStream == null) { - this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); + this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage)); this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = SDImage.FromStream(this.bmpStream); @@ -52,12 +62,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "ImageSharp Gif")] public void GifCore() { - // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; using (var memoryStream = new MemoryStream()) { - this.bmpCore.SaveAsGif(memoryStream, options); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index bf9627f4c..5c7a9e991 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -1,10 +1,11 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; using System.Drawing.Imaging; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Benchmarks.Codecs @@ -23,15 +24,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs this.ForEachImageSharpImage((img, ms) => { // Try to get as close to System.Drawing's output as possible - var options = new GifEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; - img.Save(ms, options); return null; + var options = new GifEncoder + { + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) + }; + + img.Save(ms, options); + return null; }); } [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] public void EncodeGifSystemDrawing() { - this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Gif); return null; }); + this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Gif); + return null; + }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index 639d1594e..aedf9cd77 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new OctreeQuantizer(false) }; + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } @@ -95,9 +95,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var memoryStream = new MemoryStream()) { - var options = new PngEncoder { Quantizer = new WuQuantizer(false) }; + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs index ddcbec218..7100ca6b7 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { if (this.tgaCore == null) { - this.tgaCore = Image.Load(TestImageFullPath); + this.tgaCore = Image.Load(this.TestImageFullPath); this.tgaMagick = new MagickImage(this.TestImageFullPath); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs index f0d7a54d0..93f5bc8d8 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing; @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var image = new Image(400, 400)) { - image[200, 200] = Rgba32.White; + image[200, 200] = Color.White; return image[200, 200]; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs index 7c3da90db..4a9d709ee 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/ImageBenchmarkTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // This file contains small, cheap and "unit test" benchmarks to test MultiImageBenchmarkBase. @@ -6,7 +6,6 @@ // Uncomment this to enable benchmark testing // #define TEST - #if TEST // ReSharper disable InconsistentNaming @@ -76,4 +75,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } -#endif \ No newline at end of file +#endif diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs index bf9b1af33..55d66d488 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo1x1.cs @@ -1,44 +1,69 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - public class Block8x8F_CopyTo1x1 + public unsafe class Block8x8F_CopyTo1x1 { private Block8x8F block; + private readonly Block8x8F[] blockArray = new Block8x8F[1]; - private Buffer2D buffer; + private static readonly int Width = 100; - private BufferArea destArea; + private float[] buffer = new float[Width * 500]; + private readonly float[] unpinnedBuffer = new float[Width * 500]; + private GCHandle bufferHandle; + private GCHandle blockHandle; + private float* bufferPtr; + private float* blockPtr; [GlobalSetup] public void Setup() { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { throw new InvalidOperationException("Benchmark Block8x8F_CopyTo1x1 is invalid on platforms without AVX2 support."); } - this.buffer = Configuration.Default.MemoryAllocator.Allocate2D(1000, 500); - this.destArea = this.buffer.GetArea(200, 100, 64, 64); + this.bufferHandle = GCHandle.Alloc(this.buffer, GCHandleType.Pinned); + this.bufferPtr = (float*)this.bufferHandle.AddrOfPinnedObject(); + + // Pin self so we can take address of to the block: + this.blockHandle = GCHandle.Alloc(this.blockArray, GCHandleType.Pinned); + this.blockPtr = (float*)Unsafe.AsPointer(ref this.block); + } + + [GlobalCleanup] + public void Cleanup() + { + this.bufferPtr = null; + this.blockPtr = null; + this.bufferHandle.Free(); + this.blockHandle.Free(); + this.buffer = null; } [Benchmark(Baseline = true)] public void Original() { ref byte selfBase = ref Unsafe.As(ref this.block); - ref byte destBase = ref Unsafe.As(ref this.destArea.GetReferenceToOrigin()); - int destStride = this.destArea.Stride * sizeof(float); + ref byte destBase = ref Unsafe.AsRef(this.bufferPtr); + int destStride = Width * sizeof(float); CopyRowImpl(ref selfBase, ref destBase, destStride, 0); CopyRowImpl(ref selfBase, ref destBase, destStride, 1); @@ -58,12 +83,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float)); } - [Benchmark] + // [Benchmark] public void UseVector8() { ref Block8x8F s = ref this.block; - ref float origin = ref this.destArea.GetReferenceToOrigin(); - int stride = this.destArea.Stride; + ref float origin = ref Unsafe.AsRef(this.bufferPtr); + int stride = Width; ref Vector d0 = ref Unsafe.As>(ref origin); ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); @@ -93,12 +118,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations d7 = row7; } - [Benchmark] + // [Benchmark] public void UseVector8_V2() { ref Block8x8F s = ref this.block; - ref float origin = ref this.destArea.GetReferenceToOrigin(); - int stride = this.destArea.Stride; + ref float origin = ref Unsafe.AsRef(this.bufferPtr); + int stride = Width; ref Vector d0 = ref Unsafe.As>(ref origin); ref Vector d1 = ref Unsafe.As>(ref Unsafe.Add(ref origin, stride)); @@ -119,15 +144,247 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations d7 = Unsafe.As>(ref s.V7L); } - // RESULTS: + [Benchmark] + public void UseVector8_V3() + { + int stride = Width * sizeof(float); + ref float d = ref this.unpinnedBuffer[0]; + ref Vector s = ref Unsafe.As>(ref this.block); + + Vector v0 = s; + Vector v1 = Unsafe.AddByteOffset(ref s, (IntPtr)1); + Vector v2 = Unsafe.AddByteOffset(ref s, (IntPtr)2); + Vector v3 = Unsafe.AddByteOffset(ref s, (IntPtr)3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)stride)) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 2))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 3))) = v3; + + v0 = Unsafe.AddByteOffset(ref s, (IntPtr)4); + v1 = Unsafe.AddByteOffset(ref s, (IntPtr)5); + v2 = Unsafe.AddByteOffset(ref s, (IntPtr)6); + v3 = Unsafe.AddByteOffset(ref s, (IntPtr)7); + + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 4))) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 5))) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 6))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 7))) = v3; + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void UseVector256_Avx2_Variant1() + { + int stride = Width; + float* d = this.bufferPtr; + float* s = this.blockPtr; + Vector256 v; + + v = Avx.LoadVector256(s); + Avx.Store(d, v); + + v = Avx.LoadVector256(s + 8); + Avx.Store(d + stride, v); + + v = Avx.LoadVector256(s + (8 * 2)); + Avx.Store(d + (stride * 2), v); + + v = Avx.LoadVector256(s + (8 * 3)); + Avx.Store(d + (stride * 3), v); + + v = Avx.LoadVector256(s + (8 * 4)); + Avx.Store(d + (stride * 4), v); + + v = Avx.LoadVector256(s + (8 * 5)); + Avx.Store(d + (stride * 5), v); + + v = Avx.LoadVector256(s + (8 * 6)); + Avx.Store(d + (stride * 6), v); + + v = Avx.LoadVector256(s + (8 * 7)); + Avx.Store(d + (stride * 7), v); + } + + [Benchmark] + public void UseVector256_Avx2_Variant2() + { + int stride = Width; + float* d = this.bufferPtr; + float* s = this.blockPtr; + + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 8); + Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); + Vector256 v4 = Avx.LoadVector256(s + (8 * 4)); + Vector256 v5 = Avx.LoadVector256(s + (8 * 5)); + Vector256 v6 = Avx.LoadVector256(s + (8 * 6)); + Vector256 v7 = Avx.LoadVector256(s + (8 * 7)); + + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + Avx.Store(d + (stride * 4), v4); + Avx.Store(d + (stride * 5), v5); + Avx.Store(d + (stride * 6), v6); + Avx.Store(d + (stride * 7), v7); + } + + [Benchmark] + public void UseVector256_Avx2_Variant3() + { + int stride = Width; + float* d = this.bufferPtr; + float* s = this.blockPtr; + + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 8); + Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + v0 = Avx.LoadVector256(s + (8 * 4)); + v1 = Avx.LoadVector256(s + (8 * 5)); + v2 = Avx.LoadVector256(s + (8 * 6)); + v3 = Avx.LoadVector256(s + (8 * 7)); + Avx.Store(d + (stride * 4), v0); + Avx.Store(d + (stride * 5), v1); + Avx.Store(d + (stride * 6), v2); + Avx.Store(d + (stride * 7), v3); + } + + [Benchmark] + public void UseVector256_Avx2_Variant3_RefCast() + { + int stride = Width; + ref float d = ref this.unpinnedBuffer[0]; + ref Vector256 s = ref Unsafe.As>(ref this.block); + + Vector256 v0 = s; + Vector256 v1 = Unsafe.Add(ref s, 1); + Vector256 v2 = Unsafe.Add(ref s, 2); + Vector256 v3 = Unsafe.Add(ref s, 3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.Add(ref d, stride)) = v1; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 2)) = v2; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 3)) = v3; + + v0 = Unsafe.Add(ref s, 4); + v1 = Unsafe.Add(ref s, 5); + v2 = Unsafe.Add(ref s, 6); + v3 = Unsafe.Add(ref s, 7); + + Unsafe.As>(ref Unsafe.Add(ref d, stride * 4)) = v0; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 5)) = v1; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 6)) = v2; + Unsafe.As>(ref Unsafe.Add(ref d, stride * 7)) = v3; + } + + [Benchmark] + public void UseVector256_Avx2_Variant3_RefCast_Mod() + { + int stride = Width * sizeof(float); + ref float d = ref this.unpinnedBuffer[0]; + ref Vector256 s = ref Unsafe.As>(ref this.block); + + Vector256 v0 = s; + Vector256 v1 = Unsafe.AddByteOffset(ref s, (IntPtr)1); + Vector256 v2 = Unsafe.AddByteOffset(ref s, (IntPtr)2); + Vector256 v3 = Unsafe.AddByteOffset(ref s, (IntPtr)3); + + Unsafe.As>(ref d) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)stride)) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 2))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 3))) = v3; + + v0 = Unsafe.AddByteOffset(ref s, (IntPtr)4); + v1 = Unsafe.AddByteOffset(ref s, (IntPtr)5); + v2 = Unsafe.AddByteOffset(ref s, (IntPtr)6); + v3 = Unsafe.AddByteOffset(ref s, (IntPtr)7); + + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 4))) = v0; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 5))) = v1; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 6))) = v2; + Unsafe.As>(ref Unsafe.AddByteOffset(ref d, (IntPtr)(stride * 7))) = v3; + } + + // [Benchmark] + public void UseVector256_Avx2_Variant3_WithLocalPinning() + { + int stride = Width; + fixed (float* d = this.unpinnedBuffer) + fixed (Block8x8F* ss = &this.block) + { + var s = (float*)ss; + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 8); + Vector256 v2 = Avx.LoadVector256(s + (8 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (8 * 3)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + v0 = Avx.LoadVector256(s + (8 * 4)); + v1 = Avx.LoadVector256(s + (8 * 5)); + v2 = Avx.LoadVector256(s + (8 * 6)); + v3 = Avx.LoadVector256(s + (8 * 7)); + Avx.Store(d + (stride * 4), v0); + Avx.Store(d + (stride * 5), v1); + Avx.Store(d + (stride * 6), v2); + Avx.Store(d + (stride * 7), v3); + } + } + + // [Benchmark] + public void UseVector256_Avx2_Variant3_sbyte() + { + int stride = Width * 4; + var d = (sbyte*)this.bufferPtr; + var s = (sbyte*)this.blockPtr; + + Vector256 v0 = Avx.LoadVector256(s); + Vector256 v1 = Avx.LoadVector256(s + 32); + Vector256 v2 = Avx.LoadVector256(s + (32 * 2)); + Vector256 v3 = Avx.LoadVector256(s + (32 * 3)); + Avx.Store(d, v0); + Avx.Store(d + stride, v1); + Avx.Store(d + (stride * 2), v2); + Avx.Store(d + (stride * 3), v3); + + v0 = Avx.LoadVector256(s + (32 * 4)); + v1 = Avx.LoadVector256(s + (32 * 5)); + v2 = Avx.LoadVector256(s + (32 * 6)); + v3 = Avx.LoadVector256(s + (32 * 7)); + Avx.Store(d + (stride * 4), v0); + Avx.Store(d + (stride * 5), v1); + Avx.Store(d + (stride * 6), v2); + Avx.Store(d + (stride * 7), v3); + } +#endif + + // *** RESULTS 02/2020 *** + // BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 + // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores + // .NET Core SDK=3.1.200-preview-014971 + // [Host] : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + // DefaultJob : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT // - // Method | Mean | Error | StdDev | Scaled | - // -------------- |---------:|----------:|----------:|-------:| - // Original | 22.53 ns | 0.1660 ns | 0.1553 ns | 1.00 | - // UseVector8 | 21.59 ns | 0.3079 ns | 0.2571 ns | 0.96 | - // UseVector8_V2 | 22.57 ns | 0.1699 ns | 0.1506 ns | 1.00 | // - // Conclusion: - // Doesn't worth to bother with this + // | Method | Mean | Error | StdDev | Ratio | RatioSD | + // |--------------------------------------- |---------:|----------:|----------:|------:|--------:| + // | Original | 4.012 ns | 0.0567 ns | 0.0531 ns | 1.00 | 0.00 | + // | UseVector8_V3 | 4.013 ns | 0.0947 ns | 0.0840 ns | 1.00 | 0.03 | + // | UseVector256_Avx2_Variant1 | 2.546 ns | 0.0376 ns | 0.0314 ns | 0.63 | 0.01 | + // | UseVector256_Avx2_Variant2 | 2.643 ns | 0.0162 ns | 0.0151 ns | 0.66 | 0.01 | + // | UseVector256_Avx2_Variant3 | 2.520 ns | 0.0760 ns | 0.0813 ns | 0.63 | 0.02 | + // | UseVector256_Avx2_Variant3_RefCast | 2.300 ns | 0.0877 ns | 0.0938 ns | 0.58 | 0.03 | + // | UseVector256_Avx2_Variant3_RefCast_Mod | 2.139 ns | 0.0698 ns | 0.0686 ns | 0.53 | 0.02 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs index 3d9b54dff..76068ab43 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_CopyTo2x2.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -8,8 +8,8 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { public class Block8x8F_CopyTo2x2 @@ -335,7 +335,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations Unsafe.Add(ref dBottomLeft, 7) = wRight; } - [Benchmark] public void UseVector4_V2() { @@ -409,4 +408,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations // UseVector4_SafeRightCorner | 58.97 ns | 0.4152 ns | 0.3884 ns | 0.64 | 0.02 | // UseVector4_V2 | 41.88 ns | 0.3531 ns | 0.3303 ns | 0.45 | 0.01 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs index 15a3c7eb7..05b7156ff 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_DivideRound.cs @@ -9,7 +9,6 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { /// @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { for (int i = 0; i < Block8x8F.Size; i++) { - this.inputDividend[i] = i*44.8f; + this.inputDividend[i] = i * 44.8f; this.inputDivisor[i] = 100 - i; } } @@ -54,10 +53,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations sum = 0; for (int i = 0; i < Block8x8F.Size; i++) { - int a = (int) pDividend[i]; - int b = (int) pDivisor; + int a = (int)pDividend[i]; + int b = (int)pDivisor; result[i] = RationalRound(a, b); } + for (int i = 0; i < Block8x8F.Size; i++) { sum += result[i]; @@ -83,13 +83,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations for (int i = 0; i < Block8x8F.Size; i++) { double value = pDividend[i] / pDivisor[i]; - pDividend[i] = (float) System.Math.Round(value); + pDividend[i] = (float)System.Math.Round(value); } + for (int i = 0; i < Block8x8F.Size; i++) { - sum += (int) pDividend[i]; + sum += (int)pDividend[i]; } } + return sum; } @@ -111,6 +113,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations sum += (int)pDividend[i]; } } + return sum; } @@ -138,10 +141,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { - Vector4 sign = Vector4.Min(dividend, Vector4.One); + var sign = Vector4.Min(dividend, Vector4.One); sign = Vector4.Max(sign, MinusOne); - return dividend / divisor + sign * Half; + return (dividend / divisor) + (sign * Half); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs index 29ee402a0..5dac39116 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Numerics; @@ -10,6 +8,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { public class Block8x8F_LoadFromInt16 @@ -50,4 +49,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations // Scalar | 34.88 ns | 0.3296 ns | 0.3083 ns | 1.00 | // ExtendedAvx2 | 21.58 ns | 0.2125 ns | 0.1884 ns | 0.62 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs index 09e25827c..32d838f8c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs @@ -1,22 +1,31 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations { - public class Block8x8F_Round + public unsafe class Block8x8F_Round { private Block8x8F block; + private readonly byte[] blockBuffer = new byte[512]; + private GCHandle blockHandle; + private float* alignedPtr; + [GlobalSetup] public void Setup() { @@ -25,13 +34,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations throw new NotSupportedException("Vector.Count != 8"); } - for (int i = 0; i < Block8x8F.Size; i++) + this.blockHandle = GCHandle.Alloc(this.blockBuffer, GCHandleType.Pinned); + ulong ptr = (ulong)this.blockHandle.AddrOfPinnedObject(); + ptr += 16; + ptr -= ptr % 16; + + if (ptr % 16 != 0) { - this.block[i] = i * 44.8f; + throw new Exception("ptr is unaligned"); } + + this.alignedPtr = (float*)ptr; } - [Benchmark(Baseline = true)] + [GlobalCleanup] + public void Cleanup() + { + this.blockHandle.Free(); + this.alignedPtr = null; + } + + [Benchmark] public void ScalarRound() { ref float b = ref Unsafe.As(ref this.block); @@ -43,8 +66,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations } } - [Benchmark] - public void SimdRound() + [Benchmark(Baseline = true)] + public void SimdUtils_FastRound_Vector8() { ref Block8x8F b = ref this.block; @@ -65,5 +88,411 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations ref Vector row7 = ref Unsafe.As>(ref b.V7L); row7 = SimdUtils.FastRound(row7); } + + [Benchmark] + public void SimdUtils_FastRound_Vector8_ForceAligned() + { + ref Block8x8F b = ref Unsafe.AsRef(this.alignedPtr); + + ref Vector row0 = ref Unsafe.As>(ref b.V0L); + row0 = SimdUtils.FastRound(row0); + ref Vector row1 = ref Unsafe.As>(ref b.V1L); + row1 = SimdUtils.FastRound(row1); + ref Vector row2 = ref Unsafe.As>(ref b.V2L); + row2 = SimdUtils.FastRound(row2); + ref Vector row3 = ref Unsafe.As>(ref b.V3L); + row3 = SimdUtils.FastRound(row3); + ref Vector row4 = ref Unsafe.As>(ref b.V4L); + row4 = SimdUtils.FastRound(row4); + ref Vector row5 = ref Unsafe.As>(ref b.V5L); + row5 = SimdUtils.FastRound(row5); + ref Vector row6 = ref Unsafe.As>(ref b.V6L); + row6 = SimdUtils.FastRound(row6); + ref Vector row7 = ref Unsafe.As>(ref b.V7L); + row7 = SimdUtils.FastRound(row7); + } + + [Benchmark] + public void SimdUtils_FastRound_Vector8_Grouped() + { + ref Block8x8F b = ref this.block; + + ref Vector row0 = ref Unsafe.As>(ref b.V0L); + ref Vector row1 = ref Unsafe.As>(ref b.V1L); + ref Vector row2 = ref Unsafe.As>(ref b.V2L); + ref Vector row3 = ref Unsafe.As>(ref b.V3L); + + row0 = SimdUtils.FastRound(row0); + row1 = SimdUtils.FastRound(row1); + row2 = SimdUtils.FastRound(row2); + row3 = SimdUtils.FastRound(row3); + + row0 = ref Unsafe.As>(ref b.V4L); + row1 = ref Unsafe.As>(ref b.V5L); + row2 = ref Unsafe.As>(ref b.V6L); + row3 = ref Unsafe.As>(ref b.V7L); + + row0 = SimdUtils.FastRound(row0); + row1 = SimdUtils.FastRound(row1); + row2 = SimdUtils.FastRound(row2); + row3 = SimdUtils.FastRound(row3); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void Sse41_V1() + { + ref Vector128 b0 = ref Unsafe.As>(ref this.block); + + ref Vector128 p = ref b0; + p = Sse41.RoundToNearestInteger(p); + + p = ref Unsafe.Add(ref b0, 1); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 2); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 3); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 4); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 5); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 6); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 7); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 8); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 9); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 10); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 11); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 12); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 13); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 14); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.Add(ref b0, 15); + p = Sse41.RoundToNearestInteger(p); + } + + [Benchmark] + public unsafe void Sse41_V2() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + p = Sse41.RoundToNearestInteger(p); + var offset = (IntPtr)sizeof(Vector128); + p = Sse41.RoundToNearestInteger(p); + + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + } + + [Benchmark] + public unsafe void Sse41_V3() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + p = Sse41.RoundToNearestInteger(p); + var offset = (IntPtr)sizeof(Vector128); + + for (int i = 0; i < 15; i++) + { + p = ref Unsafe.AddByteOffset(ref p, offset); + p = Sse41.RoundToNearestInteger(p); + } + } + + [Benchmark] + public unsafe void Sse41_V4() + { + ref Vector128 p = ref Unsafe.As>(ref this.block); + var offset = (IntPtr)sizeof(Vector128); + + ref Vector128 a = ref p; + ref Vector128 b = ref Unsafe.AddByteOffset(ref a, offset); + ref Vector128 c = ref Unsafe.AddByteOffset(ref b, offset); + ref Vector128 d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + + a = ref Unsafe.AddByteOffset(ref d, offset); + b = ref Unsafe.AddByteOffset(ref a, offset); + c = ref Unsafe.AddByteOffset(ref b, offset); + d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + + a = ref Unsafe.AddByteOffset(ref d, offset); + b = ref Unsafe.AddByteOffset(ref a, offset); + c = ref Unsafe.AddByteOffset(ref b, offset); + d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + + a = ref Unsafe.AddByteOffset(ref d, offset); + b = ref Unsafe.AddByteOffset(ref a, offset); + c = ref Unsafe.AddByteOffset(ref b, offset); + d = ref Unsafe.AddByteOffset(ref c, offset); + a = Sse41.RoundToNearestInteger(a); + b = Sse41.RoundToNearestInteger(b); + c = Sse41.RoundToNearestInteger(c); + d = Sse41.RoundToNearestInteger(d); + } + + [Benchmark] + public unsafe void Sse41_V5_Unaligned() + { + float* p = this.alignedPtr + 1; + + Vector128 v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + p += 8; + + v = Sse.LoadVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.Store(p, v); + } + + [Benchmark] + public unsafe void Sse41_V5_Aligned() + { + float* p = this.alignedPtr; + + Vector128 v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + + v = Sse.LoadAlignedVector128(p); + v = Sse41.RoundToNearestInteger(v); + Sse.StoreAligned(p, v); + p += 8; + } + + [Benchmark] + public void Sse41_V6_Aligned() + { + float* p = this.alignedPtr; + + Round8SseVectors(p); + Round8SseVectors(p + 32); + } + + private static void Round8SseVectors(float* p0) + { + float* p1 = p0 + 4; + float* p2 = p1 + 4; + float* p3 = p2 + 4; + float* p4 = p3 + 4; + float* p5 = p4 + 4; + float* p6 = p5 + 4; + float* p7 = p6 + 4; + + Vector128 v0 = Sse.LoadAlignedVector128(p0); + Vector128 v1 = Sse.LoadAlignedVector128(p1); + Vector128 v2 = Sse.LoadAlignedVector128(p2); + Vector128 v3 = Sse.LoadAlignedVector128(p3); + Vector128 v4 = Sse.LoadAlignedVector128(p4); + Vector128 v5 = Sse.LoadAlignedVector128(p5); + Vector128 v6 = Sse.LoadAlignedVector128(p6); + Vector128 v7 = Sse.LoadAlignedVector128(p7); + + v0 = Sse41.RoundToNearestInteger(v0); + v1 = Sse41.RoundToNearestInteger(v1); + v2 = Sse41.RoundToNearestInteger(v2); + v3 = Sse41.RoundToNearestInteger(v3); + v4 = Sse41.RoundToNearestInteger(v4); + v5 = Sse41.RoundToNearestInteger(v5); + v6 = Sse41.RoundToNearestInteger(v6); + v7 = Sse41.RoundToNearestInteger(v7); + + Sse.StoreAligned(p0, v0); + Sse.StoreAligned(p1, v1); + Sse.StoreAligned(p2, v2); + Sse.StoreAligned(p3, v3); + Sse.StoreAligned(p4, v4); + Sse.StoreAligned(p5, v5); + Sse.StoreAligned(p6, v6); + Sse.StoreAligned(p7, v7); + } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 5b783dddc..51da29172 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -1,8 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System.Drawing; using System.IO; using BenchmarkDotNet.Attributes; + using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; using SDSize = System.Drawing.Size; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index f8a7556ca..06492bc92 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// @@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg this.ForEachStream(SDImage.FromStream); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 6a2040afc..1696623ef 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -13,8 +13,8 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; using SDImage = System.Drawing.Image; using SDSize = System.Drawing.Size; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// @@ -34,10 +34,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { public ShortClr() { - this.Add( - // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), - Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3) - ); + // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), + this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); } } } @@ -46,6 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + #pragma warning disable SA1115 [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, @@ -53,14 +52,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // The scaled result for the large image "ExifGetString750Transform_Huge420YCbCr" // is almost the same as the result for Jpeg420Exif, // which proves that the execution time for the most common YCbCr 420 path scales linearly. - // // TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr - )] public string TestImage { get; set; } - [GlobalSetup] public void ReadImages() { @@ -101,7 +97,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // Frequency=2742191 Hz, Resolution=364.6719 ns, Timer=TSC // .NET Core SDK=2.1.403 // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // + // // Method | TestImage | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // ------------------------------- |-------------------------------------------- |-----------:|-----------:|----------:|-------:|---------:|----------:|---------:|---------:|------------:| // 'Decode Jpeg - System.Drawing' | Jpg/baseline/Lake.jpg | 6.117 ms | 0.3923 ms | 0.0222 ms | 1.00 | 0.00 | 62.5000 | - | - | 205.83 KB | @@ -118,21 +114,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // RESULTS (2019 April 23): // - //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) - //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores - //.NET Core SDK=2.2.202 + // BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + // Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + // .NET Core SDK=2.2.202 // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // - //| Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - //|------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:| - //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB | - //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB | - //| | | | | | | | | | | | - //| 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | - //| 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB | - //| | | | | | | | | | | | - //| 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB | - //| 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB | + // | Method | TestImage | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |------------------------------- |--------------------- |-----------:|-----------:|-----------:|------:|--------:|----------:|------:|------:|------------:| + // | 'Decode Jpeg - System.Drawing' | Jpg/b(...)e.jpg [21] | 6.957 ms | 9.618 ms | 0.5272 ms | 1.00 | 0.00 | 93.7500 | - | - | 205.83 KB | + // | 'Decode Jpeg - ImageSharp' | Jpg/b(...)e.jpg [21] | 18.348 ms | 8.876 ms | 0.4865 ms | 2.65 | 0.23 | - | - | - | 14.49 KB | + // | | | | | | | | | | | | + // | 'Decode Jpeg - System.Drawing' | Jpg/b(...)f.jpg [28] | 18.687 ms | 11.632 ms | 0.6376 ms | 1.00 | 0.00 | 343.7500 | - | - | 757.04 KB | + // | 'Decode Jpeg - ImageSharp' | Jpg/b(...)f.jpg [28] | 41.990 ms | 25.514 ms | 1.3985 ms | 2.25 | 0.10 | - | - | - | 15.48 KB | + // | | | | | | | | | | | | + // | 'Decode Jpeg - System.Drawing' | Jpg/i(...)e.jpg [43] | 477.265 ms | 732.126 ms | 40.1303 ms | 1.00 | 0.00 | 3000.0000 | - | - | 7403.76 KB | + // | 'Decode Jpeg - ImageSharp' | Jpg/i(...)e.jpg [43] | 348.545 ms | 91.480 ms | 5.0143 ms | 0.73 | 0.06 | - | - | - | 35177.21 KB | } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs index 6f2c492d0..6f3ea0e14 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DoubleBufferedStreams.cs @@ -125,28 +125,29 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } - // RESULTS (2019 April 24): - // - //BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) - //Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores - //.NET Core SDK=2.2.202 - // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 - // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - //IterationCount=3 LaunchCount=1 WarmupCount=3 - // - //| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | - //|----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| - //| StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - | - //| StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - | - //| DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - | - //| DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - | - //| SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - | - //| | | | | | | | | | | | | - //| StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - | - //| StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - | - //| DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - | - //| DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - | - //| SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - | + /* RESULTS (2019 April 24): + + BenchmarkDotNet=v0.11.5, OS=Windows 10.0.17763.437 (1809/October2018Update/Redstone5) + Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores + .NET Core SDK=2.2.202 + [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 + Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT + + IterationCount=3 LaunchCount=1 WarmupCount=3 + + | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + |----------------------------- |----- |-------- |---------:|-----------:|----------:|------:|--------:|------:|------:|------:|----------:| + | StandardStreamReadByte | Clr | Clr | 96.71 us | 5.9950 us | 0.3286 us | 1.00 | 0.00 | - | - | - | - | + | StandardStreamRead | Clr | Clr | 77.73 us | 5.2284 us | 0.2866 us | 0.80 | 0.00 | - | - | - | - | + | DoubleBufferedStreamReadByte | Clr | Clr | 23.17 us | 26.2354 us | 1.4381 us | 0.24 | 0.01 | - | - | - | - | + | DoubleBufferedStreamRead | Clr | Clr | 33.35 us | 3.4071 us | 0.1868 us | 0.34 | 0.00 | - | - | - | - | + | SimpleReadByte | Clr | Clr | 10.85 us | 0.4927 us | 0.0270 us | 0.11 | 0.00 | - | - | - | - | + | | | | | | | | | | | | | + | StandardStreamReadByte | Core | Core | 75.35 us | 12.9789 us | 0.7114 us | 1.00 | 0.00 | - | - | - | - | + | StandardStreamRead | Core | Core | 55.36 us | 1.4432 us | 0.0791 us | 0.73 | 0.01 | - | - | - | - | + | DoubleBufferedStreamReadByte | Core | Core | 21.47 us | 29.7076 us | 1.6284 us | 0.28 | 0.02 | - | - | - | - | + | DoubleBufferedStreamRead | Core | Core | 29.67 us | 2.5988 us | 0.1424 us | 0.39 | 0.00 | - | - | - | - | + | SimpleReadByte | Core | Core | 10.84 us | 0.7567 us | 0.0415 us | 0.14 | 0.00 | - | - | - | - | + */ } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index c617d25c0..b64c86974 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -1,16 +1,15 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { using System.Drawing; using System.Drawing.Imaging; using System.IO; - using CoreImage = SixLabors.ImageSharp.Image; public class EncodeJpeg : BenchmarkBase @@ -58,4 +57,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs index afa2ad325..a710fc196 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -18,13 +18,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] public void EncodeJpegImageSharp() { - this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new JpegEncoder()); return null; }); + this.ForEachImageSharpImage((img, ms) => + { + img.Save(ms, new JpegEncoder()); + return null; + }); } [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] public void EncodeJpegSystemDrawing() { - this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Jpeg); return null; }); + this.ForEachSystemDrawingImage((img, ms) => + { + img.Save(ms, ImageFormat.Jpeg); + return null; + }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs index e39cfa6ba..4a3c88a28 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,8 +13,8 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(MultiImageBenchmarkBase.Config))] @@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg }); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs index 1834f77ea..0d0e3212b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs @@ -1,18 +1,20 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using BenchmarkDotNet.Attributes; using System; -using System.IO; -using SixLabors.ImageSharp.Tests; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; +using System.IO; +using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; + using SDImage = System.Drawing.Image; -using SixLabors.ImageSharp.Formats.Jpeg; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { [Config(typeof(Config.ShortClr))] @@ -29,9 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Params( TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr - )] + TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] public string TestImage { get; set; } [Params(false, true)] @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT // Job-ZPEZGV : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 // Job-SGOCJT : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - // + // // Method | Runtime | TestImage | ParallelExec | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | // -------------- |-------- |----------------------------- |------------- |----------:|----------:|----------:|-------:|---------:|---------:|----------:| // SystemDrawing | Clr | Jpg/baseline/jpeg420exif.jpg | False | 64.88 ms | 3.735 ms | 0.2110 ms | 1.00 | 0.00 | 250.0000 | 791.07 KB | @@ -104,4 +104,4 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg // SystemDrawing | Core | Jpg/baseline/jpeg420exif.jpg | True | 64.20 ms | 6.560 ms | 0.3707 ms | 1.00 | 0.00 | 250.0000 | 789.79 KB | // ImageSharp | Core | Jpg/baseline/jpeg420exif.jpg | True | 68.08 ms | 18.376 ms | 1.0383 ms | 1.06 | 0.01 | - | 50.49 KB | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs index 313a7c97d..1daf9b4d5 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } } - [Benchmark(Baseline = true)] + [Benchmark] public void Scalar() { var values = new JpegColorConverter.ComponentValues(this.input, 0); @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg JpegColorConverter.FromYCbCrBasic.ConvertCore(values, this.output, 255F, 128F); } - [Benchmark] + [Benchmark(Baseline = true)] public void SimdVector4() { var values = new JpegColorConverter.ComponentValues(this.input, 0); @@ -53,11 +53,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg } [Benchmark] - public void SimdAvx2() + public void SimdVector8() { var values = new JpegColorConverter.ComponentValues(this.input, 0); - JpegColorConverter.FromYCbCrSimdAvx2.ConvertCore(values, this.output, 255F, 128F); + JpegColorConverter.FromYCbCrSimdVector8.ConvertCore(values, this.output, 255F, 128F); } private static Buffer2D[] CreateRandomValues( @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } // no need to dispose when buffer is not array owner diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index 6d4caa843..eafbc0fde 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using BenchmarkDotNet.Environments; using SixLabors.ImageSharp.Tests; - using CoreImage = ImageSharp.Image; + using CoreImage = SixLabors.ImageSharp.Image; public abstract class MultiImageBenchmarkBase { @@ -36,27 +36,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { public ShortClr() { - this.Add( - Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2) - ); + this.Add(Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2)); } } } - protected Dictionary FileNamesToBytes = new Dictionary(); - - protected Dictionary> FileNamesToImageSharpImages = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages = new Dictionary(); + protected Dictionary fileNamesToBytes = new Dictionary(); + protected Dictionary> fileNamesToImageSharpImages = new Dictionary>(); + protected Dictionary fileNamesToSystemDrawingImages = new Dictionary(); /// - /// The values of this enum separate input files into categories + /// The values of this enum separate input files into categories. /// public enum InputImageCategory { + /// + /// Use all images. + /// AllImages, + /// + /// Use small images only. + /// SmallImagesOnly, + /// + /// Use large images only. + /// LargeImagesOnly } @@ -73,12 +79,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected virtual IEnumerable ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; /// - /// Enumerates folders containing files OR files to be processed by the benchmark. + /// Gets folders containing files OR files to be processed by the benchmark. /// protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// - /// The images sized above this threshold will be included in + /// Gets the large image threshold. + /// The images sized above this threshold will be included in. /// protected virtual int LargeImageThresholdInBytes => 100000; @@ -102,7 +109,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected IEnumerable> FileNames2Bytes => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToBytes, + this.fileNamesToBytes, arr => arr.Length < this.LargeImageThresholdInBytes); protected abstract IEnumerable InputImageSubfoldersOrFiles { get; } @@ -114,6 +121,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); } + // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); this.ReadFilesImpl(); } @@ -124,7 +132,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { if (File.Exists(path)) { - this.FileNamesToBytes[path] = File.ReadAllBytes(path); + this.fileNamesToBytes[path] = File.ReadAllBytes(path); continue; } @@ -138,7 +146,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs foreach (string fn in allFiles) { - this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); + this.fileNamesToBytes[fn] = File.ReadAllBytes(fn); } } } @@ -157,7 +165,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { object obj = operation(memoryStream); (obj as IDisposable)?.Dispose(); - } catch (Exception ex) { @@ -173,31 +180,30 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { base.ReadFilesImpl(); - foreach (KeyValuePair kv in this.FileNamesToBytes) + foreach (KeyValuePair kv in this.fileNamesToBytes) { byte[] bytes = kv.Value; string fn = kv.Key; using (var ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); - + this.fileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); } - this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); + this.fileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); } } protected IEnumerable>> FileNames2ImageSharpImages => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToImageSharpImages, + this.fileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( - this.FileNamesToSystemDrawingImages, + this.fileNamesToSystemDrawingImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); protected virtual int LargeImageThresholdInPixels => 700000; @@ -210,13 +216,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); - } catch (Exception ex) { Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } - } } @@ -224,13 +228,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var workStream = new MemoryStream()) { - this.ForEachImageSharpImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure return result; }); @@ -257,18 +261,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { using (var workStream = new MemoryStream()) { - this.ForEachSystemDrawingImage( img => { // ReSharper disable AccessToDisposedClosure object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin); + // ReSharper restore AccessToDisposedClosure return result; }); } - } } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs index b96422176..dc1d21c14 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromRgba32Bytes.cs @@ -1,20 +1,18 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers; using System; - +using System.Buffers; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class FromRgba32Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner destination; @@ -23,7 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk private Configuration configuration; [Params( - 128, + 128, 1024, 2048)] public int Count { get; set; } @@ -43,12 +41,12 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.source.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); Span d = this.destination.GetSpan(); - + for (int i = 0; i < this.Count; i++) { int i4 = i * 4; @@ -89,4 +87,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // CommonBulk | 2048 | 2,625.4 ns | 30.143 ns | 26.721 ns | 1.00 | // OptimizedBulk | 2048 | 1,843.0 ns | 20.505 ns | 18.177 ns | 0.70 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs index 8b2d08e66..1184bef2e 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4.cs @@ -1,24 +1,29 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { [Config(typeof(Config.ShortClr))] public abstract class FromVector4 - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { protected IMemoryOwner source; @@ -26,10 +31,8 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk protected Configuration Configuration => Configuration.Default; - [Params( - 64, - 2048 - )] + // [Params(64, 2048)] + [Params(1024)] public int Count { get; set; } [GlobalSetup] @@ -46,12 +49,11 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.source.Dispose(); } - //[Benchmark] + // [Benchmark] public void PerElement() { ref Vector4 s = ref MemoryMarshal.GetReference(this.source.GetSpan()); ref TPixel d = ref MemoryMarshal.GetReference(this.destination.GetSpan()); - for (int i = 0; i < this.Count; i++) { Unsafe.Add(ref d, i).FromVector4(Unsafe.Add(ref s, i)); @@ -79,52 +81,105 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(sBytes, dFloats); } - [Benchmark(Baseline = true)] + [Benchmark] public void BasicIntrinsics256() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.BasicIntrinsics256.NormalizedFloatToByteSaturate(sBytes, dFloats); } - [Benchmark] + [Benchmark(Baseline = true)] public void ExtendedIntrinsic() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(sBytes, dFloats); + SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); } - // RESULTS (2018 October): - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |-------------:|-------------:|------------:|-------:|---------:|-------:|----------:| - // FallbackIntrinsics128 | Clr | 64 | 340.38 ns | 22.319 ns | 1.2611 ns | 1.41 | 0.01 | - | 0 B | - // BasicIntrinsics256 | Clr | 64 | 240.79 ns | 11.421 ns | 0.6453 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Clr | 64 | 199.09 ns | 124.239 ns | 7.0198 ns | 0.83 | 0.02 | - | 0 B | - // PixelOperations_Base | Clr | 64 | 647.99 ns | 24.003 ns | 1.3562 ns | 2.69 | 0.01 | 0.0067 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 259.79 ns | 13.391 ns | 0.7566 ns | 1.08 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 64 | 234.64 ns | 12.320 ns | 0.6961 ns | 1.58 | 0.00 | - | 0 B | - // BasicIntrinsics256 | Core | 64 | 148.87 ns | 2.794 ns | 0.1579 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Core | 64 | 94.06 ns | 10.015 ns | 0.5659 ns | 0.63 | 0.00 | - | 0 B | - // PixelOperations_Base | Core | 64 | 573.52 ns | 31.865 ns | 1.8004 ns | 3.85 | 0.01 | 0.0067 | 24 B | - // PixelOperations_Specialized | Core | 64 | 117.21 ns | 13.264 ns | 0.7494 ns | 0.79 | 0.00 | - | 0 B | - // | | | | | | | | | | - // FallbackIntrinsics128 | Clr | 2048 | 6,735.93 ns | 2,139.340 ns | 120.8767 ns | 1.71 | 0.03 | - | 0 B | - // BasicIntrinsics256 | Clr | 2048 | 3,929.29 ns | 334.027 ns | 18.8731 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Clr | 2048 | 2,226.01 ns | 130.525 ns | 7.3749 ns |!! 0.57 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Clr | 2048 | 16,760.84 ns | 367.800 ns | 20.7814 ns | 4.27 | 0.02 | - | 24 B | <--- Extra copies using "Vector4 TPixel.ToVector4()" - // PixelOperations_Specialized | Clr | 2048 | 3,986.03 ns | 237.238 ns | 13.4044 ns | 1.01 | 0.00 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 2048 | 6,644.65 ns | 2,677.090 ns | 151.2605 ns | 1.69 | 0.05 | - | 0 B | - // BasicIntrinsics256 | Core | 2048 | 3,923.70 ns | 1,971.760 ns | 111.4081 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsic | Core | 2048 | 2,092.32 ns | 375.657 ns | 21.2253 ns |!! 0.53 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Core | 2048 | 16,875.73 ns | 1,271.957 ns | 71.8679 ns | 4.30 | 0.10 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 2,129.92 ns | 262.888 ns | 14.8537 ns |!! 0.54 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! +#if SUPPORTS_RUNTIME_INTRINSICS + [Benchmark] + public void UseAvx2() + { + Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); + Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); + + SimdUtils.Avx2Intrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); + } + + private static ReadOnlySpan PermuteMaskDeinterleave8x32 => new byte[] { 0, 0, 0, 0, 4, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0 }; + + [Benchmark] + public void UseAvx2_Grouped() + { + Span src = MemoryMarshal.Cast(this.source.GetSpan()); + Span dest = MemoryMarshal.Cast(this.destination.GetSpan()); + + int n = dest.Length / Vector.Count; + + ref Vector256 sourceBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(src)); + ref Vector256 destBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(dest)); + + ref byte maskBase = ref MemoryMarshal.GetReference(PermuteMaskDeinterleave8x32); + Vector256 mask = Unsafe.As>(ref maskBase); + + var maxBytes = Vector256.Create(255f); + + for (int i = 0; i < n; i++) + { + ref Vector256 s = ref Unsafe.Add(ref sourceBase, i * 4); + + Vector256 f0 = s; + Vector256 f1 = Unsafe.Add(ref s, 1); + Vector256 f2 = Unsafe.Add(ref s, 2); + Vector256 f3 = Unsafe.Add(ref s, 3); + + f0 = Avx.Multiply(maxBytes, f0); + f1 = Avx.Multiply(maxBytes, f1); + f2 = Avx.Multiply(maxBytes, f2); + f3 = Avx.Multiply(maxBytes, f3); + + Vector256 w0 = Avx.ConvertToVector256Int32(f0); + Vector256 w1 = Avx.ConvertToVector256Int32(f1); + Vector256 w2 = Avx.ConvertToVector256Int32(f2); + Vector256 w3 = Avx.ConvertToVector256Int32(f3); + + Vector256 u0 = Avx2.PackSignedSaturate(w0, w1); + Vector256 u1 = Avx2.PackSignedSaturate(w2, w3); + Vector256 b = Avx2.PackUnsignedSaturate(u0, u1); + b = Avx2.PermuteVar8x32(b.AsInt32(), mask).AsByte(); + + Unsafe.Add(ref destBase, i) = b; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 vf, Vector256 scale) + { + vf = Avx.Multiply(scale, vf); + return Avx.ConvertToVector256Int32(vf); + } +#endif + + // *** RESULTS 2020 March: *** + // Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores + // .NET Core SDK=3.1.200-preview-014971 + // Job-IUZXZT : .NET Core 3.1.2 (CoreCLR 4.700.20.6602, CoreFX 4.700.20.6702), X64 RyuJIT + // + // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | + // |---------------------------- |------ |-----------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| + // | FallbackIntrinsics128 | 1024 | 2,952.6 ns | 1,680.77 ns | 92.13 ns | 3.32 | 0.16 | - | - | - | - | + // | BasicIntrinsics256 | 1024 | 1,664.5 ns | 928.11 ns | 50.87 ns | 1.87 | 0.09 | - | - | - | - | + // | ExtendedIntrinsic | 1024 | 890.6 ns | 375.48 ns | 20.58 ns | 1.00 | 0.00 | - | - | - | - | + // | UseAvx2 | 1024 | 299.0 ns | 30.47 ns | 1.67 ns | 0.34 | 0.01 | - | - | - | - | + // | UseAvx2_Grouped | 1024 | 318.1 ns | 48.19 ns | 2.64 ns | 0.36 | 0.01 | - | - | - | - | + // | PixelOperations_Base | 1024 | 8,136.9 ns | 1,834.82 ns | 100.57 ns | 9.14 | 0.26 | - | - | - | 24 B | + // | PixelOperations_Specialized | 1024 | 951.1 ns | 123.93 ns | 6.79 ns | 1.07 | 0.03 | - | - | - | - | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs index 294baa9d5..dfcc51646 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/Rgb24Bytes.cs @@ -1,17 +1,16 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System.Buffers; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class Rgb24Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner source; @@ -57,4 +56,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk public class Rgb24Bytes_Rgba32 : Rgb24Bytes { } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs index 7f4b2bc41..c21c0abf5 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToRgba32Bytes.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,11 +9,10 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class ToRgba32Bytes - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { private IMemoryOwner source; @@ -39,7 +38,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 70de8f4e2..b57136a92 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -1,21 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - -using System.Buffers; using System; +using System.Buffers; using System.Numerics; - using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { public abstract class ToVector4 - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { protected IMemoryOwner source; @@ -23,12 +21,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk protected Configuration Configuration => Configuration.Default; - [Params( - 64, - 256, - //512, - //1024, - 2048)] + [Params(64, 256, 2048)] // 512, 1024 public int Count { get; set; } [GlobalSetup] @@ -45,7 +38,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.Dispose(); } - //[Benchmark] + // [Benchmark] public void Naive() { Span s = this.source.GetSpan(); @@ -56,7 +49,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk d[i] = s[i].ToVector4(); } } - [Benchmark] public void PixelOperations_Specialized() @@ -67,4 +59,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk this.destination.GetSpan()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs index 39702d525..3a69a6e24 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; @@ -38,4 +41,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk // PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B | // PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs index ab05a1407..483ab6174 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgba32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -19,7 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(sBytes, dFloats); } [Benchmark] @@ -37,7 +40,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.BasicIntrinsics256.ByteToNormalizedFloat(sBytes, dFloats); } [Benchmark] @@ -46,10 +49,10 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.destination.GetSpan()); - SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(sBytes, dFloats); + SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(sBytes, dFloats); } - //[Benchmark] + // [Benchmark] public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_2Loops() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); @@ -91,7 +94,7 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk } } - //[Benchmark] + // [Benchmark] public void ExtendedIntrinsics_BulkConvertByteToNormalizedFloat_ConvertInSameLoop() { Span sBytes = MemoryMarshal.Cast(this.source.GetSpan()); @@ -127,38 +130,39 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector ConvertToNormalizedSingle(Vector u, Vector scale) { - Vector vi = Vector.AsVectorInt32(u); - Vector v = Vector.ConvertToSingle(vi); + var vi = Vector.AsVectorInt32(u); + var v = Vector.ConvertToSingle(vi); v *= scale; return v; } - // RESULTS (2018 October): - // - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| - // FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | - // BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | - // PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | - // BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | - // PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | - // PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | - // | | | | | | | | | | - // FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | - // BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( - // | | | | | | | | | | - // FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | - // BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | - // ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! - // PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + /*RESULTS (2018 October): + + Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | + ---------------------------- |-------- |------ |------------:|-------------:|------------:|-------:|---------:|-------:|----------:| + FallbackIntrinsics128 | Clr | 64 | 287.62 ns | 6.026 ns | 0.3405 ns | 1.19 | 0.00 | - | 0 B | + BasicIntrinsics256 | Clr | 64 | 240.83 ns | 10.585 ns | 0.5981 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Clr | 64 | 168.28 ns | 11.478 ns | 0.6485 ns | 0.70 | 0.00 | - | 0 B | + PixelOperations_Base | Clr | 64 | 334.08 ns | 38.048 ns | 2.1498 ns | 1.39 | 0.01 | 0.0072 | 24 B | + PixelOperations_Specialized | Clr | 64 | 255.41 ns | 10.939 ns | 0.6181 ns | 1.06 | 0.00 | - | 0 B | <--- ceremonial overhead has been minimized! + | | | | | | | | | | + FallbackIntrinsics128 | Core | 64 | 183.29 ns | 8.931 ns | 0.5046 ns | 1.32 | 0.00 | - | 0 B | + BasicIntrinsics256 | Core | 64 | 139.18 ns | 7.633 ns | 0.4313 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Core | 64 | 66.29 ns | 16.366 ns | 0.9247 ns | 0.48 | 0.01 | - | 0 B | + PixelOperations_Base | Core | 64 | 257.75 ns | 16.959 ns | 0.9582 ns | 1.85 | 0.01 | 0.0072 | 24 B | + PixelOperations_Specialized | Core | 64 | 90.14 ns | 9.955 ns | 0.5625 ns | 0.65 | 0.00 | - | 0 B | + | | | | | | | | | | + FallbackIntrinsics128 | Clr | 2048 | 5,011.84 ns | 347.991 ns | 19.6621 ns | 1.22 | 0.01 | - | 0 B | + BasicIntrinsics256 | Clr | 2048 | 4,119.35 ns | 720.153 ns | 40.6900 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Clr | 2048 | 1,195.29 ns | 164.389 ns | 9.2883 ns |!! 0.29 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + PixelOperations_Base | Clr | 2048 | 6,820.58 ns | 823.433 ns | 46.5255 ns | 1.66 | 0.02 | - | 24 B | + PixelOperations_Specialized | Clr | 2048 | 4,203.53 ns | 176.714 ns | 9.9847 ns | 1.02 | 0.01 | - | 0 B | <--- can't yet detect whether ExtendedIntrinsics are available :( + | | | | | | | | | | + FallbackIntrinsics128 | Core | 2048 | 5,017.89 ns | 4,021.533 ns | 227.2241 ns | 1.24 | 0.05 | - | 0 B | + BasicIntrinsics256 | Core | 2048 | 4,046.51 ns | 1,150.390 ns | 64.9992 ns | 1.00 | 0.00 | - | 0 B | + ExtendedIntrinsics | Core | 2048 | 1,130.59 ns | 832.588 ns | 47.0427 ns |!! 0.28 | 0.01 | - | 0 B | <--- ExtendedIntrinsics rock! + PixelOperations_Base | Core | 2048 | 6,752.68 ns | 272.820 ns | 15.4148 ns | 1.67 | 0.02 | - | 24 B | + PixelOperations_Specialized | Core | 2048 | 1,126.13 ns | 79.192 ns | 4.4745 ns |!! 0.28 | 0.00 | - | 0 B | <--- ExtendedIntrinsics rock! + */ } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs index 855f5b9b4..5ca584917 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToCieLabConvert.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; @@ -18,7 +21,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces return ColorSpaceConverter.ToCieLab(CieXyz).L; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs index 07870b3a8..3f9d1648c 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToHunterLabConvert.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; @@ -30,4 +33,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces return ColorSpaceConverter.ToHunterLab(CieXyz).L; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs index 4d9ba8928..f82afaac4 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToLmsConvert.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; diff --git a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs index f20ffdcab..59705a202 100644 --- a/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs +++ b/tests/ImageSharp.Benchmarks/Color/ColorspaceCieXyzToRgbConvert.cs @@ -1,11 +1,14 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; using SixLabors.ImageSharp.ColorSpaces; - using SixLabors.ImageSharp.ColorSpaces.Conversion; + namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces { public class ColorspaceCieXyzToRgbConvert @@ -18,7 +21,6 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces private static readonly ColourfulConverter ColourfulConverter = new ColourfulConverter(); - [Benchmark(Baseline = true, Description = "Colourful Convert")] public double ColourfulConvert() { @@ -31,4 +33,4 @@ namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces return ColorSpaceConverter.ToRgb(CieXyz).R; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs index 335ecf478..a2290ce1f 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.LookupTables.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Benchmarks +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks { public partial class RgbToYCbCr { @@ -234,4 +237,4 @@ }; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs index 0571513f5..b11e389af 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbToYCbCr.cs @@ -98,6 +98,7 @@ namespace SixLabors.ImageSharp.Benchmarks { result.Data[i] = data[i]; } + return result; } } @@ -125,6 +126,7 @@ namespace SixLabors.ImageSharp.Benchmarks { this.inputSourceRGB[i] = (byte)(42 + i); } + this.inputSourceRGBAsInteger = new int[InputByteCount + Vector.Count]; // Filling this should be part of the measured operation } @@ -139,7 +141,6 @@ namespace SixLabors.ImageSharp.Benchmarks var yPtr = (float*)&result.Y; var cbPtr = (float*)&result.Cb; var crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { @@ -165,7 +166,6 @@ namespace SixLabors.ImageSharp.Benchmarks var yPtr = (float*)&result.Y; var cbPtr = (float*)&result.Cb; var crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { @@ -174,8 +174,7 @@ namespace SixLabors.ImageSharp.Benchmarks var vectorRgb = new Vector3( input.Data[i3 + 0], input.Data[i3 + 1], - input.Data[i3 + 2] - ); + input.Data[i3 + 2]); Vector3 vectorY = VectorY * vectorRgb; Vector3 vectorCb = VectorCb * vectorRgb; @@ -197,7 +196,6 @@ namespace SixLabors.ImageSharp.Benchmarks var yPtr = (float*)&result.Y; var cbPtr = (float*)&result.Cb; var crPtr = (float*)&result.Cr; - // end of code-bloat block :) var yCoeffs = new Vector(ScaledCoeffs.Y); var cbCoeffs = new Vector(ScaledCoeffs.Cb); @@ -243,7 +241,6 @@ namespace SixLabors.ImageSharp.Benchmarks float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) var yCoeffs = new Vector(ScaledCoeffs.Y); var cbCoeffs = new Vector(ScaledCoeffs.Cb); @@ -306,7 +303,6 @@ namespace SixLabors.ImageSharp.Benchmarks float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { @@ -345,7 +341,6 @@ namespace SixLabors.ImageSharp.Benchmarks float* yPtr = (float*)&result.Y; float* cbPtr = (float*)&result.Cb; float* crPtr = (float*)&result.Cr; - // end of code-bloat block :) for (int i = 0; i < InputColorCount; i++) { diff --git a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs index 060a28550..b8e58a8c5 100644 --- a/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs +++ b/tests/ImageSharp.Benchmarks/Color/RgbWorkingSpaceAdapt.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; using Colourful; using Colourful.Conversion; diff --git a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs index 2e3307d29..5d3bc26ba 100644 --- a/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs +++ b/tests/ImageSharp.Benchmarks/Color/YcbCrToRgb.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Benchmarks +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Benchmarks { using System.Numerics; diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index cb4fcbba1..fda98a097 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +#if Windows_NT +using System.Security.Principal; +using BenchmarkDotNet.Diagnostics.Windows; +#endif using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; @@ -13,6 +17,14 @@ namespace SixLabors.ImageSharp.Benchmarks public Config() { this.Add(MemoryDiagnoser.Default); + +#if Windows_NT + if (this.IsElevated) + { + this.Add(new NativeMemoryProfiler()); + } +#endif + } public class ShortClr : Config @@ -22,9 +34,26 @@ namespace SixLabors.ImageSharp.Benchmarks this.Add( Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3) - ); + Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } + } + + public class ShortCore31 : Config + { + public ShortCore31() + { + this.Add(Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + } + } + +#if Windows_NT + private bool IsElevated + { + get + { + return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); } } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/General/Array2D.cs b/tests/ImageSharp.Benchmarks/General/Array2D.cs index fe7fd2090..92190e653 100644 --- a/tests/ImageSharp.Benchmarks/General/Array2D.cs +++ b/tests/ImageSharp.Benchmarks/General/Array2D.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -9,8 +9,8 @@ using SixLabors.ImageSharp; namespace SixLabors.ImageSharp.Benchmarks.General { - /** - * Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + /* + Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | -------------------------------------------- |------ |---------:|---------:|---------:|-------:|---------:| 'Emulated 2D array access using flat array' | 32 | 224.2 ns | 4.739 ns | 13.75 ns | 0.65 | 0.07 | 'Array access using 2D array' | 32 | 346.6 ns | 9.225 ns | 26.91 ns | 1.00 | 0.00 | @@ -19,7 +19,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General * */ - public class Array2D { private float[] flatArray; @@ -34,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General public int Count { get; set; } public int Min { get; private set; } + public int Max { get; private set; } [GlobalSetup] @@ -65,11 +65,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General { for (int j = this.Min; j < this.Max; j++) { - ref float v = ref a[count * i + j]; + ref float v = ref a[(count * i) + j]; v = i * j; s += v; } } + return s; } @@ -87,6 +88,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } @@ -104,6 +106,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } @@ -121,7 +124,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General s += v; } } + return s; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs index c49c383eb..41137e28b 100644 --- a/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs +++ b/tests/ImageSharp.Benchmarks/General/ArrayReverse.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Benchmarks.General [Params(4, 16, 32)] public int Count { get; set; } - byte[] source; + private byte[] source; - byte[] destination; + private byte[] destination; [GlobalSetup] public void SetUp() @@ -34,12 +34,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General { this.ReverseBytes(this.source, 0, this.Count); - //for (int i = 0; i < this.source.Length / 2; i++) - //{ - // byte tmp = this.source[i]; - // this.source[i] = this.source[this.source.Length - i - 1]; - // this.source[this.source.Length - i - 1] = tmp; - //} + /* + for (int i = 0; i < this.source.Length / 2; i++) + { + byte tmp = this.source[i]; + this.source[i] = this.source[this.source.Length - i - 1]; + this.source[this.source.Length - i - 1] = tmp; + }*/ } public void ReverseBytes(byte[] source, int index, int length) @@ -56,4 +57,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs index ea53959b6..fc0b149c1 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Abs.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs index 404714a54..9644cbc7d 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampFloat.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -37,7 +40,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath return acc; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ClampUsingMathF(float x, float min, float max) { @@ -66,4 +68,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath // UsingMathF | 30.37 ns | 0.3764 ns | 0.3337 ns | 1.00 | // UsingBranching | 18.66 ns | 0.1043 ns | 0.0871 ns | 0.61 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs new file mode 100644 index 000000000..145b98b0f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ClampVector4.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; + +using BenchmarkDotNet.Attributes; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath +{ + public class ClampVector4 + { + private readonly float min = -1.5f; + private readonly float max = 2.5f; + private static readonly float[] Values = { -10, -5, -3, -1.5f, -0.5f, 0f, 1f, 1.5f, 2.5f, 3, 10 }; + + [Benchmark(Baseline = true)] + public Vector4 UsingVectorClamp() + { + Vector4 acc = Vector4.Zero; + + for (int i = 0; i < Values.Length; i++) + { + acc += ClampUsingVectorClamp(Values[i], this.min, this.max); + } + + return acc; + } + + [Benchmark] + public Vector4 UsingVectorMinMax() + { + Vector4 acc = Vector4.Zero; + + for (int i = 0; i < Values.Length; i++) + { + acc += ClampUsingVectorMinMax(Values[i], this.min, this.max); + } + + return acc; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 ClampUsingVectorClamp(float x, float min, float max) + { + return Vector4.Clamp(new Vector4(x), new Vector4(min), new Vector4(max)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 ClampUsingVectorMinMax(float x, float min, float max) + { + return Vector4.Min(new Vector4(max), Vector4.Max(new Vector4(min), new Vector4(x))); + } + + // RESULTS + // | Method | Mean | Error | StdDev | Ratio | + // |------------------ |---------:|---------:|---------:|------:| + // | UsingVectorClamp | 75.21 ns | 1.572 ns | 4.057 ns | 1.00 | + // | UsingVectorMinMax | 15.35 ns | 0.356 ns | 0.789 ns | 0.20 | + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs index 94349b20b..0ccde7a13 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoConstant.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -19,4 +22,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath return ImageMaths.Modulo8(this.value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs index d5683673f..e8cb8ca62 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/ModuloPowerOfTwoVariable.cs @@ -1,4 +1,7 @@ -using BenchmarkDotNet.Attributes; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { @@ -28,4 +31,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath // Standard | 1.2465 ns | 0.0093 ns | 0.0455 ns | 1.2423 ns | 1.00 | 0.00 | // Bitwise | 0.0265 ns | 0.0103 ns | 0.0515 ns | 0.0000 ns | 0.02 | 0.04 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs index 0f256fc78..b7eb01fcb 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Pow.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; @@ -9,7 +12,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath [Params(-1.333F, 1.333F)] public float X { get; set; } - [Benchmark(Baseline = true, Description = "Math.Pow 2")] public float MathPow() { diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs index 2c18b2972..bb308d480 100644 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/Round.cs @@ -1,17 +1,20 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath { public class Round { - private const float input = .51F; + private const float Input = .51F; [Benchmark] - public int ConvertTo() => Convert.ToInt32(input); + public int ConvertTo() => Convert.ToInt32(Input); [Benchmark] - public int MathRound() => (int)Math.Round(input); + public int MathRound() => (int)Math.Round(Input); // Results 20th Jan 2019 // Method | Mean | Error | StdDev | Median | diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs index 2c325d184..2afa8753f 100644 --- a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -34,7 +34,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General [Params(10, 50, 100, 1000, 10000)] public int Count { get; set; } - [GlobalSetup] public void Setup() { @@ -74,7 +73,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General Buffer.MemoryCopy(pinnedSource, pinnedDestination, this.Count, this.Count); } - [Benchmark(Description = "Marshal.Copy()")] public unsafe void MarshalCopy() { diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs index b5f339fb3..6d7c3c423 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/ITestPixel.cs @@ -1,10 +1,13 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - interface ITestPixel + public interface ITestPixel where T : struct, ITestPixel { void FromRgba32(Rgba32 source); @@ -25,4 +28,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion void CopyToVector4(ref Vector4 dest); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs index 9f1b2721b..55527da18 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; @@ -9,6 +10,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public abstract class PixelConversion_ConvertFromRgba32 @@ -16,23 +18,23 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion internal struct ConversionRunner where T : struct, ITestPixel { - public readonly T[] dest; + public readonly T[] Dest; - public readonly Rgba32[] source; + public readonly Rgba32[] Source; public ConversionRunner(int count) { - this.dest = new T[count]; - this.source = new Rgba32[count]; + this.Dest = new T[count]; + this.Source = new Rgba32[count]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunByRefConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -43,10 +45,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunByValConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -57,10 +59,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RunFromBytesConversion() { - int count = this.dest.Length; + int count = this.Dest.Length; - ref T destBaseRef = ref this.dest[0]; - ref Rgba32 sourceBaseRef = ref this.source[0]; + ref T destBaseRef = ref this.Dest[0]; + ref Rgba32 sourceBaseRef = ref this.Source[0]; for (int i = 0; i < count; i++) { @@ -69,22 +71,19 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } } - - internal ConversionRunner compatibleMemLayoutRunner; - internal ConversionRunner permutedRunnerRgbaToArgb; + internal ConversionRunner CompatibleMemLayoutRunner; - [Params( - 256, - 2048 - )] + internal ConversionRunner PermutedRunnerRgbaToArgb; + + [Params(256, 2048)] public int Count { get; set; } [GlobalSetup] public void Setup() { - this.compatibleMemLayoutRunner = new ConversionRunner(this.Count); - this.permutedRunnerRgbaToArgb = new ConversionRunner(this.Count); + this.CompatibleMemLayoutRunner = new ConversionRunner(this.Count); + this.PermutedRunnerRgbaToArgb = new ConversionRunner(this.Count); } } @@ -93,26 +92,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark(Baseline = true)] public void ByRef() { - this.compatibleMemLayoutRunner.RunByRefConversion(); + this.CompatibleMemLayoutRunner.RunByRefConversion(); } [Benchmark] public void ByVal() { - this.compatibleMemLayoutRunner.RunByValConversion(); + this.CompatibleMemLayoutRunner.RunByValConversion(); } [Benchmark] public void FromBytes() { - this.compatibleMemLayoutRunner.RunFromBytesConversion(); + this.CompatibleMemLayoutRunner.RunFromBytesConversion(); } [Benchmark] public void Inline() { - ref Rgba32 sBase = ref this.compatibleMemLayoutRunner.source[0]; - ref Rgba32 dBase = ref Unsafe.As(ref this.compatibleMemLayoutRunner.dest[0]); + ref Rgba32 sBase = ref this.CompatibleMemLayoutRunner.Source[0]; + ref Rgba32 dBase = ref Unsafe.As(ref this.CompatibleMemLayoutRunner.Dest[0]); for (int i = 0; i < this.Count; i++) { @@ -120,12 +119,12 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ---------- |------ |---------:|---------:|---------:|-------:|---------:| - // ByRef | 256 | 128.5 ns | 1.217 ns | 1.138 ns | 1.00 | 0.00 | - // ByVal | 256 | 196.7 ns | 2.792 ns | 2.612 ns | 1.53 | 0.02 | - // FromBytes | 256 | 321.7 ns | 2.180 ns | 1.820 ns | 2.50 | 0.03 | - // Inline | 256 | 129.9 ns | 2.759 ns | 2.581 ns | 1.01 | 0.02 | + /* Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + ---------- |------ |---------:|---------:|---------:|-------:|---------:| + ByRef | 256 | 128.5 ns | 1.217 ns | 1.138 ns | 1.00 | 0.00 | + ByVal | 256 | 196.7 ns | 2.792 ns | 2.612 ns | 1.53 | 0.02 | + FromBytes | 256 | 321.7 ns | 2.180 ns | 1.820 ns | 2.50 | 0.03 | + Inline | 256 | 129.9 ns | 2.759 ns | 2.581 ns | 1.01 | 0.02 | */ } public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConversion_ConvertFromRgba32 @@ -133,26 +132,26 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark(Baseline = true)] public void ByRef() { - this.permutedRunnerRgbaToArgb.RunByRefConversion(); + this.PermutedRunnerRgbaToArgb.RunByRefConversion(); } [Benchmark] public void ByVal() { - this.permutedRunnerRgbaToArgb.RunByValConversion(); + this.PermutedRunnerRgbaToArgb.RunByValConversion(); } [Benchmark] public void FromBytes() { - this.permutedRunnerRgbaToArgb.RunFromBytesConversion(); + this.PermutedRunnerRgbaToArgb.RunFromBytesConversion(); } [Benchmark] public void InlineShuffle() { - ref Rgba32 sBase = ref this.permutedRunnerRgbaToArgb.source[0]; - ref TestArgb dBase = ref this.permutedRunnerRgbaToArgb.dest[0]; + ref Rgba32 sBase = ref this.PermutedRunnerRgbaToArgb.Source[0]; + ref TestArgb dBase = ref this.PermutedRunnerRgbaToArgb.Dest[0]; for (int i = 0; i < this.Count; i++) { @@ -169,8 +168,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark] public void PixelConverter_Rgba32_ToArgb32() { - ref uint sBase = ref Unsafe.As(ref this.permutedRunnerRgbaToArgb.source[0]); - ref uint dBase = ref Unsafe.As(ref this.permutedRunnerRgbaToArgb.dest[0]); + ref uint sBase = ref Unsafe.As(ref this.PermutedRunnerRgbaToArgb.Source[0]); + ref uint dBase = ref Unsafe.As(ref this.PermutedRunnerRgbaToArgb.Dest[0]); for (int i = 0; i < this.Count; i++) { @@ -182,8 +181,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [Benchmark] public void PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer() { - Span source = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.source); - Span dest = MemoryMarshal.Cast(this.permutedRunnerRgbaToArgb.dest); + Span source = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Source); + Span dest = MemoryMarshal.Cast(this.PermutedRunnerRgbaToArgb.Dest); source.CopyTo(dest); ref uint dBase = ref MemoryMarshal.GetReference(dest); @@ -195,21 +194,23 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // RESULTS: - // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | - // ---------------------------------------------------------- |------ |-----------:|-----------:|-----------:|-------:|---------:| - // ByRef | 256 | 328.7 ns | 6.6141 ns | 6.1868 ns | 1.00 | 0.00 | - // ByVal | 256 | 322.0 ns | 4.3541 ns | 4.0728 ns | 0.98 | 0.02 | - // FromBytes | 256 | 321.5 ns | 3.3499 ns | 3.1335 ns | 0.98 | 0.02 | - // InlineShuffle | 256 | 330.7 ns | 4.2525 ns | 3.9778 ns | 1.01 | 0.02 | - // PixelConverter_Rgba32_ToArgb32 | 256 | 167.4 ns | 0.6357 ns | 0.5309 ns | 0.51 | 0.01 | - // PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 256 | 196.6 ns | 0.8929 ns | 0.7915 ns | 0.60 | 0.01 | - // | | | | | | | - // ByRef | 2048 | 2,534.4 ns | 8.2947 ns | 6.9265 ns | 1.00 | 0.00 | - // ByVal | 2048 | 2,638.5 ns | 52.6843 ns | 70.3320 ns | 1.04 | 0.03 | - // FromBytes | 2048 | 2,517.2 ns | 40.8055 ns | 38.1695 ns | 0.99 | 0.01 | - // InlineShuffle | 2048 | 2,546.5 ns | 21.2506 ns | 19.8778 ns | 1.00 | 0.01 | - // PixelConverter_Rgba32_ToArgb32 | 2048 | 1,265.7 ns | 5.1397 ns | 4.5562 ns | 0.50 | 0.00 | - // PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 2048 | 1,410.3 ns | 11.1939 ns | 9.9231 ns | 0.56 | 0.00 |// + /* + RESULTS: + Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | + ---------------------------------------------------------- |------ |-----------:|-----------:|-----------:|-------:|---------:| + ByRef | 256 | 328.7 ns | 6.6141 ns | 6.1868 ns | 1.00 | 0.00 | + ByVal | 256 | 322.0 ns | 4.3541 ns | 4.0728 ns | 0.98 | 0.02 | + FromBytes | 256 | 321.5 ns | 3.3499 ns | 3.1335 ns | 0.98 | 0.02 | + InlineShuffle | 256 | 330.7 ns | 4.2525 ns | 3.9778 ns | 1.01 | 0.02 | + PixelConverter_Rgba32_ToArgb32 | 256 | 167.4 ns | 0.6357 ns | 0.5309 ns | 0.51 | 0.01 | + PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 256 | 196.6 ns | 0.8929 ns | 0.7915 ns | 0.60 | 0.01 | + | | | | | | | + ByRef | 2048 | 2,534.4 ns | 8.2947 ns | 6.9265 ns | 1.00 | 0.00 | + ByVal | 2048 | 2,638.5 ns | 52.6843 ns | 70.3320 ns | 1.04 | 0.03 | + FromBytes | 2048 | 2,517.2 ns | 40.8055 ns | 38.1695 ns | 0.99 | 0.01 | + InlineShuffle | 2048 | 2,546.5 ns | 21.2506 ns | 19.8778 ns | 1.00 | 0.01 | + PixelConverter_Rgba32_ToArgb32 | 2048 | 1,265.7 ns | 5.1397 ns | 4.5562 ns | 0.50 | 0.00 | + PixelConverter_Rgba32_ToArgb32_CopyThenWorkOnSingleBuffer | 2048 | 1,410.3 ns | 11.1939 ns | 9.9231 ns | 0.56 | 0.00 | + */ } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs index d0c8a3045..0b24276d3 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromVector4.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System.Numerics; using System.Runtime.CompilerServices; @@ -8,12 +9,13 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertFromVector4 { [StructLayout(LayoutKind.Sequential)] - struct TestRgbaVector : ITestPixel + private struct TestRgbaVector : ITestPixel { private Vector4 v; @@ -39,13 +41,17 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } public void FromRgba32(Rgba32 source) => throw new System.NotImplementedException(); + public void FromRgba32(ref Rgba32 source) => throw new System.NotImplementedException(); + public void FromBytes(byte r, byte g, byte b, byte a) => throw new System.NotImplementedException(); + public Rgba32 ToRgba32() => throw new System.NotImplementedException(); + public void CopyToRgba32(ref Rgba32 dest) => throw new System.NotImplementedException(); } - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] dest; @@ -100,7 +106,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion this.nonVectorRunner = new ConversionRunner(this.Count); this.vectorRunner = new ConversionRunner(this.Count); } - + [Benchmark(Baseline = true)] public void VectorByRef() { @@ -124,7 +130,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { this.nonVectorRunner.RunByValConversion(); } - } /* @@ -135,8 +140,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion * VectorByVal | 32 | 24.5347 ns | 0.0771 ns | 1.04 | 0.01 | * NonVectorByRef | 32 | 59.0187 ns | 0.2114 ns | 2.49 | 0.01 | * NonVectorByVal | 32 | 58.7529 ns | 0.2545 ns | 2.48 | 0.02 | - * + * * !!! Conclusion !!! * We do not need by-ref version of ConvertFromVector4() stuff */ -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs index ea8b34c24..93a27a555 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -8,14 +11,14 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { /// /// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? - /// 1. Rgba32 ToRgba32(); + /// 1. Rgba32 ToRgba32(); /// OR /// 2. void CopyToRgba32(ref Rgba32 dest); /// ? /// public class PixelConversion_ConvertToRgba32 { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -98,7 +101,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /* * Results: - * + * * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | * --------------- |------ |------------ |---------- |------- |-------------- | * CompatibleRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | @@ -106,4 +109,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | */ -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs index fff9ae9bc..6a59e993b 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -8,7 +11,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToRgba32_AsPartOfCompositeOperation { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -110,4 +113,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // CompatibleCopyTo | 32 | 36.12 ns | 0.3596 ns | 0.3003 ns | 0.68 | 0.01 | // PermutedRetval | 32 | 303.61 ns | 5.1697 ns | 4.8358 ns | 5.72 | 0.09 | // PermutedCopyTo | 32 | 38.05 ns | 0.8053 ns | 1.2297 ns | 0.72 | 0.02 | -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs index 68a16b791..80a2e80d2 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -7,7 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4 { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -78,4 +81,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // UseRetval | 32 | 109.0 ns | 1.202 ns | 1.125 ns | 1.00 | // UseCopyTo | 32 | 108.6 ns | 1.151 ns | 1.020 ns | 1.00 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs index c6daf0f1e..699a4cf09 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertToVector4_AsPartOfCompositeOperation.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -7,7 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { public class PixelConversion_ConvertToVector4_AsPartOfCompositeOperation { - struct ConversionRunner + private struct ConversionRunner where T : struct, ITestPixel { private T[] source; @@ -92,4 +95,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // UseRetval | 32 | 120.2 ns | 1.560 ns | 1.383 ns | 1.00 | 0.00 | // UseCopyTo | 32 | 121.7 ns | 2.439 ns | 2.281 ns | 1.01 | 0.02 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs index 40893914e..7acb3ecfe 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Argb32.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.NoInlining)] private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -74,7 +77,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - [Benchmark] public void Default_Group4() { @@ -98,7 +100,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d2, 1).FromRgba32(s3); } } - + [Benchmark] public void BitOps() { @@ -137,6 +139,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The argb value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToArgb32(uint packedRgba) { @@ -148,6 +151,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The bgra value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToBgra32(uint packedRgba) { @@ -173,4 +177,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion // BitOps | 64 | 39.25 ns | 0.3266 ns | 0.2895 ns | 0.37 | // BitOps_GroupAsULong | 64 | 41.80 ns | 0.2227 ns | 0.2083 ns | 0.39 | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs index cd0aed3c4..4c8b987b2 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -10,8 +13,8 @@ using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { - //[MonoJob] - //[RyuJitX64Job] + // [MonoJob] + // [RyuJitX64Job] public class PixelConversion_Rgba32_To_Bgra32 { private Rgba32[] source; @@ -19,19 +22,22 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion private Bgra32[] dest; [StructLayout(LayoutKind.Sequential)] - struct Tuple4OfUInt32 + private struct Tuple4OfUInt32 { - public uint V0, V1, V2, V3; + private uint v0; + private uint v1; + private uint v2; + private uint v3; public void ConvertMe() { - this.V0 = FromRgba32.ToBgra32(this.V0); - this.V1 = FromRgba32.ToBgra32(this.V1); - this.V2 = FromRgba32.ToBgra32(this.V2); - this.V3 = FromRgba32.ToBgra32(this.V3); + this.v0 = FromRgba32.ToBgra32(this.v0); + this.v1 = FromRgba32.ToBgra32(this.v1); + this.v2 = FromRgba32.ToBgra32(this.v2); + this.v3 = FromRgba32.ToBgra32(this.v3); } } - + [Params(64)] public int Count { get; set; } @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.NoInlining)] private static void Default_GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -81,7 +87,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion ref Rgba32 sBase = ref this.source[0]; ref Bgra32 dBase = ref this.dest[0]; - for (int i = 0; i < this.Count; i+=2) + for (int i = 0; i < this.Count; i += 2) { ref Rgba32 s0 = ref Unsafe.Add(ref sBase, i); Rgba32 s1 = Unsafe.Add(ref s0, 1); @@ -115,10 +121,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d2, 1).FromRgba32(s3); } } - + [MethodImpl(MethodImplOptions.NoInlining)] private static void Group4GenericImpl(ReadOnlySpan source, Span dest) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ref Rgba32 sBase = ref MemoryMarshal.GetReference(source); ref TPixel dBase = ref MemoryMarshal.GetReference(dest); @@ -141,13 +147,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Default_Group4_Generic() { Group4GenericImpl(this.source.AsSpan(), this.dest.AsSpan()); } - //[Benchmark] + // [Benchmark] public void Default_Group8() { ref Rgba32 sBase = ref this.source[0]; @@ -174,7 +180,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion ref Bgra32 d5 = ref Unsafe.Add(ref d4, 1); ref Bgra32 d6 = ref Unsafe.Add(ref d5, 1); - d0.FromRgba32(s0); d1.FromRgba32(s1); d2.FromRgba32(s2); @@ -214,7 +219,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Bitops_SingleTuple() { ref Tuple4OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); @@ -225,11 +230,11 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void Bitops_Simd() { - ref Octet.OfUInt32 sBase = ref Unsafe.As(ref this.source[0]); - ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + ref Octet sBase = ref Unsafe.As>(ref this.source[0]); + ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); for (int i = 0; i < this.Count / 8; i++) { @@ -237,22 +242,24 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } +#pragma warning disable SA1132 // Do not combine fields [StructLayout(LayoutKind.Sequential)] - struct B + private struct B { - public uint tmp2, tmp5, tmp8, tmp11, tmp14, tmp17, tmp20, tmp23; + public uint Tmp2, Tmp5, Tmp8, Tmp11, Tmp14, Tmp17, Tmp20, Tmp23; } [StructLayout(LayoutKind.Sequential)] - struct C + private struct C { - public uint tmp3, tmp6, tmp9, tmp12, tmp15, tmp18, tmp21, tmp24; + public uint Tmp3, Tmp6, Tmp9, Tmp12, Tmp15, Tmp18, Tmp21, Tmp24; } +#pragma warning restore SA1132 // Do not combine fields [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BitopsSimdImpl(ref Octet.OfUInt32 s, ref Octet.OfUInt32 d) + private static void BitopsSimdImpl(ref Octet s, ref Octet d) { - Vector sVec = Unsafe.As>(ref s); + Vector sVec = Unsafe.As, Vector>(ref s); var aMask = new Vector(0xFF00FF00); var bMask = new Vector(0x00FF00FF); @@ -263,22 +270,22 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion C c = default; - c.tmp3 = (b.tmp2 << 16) | (b.tmp2 >> 16); - c.tmp6 = (b.tmp5 << 16) | (b.tmp5 >> 16); - c.tmp9 = (b.tmp8 << 16) | (b.tmp8 >> 16); - c.tmp12 = (b.tmp11 << 16) | (b.tmp11 >> 16); - c.tmp15 = (b.tmp14 << 16) | (b.tmp14 >> 16); - c.tmp18 = (b.tmp17 << 16) | (b.tmp17 >> 16); - c.tmp21 = (b.tmp20 << 16) | (b.tmp20 >> 16); - c.tmp24 = (b.tmp23 << 16) | (b.tmp23 >> 16); + c.Tmp3 = (b.Tmp2 << 16) | (b.Tmp2 >> 16); + c.Tmp6 = (b.Tmp5 << 16) | (b.Tmp5 >> 16); + c.Tmp9 = (b.Tmp8 << 16) | (b.Tmp8 >> 16); + c.Tmp12 = (b.Tmp11 << 16) | (b.Tmp11 >> 16); + c.Tmp15 = (b.Tmp14 << 16) | (b.Tmp14 >> 16); + c.Tmp18 = (b.Tmp17 << 16) | (b.Tmp17 >> 16); + c.Tmp21 = (b.Tmp20 << 16) | (b.Tmp20 >> 16); + c.Tmp24 = (b.Tmp23 << 16) | (b.Tmp23 >> 16); Vector cc = Unsafe.As>(ref c); Vector dd = aa + cc; - d = Unsafe.As, Octet.OfUInt32>(ref dd); + d = Unsafe.As, Octet>(ref dd); } - //[Benchmark] + // [Benchmark] public void BitOps_Group2() { ref uint sBase = ref Unsafe.As(ref this.source[0]); @@ -294,7 +301,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion Unsafe.Add(ref d0, 1) = FromRgba32.ToBgra32(s1); } } - + [Benchmark] public void BitOps_GroupAsULong() { @@ -315,7 +322,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - //[Benchmark] + // [Benchmark] public void BitOps_GroupAsULong_V2() { ref ulong sBase = ref Unsafe.As(ref this.source[0]); @@ -350,6 +357,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The argb value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToArgb32(uint packedRgba) { @@ -361,6 +369,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion /// /// Converts a packed to . /// + /// The bgra value. [MethodImpl(InliningOptions.ShortMethod)] public static uint ToBgra32(uint packedRgba) { @@ -376,7 +385,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion } } - // RESULTS: // Method | Count | Mean | Error | StdDev | Scaled | ScaledSD | // -------------------- |------ |---------:|----------:|----------:|-------:|---------:| diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs index 76de794ec..498520605 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestArgb.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,9 +10,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { [StructLayout(LayoutKind.Sequential)] - struct TestArgb : ITestPixel + public struct TestArgb : ITestPixel { - public byte A, R, G, B; + public byte A; + public byte R; + public byte G; + public byte B; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromRgba32(Rgba32 p) @@ -86,4 +92,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion dest.W = this.A; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs index 36d5f3e5b..b325ec7c6 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/TestRgba.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,9 +10,12 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion { [StructLayout(LayoutKind.Sequential)] - struct TestRgba : ITestPixel + public struct TestRgba : ITestPixel { - public byte R, G, B, A; + public byte R; + public byte G; + public byte B; + public byte A; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void FromRgba32(Rgba32 source) @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ToVector4() { - return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); + return new Vector4(this.R, this.G, this.B, this.A) * new Vector4(1f / 255f); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -68,4 +74,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion dest = tmp; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/StructCasting.cs b/tests/ImageSharp.Benchmarks/General/StructCasting.cs index bed68b54a..ff89ad3ff 100644 --- a/tests/ImageSharp.Benchmarks/General/StructCasting.cs +++ b/tests/ImageSharp.Benchmarks/General/StructCasting.cs @@ -1,4 +1,7 @@ -using System.Runtime.CompilerServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General diff --git a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs index 02bc5d843..80f404162 100644 --- a/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs +++ b/tests/ImageSharp.Benchmarks/General/Vector4Constants.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; @@ -28,8 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General this.GetRandomFloat(), this.GetRandomFloat(), this.GetRandomFloat(), - this.GetRandomFloat() - ); + this.GetRandomFloat()); } [Benchmark(Baseline = true)] @@ -37,10 +39,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General { Vector4 p = this.parameter; - Vector4 x = p * A / B + p * C / D; - Vector4 y = p / A * B + p / C * D; - Vector4 z = Vector4.Min(p, A); - Vector4 w = Vector4.Max(p, B); + Vector4 x = (p * A / B) + (p * C / D); + Vector4 y = (p / A * B) + (p / C * D); + var z = Vector4.Min(p, A); + var w = Vector4.Max(p, B); return x + y + z + w; } @@ -49,10 +51,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General { Vector4 p = this.parameter; - Vector4 x = p * new Vector4(1.2f) / new Vector4(2.3f) + p * new Vector4(4.5f) / new Vector4(6.7f); - Vector4 y = p / new Vector4(1.2f) * new Vector4(2.3f) + p / new Vector4(4.5f) * new Vector4(6.7f); - Vector4 z = Vector4.Min(p, new Vector4(1.2f)); - Vector4 w = Vector4.Max(p, new Vector4(2.3f)); + Vector4 x = (p * new Vector4(1.2f) / new Vector4(2.3f)) + (p * new Vector4(4.5f) / new Vector4(6.7f)); + Vector4 y = (p / new Vector4(1.2f) * new Vector4(2.3f)) + (p / new Vector4(4.5f) * new Vector4(6.7f)); + var z = Vector4.Min(p, new Vector4(1.2f)); + var w = Vector4.Max(p, new Vector4(2.3f)); return x + y + z + w; } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs index 60bf615c5..41764b816 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; @@ -24,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < this.InputSize; i++) { - this.input[i] = (uint) i; + this.input[i] = (uint)i; } } @@ -43,7 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { var v = new Vector(this.testValue); - for (int i = 0; i < this.input.Length; i+=Vector.Count) + for (int i = 0; i < this.input.Length; i += Vector.Count) { var a = new Vector(this.input, i); a = Vector.BitwiseOr(a, v); diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs index be9534f7d..8d842a0f5 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; @@ -51,4 +54,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs index bfc8d3de3..f103867cd 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; @@ -53,4 +56,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs index df09aa569..30dddf483 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs @@ -1,10 +1,15 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; namespace ImageSharp.Benchmarks.General.Vectorization { +#pragma warning disable SA1649 // File name should match first type name public class DivFloat : SIMDBenchmarkBase.Divide +#pragma warning restore SA1649 // File name should match first type name { protected override float GetTestValue() => 42; @@ -53,7 +58,7 @@ namespace ImageSharp.Benchmarks.General.Vectorization { protected override short GetTestValue() => 42; - protected override Vector GetTestVector() => new Vector(new short[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}); + protected override Vector GetTestVector() => new Vector(new short[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); [Benchmark(Baseline = true)] public void Standard() @@ -65,4 +70,4 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs index 79207a9ff..61de53782 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs index d837556f7..a800df405 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs index 7a679c000..5e9ffaae8 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs @@ -1,9 +1,14 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using BenchmarkDotNet.Attributes; namespace ImageSharp.Benchmarks.General.Vectorization { +#pragma warning disable SA1649 // File name should match first type name public class MulUInt32 : SIMDBenchmarkBase.Multiply +#pragma warning restore SA1649 // File name should match first type name { protected override uint GetTestValue() => 42u; @@ -47,4 +52,4 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs index 23f13c89b..cdc7cac2e 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/Premultiply.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -56,4 +59,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization source *= new Vector4(w) { W = 1 }; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs index 19a1bcea4..dc921bc42 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using System.Runtime.InteropServices; @@ -15,22 +18,20 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization public int InputSize { get; set; } [StructLayout(LayoutKind.Explicit)] - struct UIntFloatUnion + private struct UIntFloatUnion { [FieldOffset(0)] - public float f; + public float F; [FieldOffset(0)] - public uint i; + public uint I; } - [GlobalSetup] public void Setup() { this.input = new uint[this.InputSize]; this.result = new float[this.InputSize]; - for (int i = 0; i < this.InputSize; i++) { this.input[i] = (uint)i; @@ -43,8 +44,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization UIntFloatUnion u = default; for (int i = 0; i < this.input.Length; i++) { - u.i = this.input[i]; - this.result[i] = u.f; + u.I = this.input[i]; + this.result[i] = u.F; } } @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < this.input.Length; i += Vector.Count) { var a = new Vector(this.input, i); - Vector b = Vector.AsVectorSingle(a); + var b = Vector.AsVectorSingle(a); b.CopyTo(this.result, i); } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs index 8a14f0245..8fa0b5cfc 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using System.Runtime.CompilerServices; @@ -22,7 +25,7 @@ namespace ImageSharp.Benchmarks.General.Vectorization [Params(32)] public int InputSize { get; set; } - + [GlobalSetup] public virtual void Setup() { @@ -63,7 +66,5 @@ namespace ImageSharp.Benchmarks.General.Vectorization } } } - - } } diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs index 2c9f4289e..3c79df494 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Numerics; using System.Runtime.CompilerServices; @@ -32,10 +35,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization for (int i = 0; i < n; i++) { - // union { float f; uint32_t i; } u; - // u.f = 32768.0f + x * (255.0f / 256.0f); - // return (uint8_t)u.i; - ref Vector df = ref Unsafe.Add(ref b, i); var vi = Vector.AsVectorUInt32(df); @@ -67,7 +66,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization Unsafe.Add(ref bf, i) = v; } } - + [Benchmark] public void StandardSimdFromInt() { @@ -87,7 +86,6 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } - [Benchmark] public void StandardSimdFromInt_RefCast() { diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs index 4d83dd491..6d177588b 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/VectorFetching.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { using System; @@ -18,13 +21,13 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization [Params(64)] public int InputSize { get; set; } - + [GlobalSetup] public void Setup() { this.data = new float[this.InputSize]; this.testValue = 42; - + for (int i = 0; i < this.InputSize; i++) { this.data[i] = i; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index 2bc3af4c9..beac94269 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -1,4 +1,7 @@ -using System.Numerics; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; @@ -28,8 +31,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { const int N = Count / 8; - ref Octet.OfByte sBase = ref Unsafe.As(ref this.source[0]); - ref Octet.OfUInt32 dBase = ref Unsafe.As(ref this.dest[0]); + ref Octet sBase = ref Unsafe.As>(ref this.source[0]); + ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); for (int i = 0; i < N; i++) { @@ -61,4 +64,4 @@ namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 60b1fde8e..f380d0a6a 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -9,6 +9,9 @@ false false + + + @@ -19,9 +22,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 4241a12f6..8953228f9 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Benchmarks Span background, Span source, Span amount) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Benchmarks Span background, Span source, Span amount) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs index e60ff8f02..8a5cccd68 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs @@ -1,20 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; - using System.Drawing; using System.Drawing.Drawing2D; - using BenchmarkDotNet.Attributes; + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SDImage = System.Drawing.Image; -using SDSize = System.Drawing.Size; using SDRectangle = System.Drawing.Rectangle; +using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks { + [Config(typeof(Config.ShortClr))] public class Crop : BenchmarkBase { [Benchmark(Baseline = true, Description = "System.Drawing Crop")] @@ -28,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.CompositingQuality = CompositingQuality.HighQuality; graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - + return destination.Size; } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index b36b28ef3..7718e7215 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -10,8 +10,7 @@ namespace SixLabors.ImageSharp.Benchmarks using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Processing; - - using CoreImage = ImageSharp.Image; + using CoreImage = SixLabors.ImageSharp.Image; public class DetectEdges : BenchmarkBase { @@ -51,4 +50,4 @@ namespace SixLabors.ImageSharp.Benchmarks this.image.Mutate(x => x.DetectEdges(EdgeDetectionOperators.Sobel)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs index e5b12a0a2..e53661c73 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs @@ -1,7 +1,9 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp; namespace SixLabors.ImageSharp.Benchmarks.Samplers { @@ -11,9 +13,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoDiffuse() { - using (var image = new Image(Configuration.Default, 800, 800, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) + { + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); + + return image.Size(); + } + } + + [Benchmark] + public Size DoDither() + { + using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) { - image.Mutate(x => x.Diffuse()); + image.Mutate(x => x.Dither()); return image.Size(); } @@ -21,28 +34,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// #### 25th October 2019 #### +// #### 20th February 2020 #### // -// BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362 +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 // Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores -// .NET Core SDK = 3.0.100 -// -// [Host] : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT -// Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.8.4018.0 -// Core : .NET Core 2.1.13 (CoreCLR 4.6.28008.01, CoreFX 4.6.28008.01), 64bit RyuJIT -// -// IterationCount=3 LaunchCount=1 WarmupCount=3 +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-OJKYBT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-RZWLFP : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-NUYUQV : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT // -// #### Before #### +// IterationCount=3 LaunchCount=1 WarmupCount=3 // -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |----- |-------- |----------:|---------:|---------:|------:|------:|------:|----------:| -// | DoDiffuse | Clr | Clr | 129.58 ms | 24.60 ms | 1.349 ms | - | - | - | 6 KB | -// | DoDiffuse | Core | Core | 92.63 ms | 89.78 ms | 4.921 ms | - | - | - | 4.58 KB | -// -// #### After #### -// -// | Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |---------- |----- |-------- |----------:|----------:|----------:|------:|------:|------:|----------:| -// | DoDiffuse | Clr | Clr | 124.93 ms | 33.297 ms | 1.8251 ms | - | - | - | 2 KB | -// | DoDiffuse | Core | Core | 89.63 ms | 9.895 ms | 0.5424 ms | - | - | - | 1.91 KB | +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |---------- |-------------- |----------:|----------:|----------:|------:|------:|------:|----------:| +// | DoDiffuse | .NET 4.7.2 | 30.535 ms | 19.217 ms | 1.0534 ms | - | - | - | 26.25 KB | +// | DoDither | .NET 4.7.2 | 14.174 ms | 1.625 ms | 0.0891 ms | - | - | - | 31.38 KB | +// | DoDiffuse | .NET Core 2.1 | 15.984 ms | 3.686 ms | 0.2020 ms | - | - | - | 25.98 KB | +// | DoDither | .NET Core 2.1 | 8.646 ms | 1.635 ms | 0.0896 ms | - | - | - | 28.99 KB | +// | DoDiffuse | .NET Core 3.1 | 16.235 ms | 9.612 ms | 0.5269 ms | - | - | - | 25.96 KB | +// | DoDither | .NET Core 3.1 | 8.429 ms | 1.270 ms | 0.0696 ms | - | - | - | 31.61 KB | diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs index 3a47d016a..711669b14 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -10,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.White)) + using (var image = new Image(Configuration.Default, 400, 400, Color.White)) { image.Mutate(c => c.GaussianBlur()); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs index 172e24372..49a1bd541 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Resize.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Drawing; @@ -14,8 +14,10 @@ using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Benchmarks { [Config(typeof(Config.ShortClr))] +#pragma warning disable SA1649 // File name should match first type name public abstract class ResizeBenchmarkBase - where TPixel : struct, IPixel +#pragma warning restore SA1649 // File name should match first type name + where TPixel : unmanaged, IPixel { protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); @@ -24,20 +26,18 @@ namespace SixLabors.ImageSharp.Benchmarks private Bitmap sourceBitmap; [Params("3032-400")] - public virtual string SourceToDest { get; set; } - + public virtual string SourceToDest { get; set; } + protected int SourceSize { get; private set; } protected int DestSize { get; private set; } - [GlobalSetup] public virtual void Setup() { string[] stuff = this.SourceToDest.Split('-'); this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture); this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture); - this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); } @@ -75,11 +75,13 @@ namespace SixLabors.ImageSharp.Benchmarks // Parallel cases have been disabled for fast benchmark execution. // Uncomment, if you are interested in parallel speedup - //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] - //public int ImageSharp_P4() => this.RunImageSharpResize(4); + /* + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 4")] + public int ImageSharp_P4() => this.RunImageSharpResize(4); - //[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] - //public int ImageSharp_P8() => this.RunImageSharpResize(8); + [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 8")] + public int ImageSharp_P8() => this.RunImageSharpResize(8); + */ protected int RunImageSharpResize(int maxDegreeOfParallelism) { @@ -110,9 +112,9 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3394.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // - // IterationCount=3 LaunchCount=1 WarmupCount=3 - // + // + // IterationCount=3 LaunchCount=1 WarmupCount=3 + // // Method | Job | Runtime | SourceToDest | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |------------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032-400 | 120.11 ms | 1.435 ms | 0.0786 ms | 1.00 | 0.00 | - | - | - | 1638 B | @@ -157,9 +159,9 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // + // // IterationCount=3 LaunchCount=1 WarmupCount=3 - // + // // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032 | 400 | 119.01 ms | 18.513 ms | 1.0147 ms | 1.00 | - | - | - | 1638 B | @@ -185,7 +187,7 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // + // // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |----------- |--------- |----------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032 | 400 | 121.37 ms | 48.580 ms | 2.6628 ms | 1.00 | 0.00 | - | - | - | 2048 B | @@ -195,7 +197,6 @@ namespace SixLabors.ImageSharp.Benchmarks // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | } - public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) @@ -212,9 +213,9 @@ namespace SixLabors.ImageSharp.Benchmarks // [Host] : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT // Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3362.0 // Core : .NET Core 2.1.9 (CoreCLR 4.6.27414.06, CoreFX 4.6.27415.01), 64bit RyuJIT - // + // // IterationCount=3 LaunchCount=1 WarmupCount=3 - // + // // Method | Job | Runtime | SourceSize | DestSize | Mean | Error | StdDev | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op | // ----------------------------------------- |----- |-------- |----------- |--------- |---------:|----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:| // SystemDrawing | Clr | Clr | 3032 | 400 | 120.7 ms | 68.985 ms | 3.7813 ms | 1.00 | 0.00 | - | - | - | 1638 B | @@ -223,4 +224,4 @@ namespace SixLabors.ImageSharp.Benchmarks // SystemDrawing | Core | Core | 3032 | 400 | 118.3 ms | 6.899 ms | 0.3781 ms | 1.00 | 0.00 | - | - | - | 96 B | // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 122.4 ms | 15.069 ms | 0.8260 ms | 1.03 | 0.01 | - | - | - | 15712 B | } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs index 244a0bb41..0610079fe 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp; namespace SixLabors.ImageSharp.Benchmarks.Samplers { @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoRotate() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Rotate(37.5F)); @@ -24,25 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// Nov 7 2018 -//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 -//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores -//.NET Core SDK = 2.1.403 - -// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT -// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 -// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - -//LaunchCount=1 TargetCount=3 WarmupCount=3 - -// #### BEFORE ####: -// Method | Runtime | Mean | Error | StdDev | Allocated | -//--------- |-------- |---------:|----------:|----------:|----------:| -// DoRotate | Clr | 85.19 ms | 13.379 ms | 0.7560 ms | 6 KB | -// DoRotate | Core | 53.51 ms | 9.512 ms | 0.5375 ms | 4.29 KB | - -// #### AFTER ####: -//Method | Runtime | Mean | Error | StdDev | Allocated | -//--------- |-------- |---------:|---------:|---------:|----------:| -// DoRotate | Clr | 77.08 ms | 23.97 ms | 1.354 ms | 6 KB | -// DoRotate | Core | 40.36 ms | 47.43 ms | 2.680 ms | 4.36 KB | \ No newline at end of file +// #### 21th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-HOGSNT : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-FKDHXC : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-ODABAZ : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |--------- |-------------- |---------:|---------:|---------:|------:|------:|------:|----------:| +// | DoRotate | .NET 4.7.2 | 28.77 ms | 3.304 ms | 0.181 ms | - | - | - | 6.5 KB | +// | DoRotate | .NET Core 2.1 | 16.27 ms | 1.044 ms | 0.057 ms | - | - | - | 5.25 KB | +// | DoRotate | .NET Core 3.1 | 17.12 ms | 4.352 ms | 0.239 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs index 4061120b7..7b8ec83a5 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Skew.cs @@ -1,10 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp; namespace SixLabors.ImageSharp.Benchmarks.Samplers { @@ -14,7 +13,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public Size DoSkew() { - using (var image = new Image(Configuration.Default, 400, 400, Rgba32.BlanchedAlmond)) + using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) { image.Mutate(x => x.Skew(20, 10)); @@ -24,25 +23,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers } } -// Nov 7 2018 -//BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17763 -//Intel Core i7-6600U CPU 2.60GHz(Skylake), 1 CPU, 4 logical and 2 physical cores -//.NET Core SDK = 2.1.403 - -// [Host] : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT -// Job-KKDIMW : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3190.0 -// Job-IUZRFA : .NET Core 2.1.5 (CoreCLR 4.6.26919.02, CoreFX 4.6.26919.02), 64bit RyuJIT - -//LaunchCount=1 TargetCount=3 WarmupCount=3 - -// #### BEFORE ####: -//Method | Runtime | Mean | Error | StdDev | Allocated | -//------- |-------- |---------:|---------:|----------:|----------:| -// DoSkew | Clr | 78.14 ms | 8.383 ms | 0.4736 ms | 6 KB | -// DoSkew | Core | 44.22 ms | 4.109 ms | 0.2322 ms | 4.28 KB | - -// #### AFTER ####: -//Method | Runtime | Mean | Error | StdDev | Allocated | -//------- |-------- |---------:|----------:|----------:|----------:| -// DoSkew | Clr | 71.63 ms | 25.589 ms | 1.4458 ms | 6 KB | -// DoSkew | Core | 38.99 ms | 8.640 ms | 0.4882 ms | 4.36 KB | \ No newline at end of file +// #### 21th February 2020 #### +// +// BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363 +// Intel Core i7-8650U CPU 1.90GHz(Kaby Lake R), 1 CPU, 8 logical and 4 physical cores +// .NET Core SDK = 3.1.101 +// +// [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// Job-VKKTMF : .NET Framework 4.8 (4.8.4121.0), X64 RyuJIT +// Job-KTVRKR : .NET Core 2.1.15 (CoreCLR 4.6.28325.01, CoreFX 4.6.28327.02), X64 RyuJIT +// Job-EONWDB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT +// +// IterationCount=3 LaunchCount=1 WarmupCount=3 +// +// | Method | Runtime | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +// |------- |-------------- |---------:|----------:|---------:|------:|------:|------:|----------:| +// | DoSkew | .NET 4.7.2 | 24.60 ms | 33.971 ms | 1.862 ms | - | - | - | 6.5 KB | +// | DoSkew | .NET Core 2.1 | 12.13 ms | 2.256 ms | 0.124 ms | - | - | - | 5.21 KB | +// | DoSkew | .NET Core 3.1 | 12.83 ms | 1.442 ms | 0.079 ms | - | - | - | 6.57 KB | diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 99269e339..7c8031693 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -14,6 +14,10 @@ false + + + + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index f041e16eb..a94d0ed83 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -7,6 +7,10 @@ using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; using Xunit.Abstractions; +// in this file, comments are used for disabling stuff for local execution +#pragma warning disable SA1515 +#pragma warning disable SA1512 + namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { public class Program @@ -28,10 +32,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox public static void Main(string[] args) { // RunJpegColorProfilingTests(); - - // RunDecodeJpegProfilingTests(); + RunDecodeJpegProfilingTests(); // RunToVector4ProfilingTest(); - RunResizeProfilingTest(); + // RunResizeProfilingTest(); Console.ReadLine(); } @@ -61,8 +64,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox foreach (object[] data in JpegProfilingBenchmarks.DecodeJpegData) { string fileName = (string)data[0]; - benchmarks.DecodeJpeg(fileName); + int executionCount = (int)data[1]; + benchmarks.DecodeJpeg(fileName, executionCount); } + + Console.WriteLine("DONE."); } } } diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 8d6033849..dca124849 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -1,151 +1,160 @@ -// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; +using System.Linq; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Advanced { public class AdvancedImageExtensionsTests { - public class GetPixelMemory + public class GetPixelMemoryGroup { [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsOwned(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - // Act: - Memory memory = image0.GetPixelMemory(); + using Image image = provider.GetImage(); - // Assert: - Assert.Equal(image0.Width * image0.Height, memory.Length); - memory.Span.CopyTo(targetBuffer); + // Act: + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); - } - } + // Assert: + VerifyMemoryGroupDataMatchesTestPattern(provider, memoryGroup, image.Size()); } - [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32 | PixelTypes.Bgr24)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void WhenMemoryIsConsumed(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBlankImages(16, 16, PixelTypes.Rgba32)] + public void OwnedMemory_DestructiveMutate_ShouldInvalidateMemoryGroup(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image0 = provider.GetImage()) - { - var targetBuffer = new TPixel[image0.Width * image0.Height]; - image0.GetPixelSpan().CopyTo(targetBuffer); + using Image image = provider.GetImage(); - var managerOfExternalMemory = new TestMemoryManager(targetBuffer); + IMemoryGroup memoryGroup = image.GetPixelMemoryGroup(); + Memory memory = memoryGroup.Single(); - Memory externalMemory = managerOfExternalMemory.Memory; + image.Mutate(c => c.Resize(8, 8)); - using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) - { - Memory internalMemory = image1.GetPixelMemory(); - Assert.Equal(targetBuffer.Length, internalMemory.Length); - Assert.True(Unsafe.AreSame(ref targetBuffer[0], ref internalMemory.Span[0])); + Assert.False(memoryGroup.IsValid); + Assert.ThrowsAny(() => _ = memoryGroup.First()); + Assert.ThrowsAny(() => _ = memory.Span); + } - image0.ComparePixelBufferTo(internalMemory.Span); - } + [Theory] + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Bgr24)] + public void ConsumedMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image0 = provider.GetImage(); + var targetBuffer = new TPixel[image0.Width * image0.Height]; + image0.GetPixelSpan().CopyTo(targetBuffer); + + var managerOfExternalMemory = new TestMemoryManager(targetBuffer); + + Memory externalMemory = managerOfExternalMemory.Memory; - // Make sure externalMemory works after destruction: - image0.ComparePixelBufferTo(externalMemory.Span); + using (var image1 = Image.WrapMemory(externalMemory, image0.Width, image0.Height)) + { + VerifyMemoryGroupDataMatchesTestPattern(provider, image1.GetPixelMemoryGroup(), image1.Size()); } + + // Make sure externalMemory works after destruction: + VerifyMemoryGroupDataMatchesTestPattern(provider, image0.GetPixelMemoryGroup(), image0.Size()); } - } - [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowMemory(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) + private static void VerifyMemoryGroupDataMatchesTestPattern( + TestImageProvider provider, + IMemoryGroup memoryGroup, + Size size) + where TPixel : unmanaged, IPixel { - var targetBuffer = new TPixel[image.Width * image.Height]; + Assert.True(memoryGroup.IsValid); + Assert.Equal(size.Width * size.Height, memoryGroup.TotalLength); + Assert.True(memoryGroup.BufferLength % size.Width == 0); - // Act: - for (int y = 0; y < image.Height; y++) + int cnt = 0; + for (MemoryGroupIndex i = memoryGroup.MaxIndex(); i < memoryGroup.MaxIndex(); i += 1, cnt++) { - Memory rowMemory = image.GetPixelRowMemory(y); - rowMemory.Span.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + int y = cnt / size.Width; + int x = cnt % size.Width; - // Assert: - using (Image image1 = provider.GetImage()) - { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + TPixel expected = provider.GetExpectedBasicTestPatternPixelAt(x, y); + TPixel actual = memoryGroup.GetElementAt(i); + Assert.Equal(expected, actual); } } } [Theory] - [WithSolidFilledImages(1, 1, "Red", PixelTypes.Rgba32)] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public void GetPixelRowSpan(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] + [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] + public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + + using Image image = provider.GetImage(); + for (int y = 0; y < image.Height; y++) + { // Act: - for (int y = 0; y < image.Height; y++) - { - Span rowMemory = image.GetPixelRowSpan(y); - rowMemory.CopyTo(targetBuffer.AsSpan(image.Width * y)); - } + Memory rowMemory = image.GetPixelRowMemory(y); + Span span = rowMemory.Span; // Assert: - using (Image image1 = provider.GetImage()) + for (int x = 0; x < image.Width; x++) { - // We are using a copy of the original image for assertion - image1.ComparePixelBufferTo(targetBuffer); + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]); } } } - #pragma warning disable 0618 + [Theory] + [WithBasicTestPatternImages(16, 16, PixelTypes.Rgba32)] + public void GetPixelRowMemory_DestructiveMutate_ShouldInvalidateMemory(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + Memory memory3 = image.GetPixelRowMemory(3); + Memory memory10 = image.GetPixelRowMemory(10); + + image.Mutate(c => c.Resize(8, 8)); + + Assert.ThrowsAny(() => _ = memory3.Span); + Assert.ThrowsAny(() => _ = memory10.Span); + } [Theory] - [WithTestPatternImages(131, 127, PixelTypes.Rgba32 | PixelTypes.Bgr24)] - public unsafe void DangerousGetPinnableReference_CopyToBuffer(TestImageProvider provider) - where TPixel : struct, IPixel + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + [WithBlankImages(100, 111, PixelTypes.Rgba32)] + [WithBlankImages(400, 600, PixelTypes.Rgba32)] + public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) - { - var targetBuffer = new TPixel[image.Width * image.Height]; + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - 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); - } + using Image image = provider.GetImage(); - image.ComparePixelBufferTo(targetBuffer); - } + Memory memory = image.GetPixelRowMemory(image.Height - 1); + Span span = image.GetPixelRowSpan(image.Height - 1); + + Assert.True(span == memory.Span); } } } diff --git a/tests/ImageSharp.Tests/AssemblyInfo.cs b/tests/ImageSharp.Tests/AssemblyInfo.cs deleted file mode 100644 index 944fbe101..000000000 --- a/tests/ImageSharp.Tests/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; - -[assembly:InternalsVisibleTo("ImageSharp.Tests.ProfilingSandbox")] diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index fbd1c73f1..c658227ae 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index 6d9b34ee9..c689431f3 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -3,9 +3,7 @@ using System; using System.Linq; - using SixLabors.ImageSharp.PixelFormats; - using Xunit; namespace SixLabors.ImageSharp.Tests @@ -15,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void WithAlpha() { - Color c1 = Color.FromRgba(111, 222, 55, 255); + var c1 = Color.FromRgba(111, 222, 55, 255); Color c2 = c1.WithAlpha(0.5f); var expected = new Rgba32(111, 222, 55, 128); @@ -56,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests public void ToHex() { string expected = "ABCD1234"; - Color color = Color.FromHex(expected); + var color = Color.ParseHex(expected); string actual = color.ToHex(); Assert.Equal(expected, actual); @@ -66,14 +64,22 @@ namespace SixLabors.ImageSharp.Tests public void WebSafePalette_IsCorrect() { Rgba32[] actualPalette = Color.WebSafePalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(ReferencePalette.WebSafeColors, actualPalette); + + for (int i = 0; i < ReferencePalette.WebSafeColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WebSafeColors[i], actualPalette[i]); + } } [Fact] public void WernerPalette_IsCorrect() { Rgba32[] actualPalette = Color.WernerPalette.ToArray().Select(c => (Rgba32)c).ToArray(); - Assert.Equal(ReferencePalette.WernerColors, actualPalette); + + for (int i = 0; i < ReferencePalette.WernerColors.Length; i++) + { + Assert.Equal((Rgba32)ReferencePalette.WernerColors[i], actualPalette[i]); + } } public class FromHex @@ -81,28 +87,134 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ShortHex() { - Assert.Equal(new Rgb24(255, 255, 255), (Rgb24) Color.FromHex("#fff")); - Assert.Equal(new Rgb24(255, 255, 255), (Rgb24) Color.FromHex("fff")); - Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32) Color.FromHex("000f")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("#fff")); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)Color.ParseHex("fff")); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)Color.ParseHex("000f")); + } + + [Fact] + public void TryShortHex() + { + Assert.True(Color.TryParseHex("#fff", out Color actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("fff", out actual)); + Assert.Equal(new Rgb24(255, 255, 255), (Rgb24)actual); + + Assert.True(Color.TryParseHex("000f", out actual)); + Assert.Equal(new Rgba32(0, 0, 0, 255), (Rgba32)actual); } [Fact] public void LeadingPoundIsOptional() { - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24) Color.FromHex("#008080")); - Assert.Equal(new Rgb24(0, 128, 128), (Rgb24) Color.FromHex("008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("#008080")); + Assert.Equal(new Rgb24(0, 128, 128), (Rgb24)Color.ParseHex("008080")); } [Fact] public void ThrowsOnEmpty() { - Assert.Throws(() => Color.FromHex("")); + Assert.Throws(() => Color.ParseHex(string.Empty)); + } + + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.ParseHex("!")); } [Fact] public void ThrowsOnNull() { - Assert.Throws(() => Color.FromHex(null)); + Assert.Throws(() => Color.ParseHex(null)); + } + + [Fact] + public void FalseOnEmpty() + { + Assert.False(Color.TryParseHex(string.Empty, out Color _)); + } + + [Fact] + public void FalseOnInvalid() + { + Assert.False(Color.TryParseHex("!", out Color _)); + } + + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParseHex(null, out Color _)); + } + } + + public class FromString + { + [Fact] + public void ColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + Assert.Equal(expected, (Rgba32)Color.Parse(name)); + Assert.Equal(expected, (Rgba32)Color.Parse(name.ToLowerInvariant())); + Assert.Equal(expected, (Rgba32)Color.Parse(expected.ToHex())); + } + } + + [Fact] + public void TryColorNames() + { + foreach (string name in ReferencePalette.ColorNames.Keys) + { + Rgba32 expected = ReferencePalette.ColorNames[name]; + + Assert.True(Color.TryParse(name, out Color actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(name.ToLowerInvariant(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + + Assert.True(Color.TryParse(expected.ToHex(), out actual)); + Assert.Equal(expected, (Rgba32)actual); + } + } + + [Fact] + public void ThrowsOnEmpty() + { + Assert.Throws(() => Color.Parse(string.Empty)); + } + + [Fact] + public void ThrowsOnInvalid() + { + Assert.Throws(() => Color.Parse("!")); + } + + [Fact] + public void ThrowsOnNull() + { + Assert.Throws(() => Color.Parse(null)); + } + + [Fact] + public void FalseOnEmpty() + { + Assert.False(Color.TryParse(string.Empty, out Color _)); + } + + [Fact] + public void FalseOnInvalid() + { + Assert.False(Color.TryParse("!", out Color _)); + } + + [Fact] + public void FalseOnNull() + { + Assert.False(Color.TryParse(null, out Color _)); } } } diff --git a/tests/ImageSharp.Tests/Color/ReferencePalette.cs b/tests/ImageSharp.Tests/Color/ReferencePalette.cs index 3c6e382c5..d8403e27e 100644 --- a/tests/ImageSharp.Tests/Color/ReferencePalette.cs +++ b/tests/ImageSharp.Tests/Color/ReferencePalette.cs @@ -1,7 +1,8 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. -using SixLabors.ImageSharp.PixelFormats; +using System; +using System.Collections.Generic; namespace SixLabors.ImageSharp.Tests { @@ -10,268 +11,422 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets a collection of named, web safe, colors as defined in the CSS Color Module Level 4. /// - public static readonly Rgba32[] WebSafeColors = + public static readonly Color[] WebSafeColors = { - Rgba32.AliceBlue, - Rgba32.AntiqueWhite, - Rgba32.Aqua, - Rgba32.Aquamarine, - Rgba32.Azure, - Rgba32.Beige, - Rgba32.Bisque, - Rgba32.Black, - Rgba32.BlanchedAlmond, - Rgba32.Blue, - Rgba32.BlueViolet, - Rgba32.Brown, - Rgba32.BurlyWood, - Rgba32.CadetBlue, - Rgba32.Chartreuse, - Rgba32.Chocolate, - Rgba32.Coral, - Rgba32.CornflowerBlue, - Rgba32.Cornsilk, - Rgba32.Crimson, - Rgba32.Cyan, - Rgba32.DarkBlue, - Rgba32.DarkCyan, - Rgba32.DarkGoldenrod, - Rgba32.DarkGray, - Rgba32.DarkGreen, - Rgba32.DarkKhaki, - Rgba32.DarkMagenta, - Rgba32.DarkOliveGreen, - Rgba32.DarkOrange, - Rgba32.DarkOrchid, - Rgba32.DarkRed, - Rgba32.DarkSalmon, - Rgba32.DarkSeaGreen, - Rgba32.DarkSlateBlue, - Rgba32.DarkSlateGray, - Rgba32.DarkTurquoise, - Rgba32.DarkViolet, - Rgba32.DeepPink, - Rgba32.DeepSkyBlue, - Rgba32.DimGray, - Rgba32.DodgerBlue, - Rgba32.Firebrick, - Rgba32.FloralWhite, - Rgba32.ForestGreen, - Rgba32.Fuchsia, - Rgba32.Gainsboro, - Rgba32.GhostWhite, - Rgba32.Gold, - Rgba32.Goldenrod, - Rgba32.Gray, - Rgba32.Green, - Rgba32.GreenYellow, - Rgba32.Honeydew, - Rgba32.HotPink, - Rgba32.IndianRed, - Rgba32.Indigo, - Rgba32.Ivory, - Rgba32.Khaki, - Rgba32.Lavender, - Rgba32.LavenderBlush, - Rgba32.LawnGreen, - Rgba32.LemonChiffon, - Rgba32.LightBlue, - Rgba32.LightCoral, - Rgba32.LightCyan, - Rgba32.LightGoldenrodYellow, - Rgba32.LightGray, - Rgba32.LightGreen, - Rgba32.LightPink, - Rgba32.LightSalmon, - Rgba32.LightSeaGreen, - Rgba32.LightSkyBlue, - Rgba32.LightSlateGray, - Rgba32.LightSteelBlue, - Rgba32.LightYellow, - Rgba32.Lime, - Rgba32.LimeGreen, - Rgba32.Linen, - Rgba32.Magenta, - Rgba32.Maroon, - Rgba32.MediumAquamarine, - Rgba32.MediumBlue, - Rgba32.MediumOrchid, - Rgba32.MediumPurple, - Rgba32.MediumSeaGreen, - Rgba32.MediumSlateBlue, - Rgba32.MediumSpringGreen, - Rgba32.MediumTurquoise, - Rgba32.MediumVioletRed, - Rgba32.MidnightBlue, - Rgba32.MintCream, - Rgba32.MistyRose, - Rgba32.Moccasin, - Rgba32.NavajoWhite, - Rgba32.Navy, - Rgba32.OldLace, - Rgba32.Olive, - Rgba32.OliveDrab, - Rgba32.Orange, - Rgba32.OrangeRed, - Rgba32.Orchid, - Rgba32.PaleGoldenrod, - Rgba32.PaleGreen, - Rgba32.PaleTurquoise, - Rgba32.PaleVioletRed, - Rgba32.PapayaWhip, - Rgba32.PeachPuff, - Rgba32.Peru, - Rgba32.Pink, - Rgba32.Plum, - Rgba32.PowderBlue, - Rgba32.Purple, - Rgba32.RebeccaPurple, - Rgba32.Red, - Rgba32.RosyBrown, - Rgba32.RoyalBlue, - Rgba32.SaddleBrown, - Rgba32.Salmon, - Rgba32.SandyBrown, - Rgba32.SeaGreen, - Rgba32.SeaShell, - Rgba32.Sienna, - Rgba32.Silver, - Rgba32.SkyBlue, - Rgba32.SlateBlue, - Rgba32.SlateGray, - Rgba32.Snow, - Rgba32.SpringGreen, - Rgba32.SteelBlue, - Rgba32.Tan, - Rgba32.Teal, - Rgba32.Thistle, - Rgba32.Tomato, - Rgba32.Transparent, - Rgba32.Turquoise, - Rgba32.Violet, - Rgba32.Wheat, - Rgba32.White, - Rgba32.WhiteSmoke, - Rgba32.Yellow, - Rgba32.YellowGreen + Color.AliceBlue, + Color.AntiqueWhite, + Color.Aqua, + Color.Aquamarine, + Color.Azure, + Color.Beige, + Color.Bisque, + Color.Black, + Color.BlanchedAlmond, + Color.Blue, + Color.BlueViolet, + Color.Brown, + Color.BurlyWood, + Color.CadetBlue, + Color.Chartreuse, + Color.Chocolate, + Color.Coral, + Color.CornflowerBlue, + Color.Cornsilk, + Color.Crimson, + Color.Cyan, + Color.DarkBlue, + Color.DarkCyan, + Color.DarkGoldenrod, + Color.DarkGray, + Color.DarkGreen, + Color.DarkKhaki, + Color.DarkMagenta, + Color.DarkOliveGreen, + Color.DarkOrange, + Color.DarkOrchid, + Color.DarkRed, + Color.DarkSalmon, + Color.DarkSeaGreen, + Color.DarkSlateBlue, + Color.DarkSlateGray, + Color.DarkTurquoise, + Color.DarkViolet, + Color.DeepPink, + Color.DeepSkyBlue, + Color.DimGray, + Color.DodgerBlue, + Color.Firebrick, + Color.FloralWhite, + Color.ForestGreen, + Color.Fuchsia, + Color.Gainsboro, + Color.GhostWhite, + Color.Gold, + Color.Goldenrod, + Color.Gray, + Color.Green, + Color.GreenYellow, + Color.Honeydew, + Color.HotPink, + Color.IndianRed, + Color.Indigo, + Color.Ivory, + Color.Khaki, + Color.Lavender, + Color.LavenderBlush, + Color.LawnGreen, + Color.LemonChiffon, + Color.LightBlue, + Color.LightCoral, + Color.LightCyan, + Color.LightGoldenrodYellow, + Color.LightGray, + Color.LightGreen, + Color.LightPink, + Color.LightSalmon, + Color.LightSeaGreen, + Color.LightSkyBlue, + Color.LightSlateGray, + Color.LightSteelBlue, + Color.LightYellow, + Color.Lime, + Color.LimeGreen, + Color.Linen, + Color.Magenta, + Color.Maroon, + Color.MediumAquamarine, + Color.MediumBlue, + Color.MediumOrchid, + Color.MediumPurple, + Color.MediumSeaGreen, + Color.MediumSlateBlue, + Color.MediumSpringGreen, + Color.MediumTurquoise, + Color.MediumVioletRed, + Color.MidnightBlue, + Color.MintCream, + Color.MistyRose, + Color.Moccasin, + Color.NavajoWhite, + Color.Navy, + Color.OldLace, + Color.Olive, + Color.OliveDrab, + Color.Orange, + Color.OrangeRed, + Color.Orchid, + Color.PaleGoldenrod, + Color.PaleGreen, + Color.PaleTurquoise, + Color.PaleVioletRed, + Color.PapayaWhip, + Color.PeachPuff, + Color.Peru, + Color.Pink, + Color.Plum, + Color.PowderBlue, + Color.Purple, + Color.RebeccaPurple, + Color.Red, + Color.RosyBrown, + Color.RoyalBlue, + Color.SaddleBrown, + Color.Salmon, + Color.SandyBrown, + Color.SeaGreen, + Color.SeaShell, + Color.Sienna, + Color.Silver, + Color.SkyBlue, + Color.SlateBlue, + Color.SlateGray, + Color.Snow, + Color.SpringGreen, + Color.SteelBlue, + Color.Tan, + Color.Teal, + Color.Thistle, + Color.Tomato, + Color.Transparent, + Color.Turquoise, + Color.Violet, + Color.Wheat, + Color.White, + Color.WhiteSmoke, + Color.Yellow, + Color.YellowGreen }; /// /// Gets a collection of colors as defined in the original second edition of Werner’s Nomenclature of Colours 1821. /// The hex codes were collected and defined by Nicholas Rougeux /// - public static readonly Rgba32[] WernerColors = + public static readonly Color[] WernerColors = { - Rgba32.FromHex("#f1e9cd"), - Rgba32.FromHex("#f2e7cf"), - Rgba32.FromHex("#ece6d0"), - Rgba32.FromHex("#f2eacc"), - Rgba32.FromHex("#f3e9ca"), - Rgba32.FromHex("#f2ebcd"), - Rgba32.FromHex("#e6e1c9"), - Rgba32.FromHex("#e2ddc6"), - Rgba32.FromHex("#cbc8b7"), - Rgba32.FromHex("#bfbbb0"), - Rgba32.FromHex("#bebeb3"), - Rgba32.FromHex("#b7b5ac"), - Rgba32.FromHex("#bab191"), - Rgba32.FromHex("#9c9d9a"), - Rgba32.FromHex("#8a8d84"), - Rgba32.FromHex("#5b5c61"), - Rgba32.FromHex("#555152"), - Rgba32.FromHex("#413f44"), - Rgba32.FromHex("#454445"), - Rgba32.FromHex("#423937"), - Rgba32.FromHex("#433635"), - Rgba32.FromHex("#252024"), - Rgba32.FromHex("#241f20"), - Rgba32.FromHex("#281f3f"), - Rgba32.FromHex("#1c1949"), - Rgba32.FromHex("#4f638d"), - Rgba32.FromHex("#383867"), - Rgba32.FromHex("#5c6b8f"), - Rgba32.FromHex("#657abb"), - Rgba32.FromHex("#6f88af"), - Rgba32.FromHex("#7994b5"), - Rgba32.FromHex("#6fb5a8"), - Rgba32.FromHex("#719ba2"), - Rgba32.FromHex("#8aa1a6"), - Rgba32.FromHex("#d0d5d3"), - Rgba32.FromHex("#8590ae"), - Rgba32.FromHex("#3a2f52"), - Rgba32.FromHex("#39334a"), - Rgba32.FromHex("#6c6d94"), - Rgba32.FromHex("#584c77"), - Rgba32.FromHex("#533552"), - Rgba32.FromHex("#463759"), - Rgba32.FromHex("#bfbac0"), - Rgba32.FromHex("#77747f"), - Rgba32.FromHex("#4a475c"), - Rgba32.FromHex("#b8bfaf"), - Rgba32.FromHex("#b2b599"), - Rgba32.FromHex("#979c84"), - Rgba32.FromHex("#5d6161"), - Rgba32.FromHex("#61ac86"), - Rgba32.FromHex("#a4b6a7"), - Rgba32.FromHex("#adba98"), - Rgba32.FromHex("#93b778"), - Rgba32.FromHex("#7d8c55"), - Rgba32.FromHex("#33431e"), - Rgba32.FromHex("#7c8635"), - Rgba32.FromHex("#8e9849"), - Rgba32.FromHex("#c2c190"), - Rgba32.FromHex("#67765b"), - Rgba32.FromHex("#ab924b"), - Rgba32.FromHex("#c8c76f"), - Rgba32.FromHex("#ccc050"), - Rgba32.FromHex("#ebdd99"), - Rgba32.FromHex("#ab9649"), - Rgba32.FromHex("#dbc364"), - Rgba32.FromHex("#e6d058"), - Rgba32.FromHex("#ead665"), - Rgba32.FromHex("#d09b2c"), - Rgba32.FromHex("#a36629"), - Rgba32.FromHex("#a77d35"), - Rgba32.FromHex("#f0d696"), - Rgba32.FromHex("#d7c485"), - Rgba32.FromHex("#f1d28c"), - Rgba32.FromHex("#efcc83"), - Rgba32.FromHex("#f3daa7"), - Rgba32.FromHex("#dfa837"), - Rgba32.FromHex("#ebbc71"), - Rgba32.FromHex("#d17c3f"), - Rgba32.FromHex("#92462f"), - Rgba32.FromHex("#be7249"), - Rgba32.FromHex("#bb603c"), - Rgba32.FromHex("#c76b4a"), - Rgba32.FromHex("#a75536"), - Rgba32.FromHex("#b63e36"), - Rgba32.FromHex("#b5493a"), - Rgba32.FromHex("#cd6d57"), - Rgba32.FromHex("#711518"), - Rgba32.FromHex("#e9c49d"), - Rgba32.FromHex("#eedac3"), - Rgba32.FromHex("#eecfbf"), - Rgba32.FromHex("#ce536b"), - Rgba32.FromHex("#b74a70"), - Rgba32.FromHex("#b7757c"), - Rgba32.FromHex("#612741"), - Rgba32.FromHex("#7a4848"), - Rgba32.FromHex("#3f3033"), - Rgba32.FromHex("#8d746f"), - Rgba32.FromHex("#4d3635"), - Rgba32.FromHex("#6e3b31"), - Rgba32.FromHex("#864735"), - Rgba32.FromHex("#553d3a"), - Rgba32.FromHex("#613936"), - Rgba32.FromHex("#7a4b3a"), - Rgba32.FromHex("#946943"), - Rgba32.FromHex("#c39e6d"), - Rgba32.FromHex("#513e32"), - Rgba32.FromHex("#8b7859"), - Rgba32.FromHex("#9b856b"), - Rgba32.FromHex("#766051"), - Rgba32.FromHex("#453b32") + Color.ParseHex("#f1e9cd"), + Color.ParseHex("#f2e7cf"), + Color.ParseHex("#ece6d0"), + Color.ParseHex("#f2eacc"), + Color.ParseHex("#f3e9ca"), + Color.ParseHex("#f2ebcd"), + Color.ParseHex("#e6e1c9"), + Color.ParseHex("#e2ddc6"), + Color.ParseHex("#cbc8b7"), + Color.ParseHex("#bfbbb0"), + Color.ParseHex("#bebeb3"), + Color.ParseHex("#b7b5ac"), + Color.ParseHex("#bab191"), + Color.ParseHex("#9c9d9a"), + Color.ParseHex("#8a8d84"), + Color.ParseHex("#5b5c61"), + Color.ParseHex("#555152"), + Color.ParseHex("#413f44"), + Color.ParseHex("#454445"), + Color.ParseHex("#423937"), + Color.ParseHex("#433635"), + Color.ParseHex("#252024"), + Color.ParseHex("#241f20"), + Color.ParseHex("#281f3f"), + Color.ParseHex("#1c1949"), + Color.ParseHex("#4f638d"), + Color.ParseHex("#383867"), + Color.ParseHex("#5c6b8f"), + Color.ParseHex("#657abb"), + Color.ParseHex("#6f88af"), + Color.ParseHex("#7994b5"), + Color.ParseHex("#6fb5a8"), + Color.ParseHex("#719ba2"), + Color.ParseHex("#8aa1a6"), + Color.ParseHex("#d0d5d3"), + Color.ParseHex("#8590ae"), + Color.ParseHex("#3a2f52"), + Color.ParseHex("#39334a"), + Color.ParseHex("#6c6d94"), + Color.ParseHex("#584c77"), + Color.ParseHex("#533552"), + Color.ParseHex("#463759"), + Color.ParseHex("#bfbac0"), + Color.ParseHex("#77747f"), + Color.ParseHex("#4a475c"), + Color.ParseHex("#b8bfaf"), + Color.ParseHex("#b2b599"), + Color.ParseHex("#979c84"), + Color.ParseHex("#5d6161"), + Color.ParseHex("#61ac86"), + Color.ParseHex("#a4b6a7"), + Color.ParseHex("#adba98"), + Color.ParseHex("#93b778"), + Color.ParseHex("#7d8c55"), + Color.ParseHex("#33431e"), + Color.ParseHex("#7c8635"), + Color.ParseHex("#8e9849"), + Color.ParseHex("#c2c190"), + Color.ParseHex("#67765b"), + Color.ParseHex("#ab924b"), + Color.ParseHex("#c8c76f"), + Color.ParseHex("#ccc050"), + Color.ParseHex("#ebdd99"), + Color.ParseHex("#ab9649"), + Color.ParseHex("#dbc364"), + Color.ParseHex("#e6d058"), + Color.ParseHex("#ead665"), + Color.ParseHex("#d09b2c"), + Color.ParseHex("#a36629"), + Color.ParseHex("#a77d35"), + Color.ParseHex("#f0d696"), + Color.ParseHex("#d7c485"), + Color.ParseHex("#f1d28c"), + Color.ParseHex("#efcc83"), + Color.ParseHex("#f3daa7"), + Color.ParseHex("#dfa837"), + Color.ParseHex("#ebbc71"), + Color.ParseHex("#d17c3f"), + Color.ParseHex("#92462f"), + Color.ParseHex("#be7249"), + Color.ParseHex("#bb603c"), + Color.ParseHex("#c76b4a"), + Color.ParseHex("#a75536"), + Color.ParseHex("#b63e36"), + Color.ParseHex("#b5493a"), + Color.ParseHex("#cd6d57"), + Color.ParseHex("#711518"), + Color.ParseHex("#e9c49d"), + Color.ParseHex("#eedac3"), + Color.ParseHex("#eecfbf"), + Color.ParseHex("#ce536b"), + Color.ParseHex("#b74a70"), + Color.ParseHex("#b7757c"), + Color.ParseHex("#612741"), + Color.ParseHex("#7a4848"), + Color.ParseHex("#3f3033"), + Color.ParseHex("#8d746f"), + Color.ParseHex("#4d3635"), + Color.ParseHex("#6e3b31"), + Color.ParseHex("#864735"), + Color.ParseHex("#553d3a"), + Color.ParseHex("#613936"), + Color.ParseHex("#7a4b3a"), + Color.ParseHex("#946943"), + Color.ParseHex("#c39e6d"), + Color.ParseHex("#513e32"), + Color.ParseHex("#8b7859"), + Color.ParseHex("#9b856b"), + Color.ParseHex("#766051"), + Color.ParseHex("#453b32") }; + + public static readonly Dictionary ColorNames = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { nameof(Color.AliceBlue), Color.AliceBlue }, + { nameof(Color.AntiqueWhite), Color.AntiqueWhite }, + { nameof(Color.Aqua), Color.Aqua }, + { nameof(Color.Aquamarine), Color.Aquamarine }, + { nameof(Color.Azure), Color.Azure }, + { nameof(Color.Beige), Color.Beige }, + { nameof(Color.Bisque), Color.Bisque }, + { nameof(Color.Black), Color.Black }, + { nameof(Color.BlanchedAlmond), Color.BlanchedAlmond }, + { nameof(Color.Blue), Color.Blue }, + { nameof(Color.BlueViolet), Color.BlueViolet }, + { nameof(Color.Brown), Color.Brown }, + { nameof(Color.BurlyWood), Color.BurlyWood }, + { nameof(Color.CadetBlue), Color.CadetBlue }, + { nameof(Color.Chartreuse), Color.Chartreuse }, + { nameof(Color.Chocolate), Color.Chocolate }, + { nameof(Color.Coral), Color.Coral }, + { nameof(Color.CornflowerBlue), Color.CornflowerBlue }, + { nameof(Color.Cornsilk), Color.Cornsilk }, + { nameof(Color.Crimson), Color.Crimson }, + { nameof(Color.Cyan), Color.Cyan }, + { nameof(Color.DarkBlue), Color.DarkBlue }, + { nameof(Color.DarkCyan), Color.DarkCyan }, + { nameof(Color.DarkGoldenrod), Color.DarkGoldenrod }, + { nameof(Color.DarkGray), Color.DarkGray }, + { nameof(Color.DarkGreen), Color.DarkGreen }, + { nameof(Color.DarkGrey), Color.DarkGrey }, + { nameof(Color.DarkKhaki), Color.DarkKhaki }, + { nameof(Color.DarkMagenta), Color.DarkMagenta }, + { nameof(Color.DarkOliveGreen), Color.DarkOliveGreen }, + { nameof(Color.DarkOrange), Color.DarkOrange }, + { nameof(Color.DarkOrchid), Color.DarkOrchid }, + { nameof(Color.DarkRed), Color.DarkRed }, + { nameof(Color.DarkSalmon), Color.DarkSalmon }, + { nameof(Color.DarkSeaGreen), Color.DarkSeaGreen }, + { nameof(Color.DarkSlateBlue), Color.DarkSlateBlue }, + { nameof(Color.DarkSlateGray), Color.DarkSlateGray }, + { nameof(Color.DarkSlateGrey), Color.DarkSlateGrey }, + { nameof(Color.DarkTurquoise), Color.DarkTurquoise }, + { nameof(Color.DarkViolet), Color.DarkViolet }, + { nameof(Color.DeepPink), Color.DeepPink }, + { nameof(Color.DeepSkyBlue), Color.DeepSkyBlue }, + { nameof(Color.DimGray), Color.DimGray }, + { nameof(Color.DimGrey), Color.DimGrey }, + { nameof(Color.DodgerBlue), Color.DodgerBlue }, + { nameof(Color.Firebrick), Color.Firebrick }, + { nameof(Color.FloralWhite), Color.FloralWhite }, + { nameof(Color.ForestGreen), Color.ForestGreen }, + { nameof(Color.Fuchsia), Color.Fuchsia }, + { nameof(Color.Gainsboro), Color.Gainsboro }, + { nameof(Color.GhostWhite), Color.GhostWhite }, + { nameof(Color.Gold), Color.Gold }, + { nameof(Color.Goldenrod), Color.Goldenrod }, + { nameof(Color.Gray), Color.Gray }, + { nameof(Color.Green), Color.Green }, + { nameof(Color.GreenYellow), Color.GreenYellow }, + { nameof(Color.Grey), Color.Grey }, + { nameof(Color.Honeydew), Color.Honeydew }, + { nameof(Color.HotPink), Color.HotPink }, + { nameof(Color.IndianRed), Color.IndianRed }, + { nameof(Color.Indigo), Color.Indigo }, + { nameof(Color.Ivory), Color.Ivory }, + { nameof(Color.Khaki), Color.Khaki }, + { nameof(Color.Lavender), Color.Lavender }, + { nameof(Color.LavenderBlush), Color.LavenderBlush }, + { nameof(Color.LawnGreen), Color.LawnGreen }, + { nameof(Color.LemonChiffon), Color.LemonChiffon }, + { nameof(Color.LightBlue), Color.LightBlue }, + { nameof(Color.LightCoral), Color.LightCoral }, + { nameof(Color.LightCyan), Color.LightCyan }, + { nameof(Color.LightGoldenrodYellow), Color.LightGoldenrodYellow }, + { nameof(Color.LightGray), Color.LightGray }, + { nameof(Color.LightGreen), Color.LightGreen }, + { nameof(Color.LightGrey), Color.LightGrey }, + { nameof(Color.LightPink), Color.LightPink }, + { nameof(Color.LightSalmon), Color.LightSalmon }, + { nameof(Color.LightSeaGreen), Color.LightSeaGreen }, + { nameof(Color.LightSkyBlue), Color.LightSkyBlue }, + { nameof(Color.LightSlateGray), Color.LightSlateGray }, + { nameof(Color.LightSlateGrey), Color.LightSlateGrey }, + { nameof(Color.LightSteelBlue), Color.LightSteelBlue }, + { nameof(Color.LightYellow), Color.LightYellow }, + { nameof(Color.Lime), Color.Lime }, + { nameof(Color.LimeGreen), Color.LimeGreen }, + { nameof(Color.Linen), Color.Linen }, + { nameof(Color.Magenta), Color.Magenta }, + { nameof(Color.Maroon), Color.Maroon }, + { nameof(Color.MediumAquamarine), Color.MediumAquamarine }, + { nameof(Color.MediumBlue), Color.MediumBlue }, + { nameof(Color.MediumOrchid), Color.MediumOrchid }, + { nameof(Color.MediumPurple), Color.MediumPurple }, + { nameof(Color.MediumSeaGreen), Color.MediumSeaGreen }, + { nameof(Color.MediumSlateBlue), Color.MediumSlateBlue }, + { nameof(Color.MediumSpringGreen), Color.MediumSpringGreen }, + { nameof(Color.MediumTurquoise), Color.MediumTurquoise }, + { nameof(Color.MediumVioletRed), Color.MediumVioletRed }, + { nameof(Color.MidnightBlue), Color.MidnightBlue }, + { nameof(Color.MintCream), Color.MintCream }, + { nameof(Color.MistyRose), Color.MistyRose }, + { nameof(Color.Moccasin), Color.Moccasin }, + { nameof(Color.NavajoWhite), Color.NavajoWhite }, + { nameof(Color.Navy), Color.Navy }, + { nameof(Color.OldLace), Color.OldLace }, + { nameof(Color.Olive), Color.Olive }, + { nameof(Color.OliveDrab), Color.OliveDrab }, + { nameof(Color.Orange), Color.Orange }, + { nameof(Color.OrangeRed), Color.OrangeRed }, + { nameof(Color.Orchid), Color.Orchid }, + { nameof(Color.PaleGoldenrod), Color.PaleGoldenrod }, + { nameof(Color.PaleGreen), Color.PaleGreen }, + { nameof(Color.PaleTurquoise), Color.PaleTurquoise }, + { nameof(Color.PaleVioletRed), Color.PaleVioletRed }, + { nameof(Color.PapayaWhip), Color.PapayaWhip }, + { nameof(Color.PeachPuff), Color.PeachPuff }, + { nameof(Color.Peru), Color.Peru }, + { nameof(Color.Pink), Color.Pink }, + { nameof(Color.Plum), Color.Plum }, + { nameof(Color.PowderBlue), Color.PowderBlue }, + { nameof(Color.Purple), Color.Purple }, + { nameof(Color.RebeccaPurple), Color.RebeccaPurple }, + { nameof(Color.Red), Color.Red }, + { nameof(Color.RosyBrown), Color.RosyBrown }, + { nameof(Color.RoyalBlue), Color.RoyalBlue }, + { nameof(Color.SaddleBrown), Color.SaddleBrown }, + { nameof(Color.Salmon), Color.Salmon }, + { nameof(Color.SandyBrown), Color.SandyBrown }, + { nameof(Color.SeaGreen), Color.SeaGreen }, + { nameof(Color.SeaShell), Color.SeaShell }, + { nameof(Color.Sienna), Color.Sienna }, + { nameof(Color.Silver), Color.Silver }, + { nameof(Color.SkyBlue), Color.SkyBlue }, + { nameof(Color.SlateBlue), Color.SlateBlue }, + { nameof(Color.SlateGray), Color.SlateGray }, + { nameof(Color.SlateGrey), Color.SlateGrey }, + { nameof(Color.Snow), Color.Snow }, + { nameof(Color.SpringGreen), Color.SpringGreen }, + { nameof(Color.SteelBlue), Color.SteelBlue }, + { nameof(Color.Tan), Color.Tan }, + { nameof(Color.Teal), Color.Teal }, + { nameof(Color.Thistle), Color.Thistle }, + { nameof(Color.Tomato), Color.Tomato }, + { nameof(Color.Transparent), Color.Transparent }, + { nameof(Color.Turquoise), Color.Turquoise }, + { nameof(Color.Violet), Color.Violet }, + { nameof(Color.Wheat), Color.Wheat }, + { nameof(Color.White), Color.White }, + { nameof(Color.WhiteSmoke), Color.WhiteSmoke }, + { nameof(Color.Yellow), Color.Yellow }, + { nameof(Color.YellowGreen), Color.YellowGreen } + }; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs index dbc07b916..4bba0ab03 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieLabTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -32,15 +32,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new CieLab(Vector3.One); Assert.True(default(CieLab) == default(CieLab)); - Assert.True(default(CieLab) != new CieLab(1, 0, 1)); - Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.True(new CieLab(1, 0, 1) != default(CieLab)); + Assert.False(new CieLab(1, 0, 1) == default(CieLab)); Assert.Equal(default(CieLab), default(CieLab)); Assert.Equal(new CieLab(1, 0, 1), new CieLab(1, 0, 1)); Assert.Equal(new CieLab(Vector3.One), new CieLab(Vector3.One)); Assert.False(x.Equals(y)); - Assert.False(default(CieLab) == new CieLab(1, 0, 1)); + Assert.False(new CieLab(1, 0, 1) == default(CieLab)); Assert.False(x.Equals((object)y)); Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs index 42ace9dbe..4811a66d4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/CieXyChromaticityCoordinatesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.ColorSpaces; @@ -29,15 +29,15 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new CieXyChromaticityCoordinates(1, 1); Assert.True(default(CieXyChromaticityCoordinates) == default(CieXyChromaticityCoordinates)); - Assert.True(default(CieXyChromaticityCoordinates) != new CieXyChromaticityCoordinates(1, 0)); - Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.True(new CieXyChromaticityCoordinates(1, 0) != default(CieXyChromaticityCoordinates)); + Assert.False(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); Assert.Equal(default(CieXyChromaticityCoordinates), default(CieXyChromaticityCoordinates)); Assert.Equal(new CieXyChromaticityCoordinates(1, 0), new CieXyChromaticityCoordinates(1, 0)); Assert.Equal(new CieXyChromaticityCoordinates(1, 1), new CieXyChromaticityCoordinates(1, 1)); Assert.False(x.Equals(y)); - Assert.False(default(CieXyChromaticityCoordinates) == new CieXyChromaticityCoordinates(1, 0)); + Assert.False(new CieXyChromaticityCoordinates(1, 0) == default(CieXyChromaticityCoordinates)); Assert.False(x.Equals((object)y)); Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs index 7bf84dd0a..feb3b38f0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/ApproximateColorspaceComparer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -30,13 +30,13 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion IEqualityComparer, IEqualityComparer { - private readonly float Epsilon; + private readonly float epsilon; /// /// Initializes a new instance of the class. /// /// The comparison error difference epsilon to use. - public ApproximateColorSpaceComparer(float epsilon = 1F) => this.Epsilon = epsilon; + public ApproximateColorSpaceComparer(float epsilon = 1F) => this.epsilon = epsilon; /// public bool Equals(Rgb x, Rgb y) @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion private bool Equals(float x, float y) { float d = x - y; - return d >= -this.Epsilon && d <= this.Epsilon; + return d >= -this.epsilon && d <= this.epsilon; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs index a65f61883..c5af01788 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -69,11 +69,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs index 6829c62b5..e14d02faf 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLchuvAndCieLuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -87,11 +87,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs index 3b41204f7..5566ce1b4 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndCieXyyConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs index bfc0d2ecf..f130bb947 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs index f11b17fff..9e0af62ee 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs index de2329c2e..68fe54b51 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs index 3a1bd10c4..7c3e66f52 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs index f3881f10f..d42322336 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs index 644f4577b..8223ffdbc 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs index 41b9dba09..e300049df 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieLuvAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs index 5b36beaab..1c343afa2 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs index da7737875..9a3cb8b01 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs index 96d14c98a..9e4602475 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndHunterLabConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs index 033973094..71b41e6ca 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLinearRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs index fb0e06e6b..4737ba59f 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndLmsConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs index 5bbcd9087..1193ccaa1 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndRgbConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs index 1ee84ef2e..b1342c80c 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyyAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyy[5]; // Act - var actual = Converter.ToCieXyy(input); + CieXyy actual = Converter.ToCieXyy(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs index 77f0c6969..42f00c51e 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,17 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs index 24e134d73..f12361773 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndCieLchuvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,17 +63,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs index cd1c9f2c3..eda5db125 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHslConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -70,11 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs index 8112f6a19..47f780789 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndHsvConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs index 9ea890f10..d6d59ec07 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/CieXyzAndYCbCrConversionTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -64,17 +64,16 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion Span actualSpan = new CieXyz[5]; // Act - var actual = Converter.ToCieXyz(input); + CieXyz actual = Converter.ToCieXyz(input); Converter.Convert(inputSpan, actualSpan); // Assert Assert.Equal(expected, actual, ColorSpaceComparer); - for (int i = 0; i < actualSpan.Length; i++) { Assert.Equal(expected, actualSpan[i], ColorSpaceComparer); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs index 8b1fed84c..8f9fef5e9 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/RgbAndHslConversionTest.cs @@ -72,7 +72,6 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion var input = new Rgb(r, g, b); var expected = new Hsl(h, s, l); - Span inputSpan = new Rgb[5]; inputSpan.Fill(input); diff --git a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs index b1427f4d5..bd870b01a 100644 --- a/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/Conversion/VonKriesChromaticAdaptationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,8 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces.Conversion private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(.0001F); public static readonly TheoryData WhitePoints = new TheoryData { - {CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint}, - {CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint} + { CieLuv.DefaultWhitePoint, CieLab.DefaultWhitePoint }, + { CieLuv.DefaultWhitePoint, CieLuv.DefaultWhitePoint } }; [Theory] diff --git a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs index 95261e1d9..a657098f5 100644 --- a/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/HunterLabTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new HunterLab(Vector3.One); Assert.True(default(HunterLab) == default(HunterLab)); - Assert.True(default(HunterLab) != new HunterLab(1, 0, 1)); - Assert.False(default(HunterLab) == new HunterLab(1, 0, 1)); + Assert.True(new HunterLab(1, 0, 1) != default(HunterLab)); + Assert.False(new HunterLab(1, 0, 1) == default(HunterLab)); Assert.Equal(default(HunterLab), default(HunterLab)); Assert.Equal(new HunterLab(1, 0, 1), new HunterLab(1, 0, 1)); Assert.Equal(new HunterLab(Vector3.One), new HunterLab(Vector3.One)); @@ -42,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs index 1b0939dc5..f0c1471e0 100644 --- a/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/LmsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces const float l = 75F; const float m = -64F; const float s = 87F; - var Lms = new Lms(l, m, s); + var lms = new Lms(l, m, s); - Assert.Equal(l, Lms.L); - Assert.Equal(m, Lms.M); - Assert.Equal(s, Lms.S); + Assert.Equal(l, lms.L); + Assert.Equal(m, lms.M); + Assert.Equal(s, lms.S); } [Fact] @@ -32,8 +32,8 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces var y = new Lms(Vector3.One); Assert.True(default(Lms) == default(Lms)); - Assert.True(default(Lms) != new Lms(1, 0, 1)); - Assert.False(default(Lms) == new Lms(1, 0, 1)); + Assert.True(new Lms(1, 0, 1) != default(Lms)); + Assert.False(new Lms(1, 0, 1) == default(Lms)); Assert.Equal(default(Lms), default(Lms)); Assert.Equal(new Lms(1, 0, 1), new Lms(1, 0, 1)); Assert.Equal(new Lms(Vector3.One), new Lms(Vector3.One)); @@ -42,4 +42,4 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces Assert.False(x.GetHashCode().Equals(y.GetHashCode())); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs index 5249b709b..211b98abb 100644 --- a/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs +++ b/tests/ImageSharp.Tests/Colorspaces/StringRepresentationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,58 +9,56 @@ namespace SixLabors.ImageSharp.Tests.Colorspaces { public class StringRepresentationTests { - private static readonly Vector3 one = new Vector3(1); - private static readonly Vector3 zero = new Vector3(0); - private static readonly Vector3 random = new Vector3(42.4F, 94.5F, 83.4F); + private static readonly Vector3 One = new Vector3(1); + private static readonly Vector3 Zero = new Vector3(0); + private static readonly Vector3 Random = new Vector3(42.4F, 94.5F, 83.4F); public static readonly TheoryData TestData = new TheoryData { - { new CieLab(zero), "CieLab(0, 0, 0)" }, - { new CieLch(zero), "CieLch(0, 0, 0)" }, - { new CieLchuv(zero), "CieLchuv(0, 0, 0)" }, - { new CieLuv(zero), "CieLuv(0, 0, 0)" }, - { new CieXyz(zero), "CieXyz(0, 0, 0)" }, - { new CieXyy(zero), "CieXyy(0, 0, 0)" }, - { new HunterLab(zero), "HunterLab(0, 0, 0)" }, - { new Lms(zero), "Lms(0, 0, 0)" }, - { new LinearRgb(zero), "LinearRgb(0, 0, 0)" }, - { new Rgb(zero), "Rgb(0, 0, 0)" }, - { new Hsl(zero), "Hsl(0, 0, 0)" }, - { new Hsv(zero), "Hsv(0, 0, 0)" }, - { new YCbCr(zero), "YCbCr(0, 0, 0)" }, - - { new CieLab(one), "CieLab(1, 1, 1)" }, - { new CieLch(one), "CieLch(1, 1, 1)" }, - { new CieLchuv(one), "CieLchuv(1, 1, 1)" }, - { new CieLuv(one), "CieLuv(1, 1, 1)" }, - { new CieXyz(one), "CieXyz(1, 1, 1)" }, - { new CieXyy(one), "CieXyy(1, 1, 1)" }, - { new HunterLab(one), "HunterLab(1, 1, 1)" }, - { new Lms(one), "Lms(1, 1, 1)" }, - { new LinearRgb(one), "LinearRgb(1, 1, 1)" }, - { new Rgb(one), "Rgb(1, 1, 1)" }, - { new Hsl(one), "Hsl(1, 1, 1)" }, - { new Hsv(one), "Hsv(1, 1, 1)" }, - { new YCbCr(one), "YCbCr(1, 1, 1)" }, - { new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)"}, - - { new CieLab(random), "CieLab(42.4, 94.5, 83.4)" }, - { new CieLch(random), "CieLch(42.4, 94.5, 83.4)" }, - { new CieLchuv(random), "CieLchuv(42.4, 94.5, 83.4)" }, - { new CieLuv(random), "CieLuv(42.4, 94.5, 83.4)" }, - { new CieXyz(random), "CieXyz(42.4, 94.5, 83.4)" }, - { new CieXyy(random), "CieXyy(42.4, 94.5, 83.4)" }, - { new HunterLab(random), "HunterLab(42.4, 94.5, 83.4)" }, - { new Lms(random), "Lms(42.4, 94.5, 83.4)" }, - { new LinearRgb(random), "LinearRgb(1, 1, 1)" }, // clamping to 1 is expected - { new Rgb(random), "Rgb(1, 1, 1)" }, // clamping to 1 is expected - { new Hsl(random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected - { new Hsv(random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected - { new YCbCr(random), "YCbCr(42.4, 94.5, 83.4)" }, - }; + { new CieLab(Zero), "CieLab(0, 0, 0)" }, + { new CieLch(Zero), "CieLch(0, 0, 0)" }, + { new CieLchuv(Zero), "CieLchuv(0, 0, 0)" }, + { new CieLuv(Zero), "CieLuv(0, 0, 0)" }, + { new CieXyz(Zero), "CieXyz(0, 0, 0)" }, + { new CieXyy(Zero), "CieXyy(0, 0, 0)" }, + { new HunterLab(Zero), "HunterLab(0, 0, 0)" }, + { new Lms(Zero), "Lms(0, 0, 0)" }, + { new LinearRgb(Zero), "LinearRgb(0, 0, 0)" }, + { new Rgb(Zero), "Rgb(0, 0, 0)" }, + { new Hsl(Zero), "Hsl(0, 0, 0)" }, + { new Hsv(Zero), "Hsv(0, 0, 0)" }, + { new YCbCr(Zero), "YCbCr(0, 0, 0)" }, + { new CieLab(One), "CieLab(1, 1, 1)" }, + { new CieLch(One), "CieLch(1, 1, 1)" }, + { new CieLchuv(One), "CieLchuv(1, 1, 1)" }, + { new CieLuv(One), "CieLuv(1, 1, 1)" }, + { new CieXyz(One), "CieXyz(1, 1, 1)" }, + { new CieXyy(One), "CieXyy(1, 1, 1)" }, + { new HunterLab(One), "HunterLab(1, 1, 1)" }, + { new Lms(One), "Lms(1, 1, 1)" }, + { new LinearRgb(One), "LinearRgb(1, 1, 1)" }, + { new Rgb(One), "Rgb(1, 1, 1)" }, + { new Hsl(One), "Hsl(1, 1, 1)" }, + { new Hsv(One), "Hsv(1, 1, 1)" }, + { new YCbCr(One), "YCbCr(1, 1, 1)" }, + { new CieXyChromaticityCoordinates(1, 1), "CieXyChromaticityCoordinates(1, 1)" }, + { new CieLab(Random), "CieLab(42.4, 94.5, 83.4)" }, + { new CieLch(Random), "CieLch(42.4, 94.5, 83.4)" }, + { new CieLchuv(Random), "CieLchuv(42.4, 94.5, 83.4)" }, + { new CieLuv(Random), "CieLuv(42.4, 94.5, 83.4)" }, + { new CieXyz(Random), "CieXyz(42.4, 94.5, 83.4)" }, + { new CieXyy(Random), "CieXyy(42.4, 94.5, 83.4)" }, + { new HunterLab(Random), "HunterLab(42.4, 94.5, 83.4)" }, + { new Lms(Random), "Lms(42.4, 94.5, 83.4)" }, + { new LinearRgb(Random), "LinearRgb(1, 1, 1)" }, // clamping to 1 is expected + { new Rgb(Random), "Rgb(1, 1, 1)" }, // clamping to 1 is expected + { new Hsl(Random), "Hsl(42.4, 1, 1)" }, // clamping to 1 is expected + { new Hsv(Random), "Hsv(42.4, 1, 1)" }, // clamping to 1 is expected + { new YCbCr(Random), "YCbCr(42.4, 94.5, 83.4)" }, + }; [Theory] [MemberData(nameof(TestData))] public void StringRepresentationsAreCorrect(object color, string text) => Assert.Equal(text, color.ToString()); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs index e1b4fc790..edaad4f51 100644 --- a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Common [Fact] public void GetString_EmptyBuffer_ReturnsEmptyString() { - var buffer = new ReadOnlySpan(); + var buffer = default(ReadOnlySpan); string result = Encoding.UTF8.GetString(buffer); diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 58317ca49..b6bfca4b5 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -55,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Common { data[i] = data[i - 4] + 100f; } + return new Vector(data); } @@ -66,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Common for (int i = 0; i < Vector.Count; i++) { - float v = (float)rnd.NextDouble() * (max - min) + min; + float v = ((float)rnd.NextDouble() * (max - min)) + min; data[i] = v; } @@ -104,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Common private bool SkipOnNonAvx2([CallerMemberName] string testCaseName = null) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("Skipping AVX2 specific test case: " + testCaseName); return true; @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Common SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByte(normalized, dest); - byte[] expected = orig.Select(f => (byte)(f)).ToArray(); + byte[] expected = orig.Select(f => (byte)f).ToArray(); Assert.Equal(expected, dest); } @@ -177,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.FallbackIntrinsics128.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -191,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests.Common TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.BasicIntrinsics256.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -200,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.ExtendedIntrinsics.ByteToNormalizedFloat(s.Span, d.Span)); } [Theory] @@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests.Common { TestImpl_BulkConvertByteToNormalizedFloat( count, - (s, d) => SimdUtils.BulkConvertByteToNormalizedFloat(s.Span, d.Span)); + (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); } private static void TestImpl_BulkConvertByteToNormalizedFloat( @@ -229,9 +230,9 @@ namespace SixLabors.ImageSharp.Tests.Common [MemberData(nameof(ArraySizesDivisibleBy4))] public void FallbackIntrinsics128_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.FallbackIntrinsics128.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.FallbackIntrinsics128.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] @@ -243,18 +244,16 @@ namespace SixLabors.ImageSharp.Tests.Common return; } - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.BasicIntrinsics256.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.BasicIntrinsics256.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] [MemberData(nameof(ArraySizesDivisibleBy32))] public void ExtendedIntrinsics_BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.ExtendedIntrinsics.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.ExtendedIntrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); } [Theory] @@ -278,22 +277,38 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected2, actual2); } +#if SUPPORTS_RUNTIME_INTRINSICS + + [Theory] + [MemberData(nameof(ArraySizesDivisibleBy32))] + public void Avx2_BulkConvertNormalizedFloatToByteClampOverflows(int count) + { + if (!System.Runtime.Intrinsics.X86.Avx2.IsSupported) + { + return; + } + + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( + count, + (s, d) => SimdUtils.Avx2Intrinsics.NormalizedFloatToByteSaturate(s.Span, d.Span)); + } + +#endif + [Theory] [MemberData(nameof(ArbitraryArraySizes))] public void BulkConvertNormalizedFloatToByteClampOverflows(int count) { - TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, - (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span) - ); + TestImpl_BulkConvertNormalizedFloatToByteClampOverflows(count, (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span)); - // for small values, let's stress test the implementation a bit: + // For small values, let's stress test the implementation a bit: if (count > 0 && count < 10) { for (int i = 0; i < 20; i++) { TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( count, - (s, d) => SimdUtils.BulkConvertNormalizedFloatToByteClampOverflows(s.Span, d.Span), + (s, d) => SimdUtils.NormalizedFloatToByteSaturate(s.Span, d.Span), i + 42); } } @@ -301,7 +316,9 @@ namespace SixLabors.ImageSharp.Tests.Common private static void TestImpl_BulkConvertNormalizedFloatToByteClampOverflows( int count, - Action, Memory> convert, int seed = -1) + Action, + Memory> convert, + int seed = -1) { seed = seed > 0 ? seed : count; float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); @@ -313,7 +330,7 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.Equal(expected, actual); } - private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, f * 255f + 0.5f)); + private static byte NormalizedFloatToByte(float f) => (byte)Math.Min(255f, Math.Max(0f, (f * 255f) + 0.5f)); [Theory] [InlineData(0)] diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs index 8b2c65b07..d47d5da8e 100644 --- a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -67,7 +67,10 @@ namespace SixLabors.ImageSharp.Tests.Common public long Offset; public SeekOrigin Loc; - public SeekableStream(int capacity) : base(capacity) { } + public SeekableStream(int capacity) + : base(capacity) + { + } public override long Seek(long offset, SeekOrigin loc) { @@ -83,7 +86,10 @@ namespace SixLabors.ImageSharp.Tests.Common public List Counts = new List(); - public NonSeekableStream() : base(4) { } + public NonSeekableStream() + : base(4) + { + } public override int Read(byte[] buffer, int offset, int count) { @@ -97,7 +103,10 @@ namespace SixLabors.ImageSharp.Tests.Common { public override bool CanSeek => false; - public EofStream(int capacity) : base(capacity) { } + public EofStream(int capacity) + : base(capacity) + { + } public override int Read(byte[] buffer, int offset, int count) { diff --git a/tests/ImageSharp.Tests/Common/Tuple8.cs b/tests/ImageSharp.Tests/Common/Tuple8.cs index 3335e6e37..7c7f254db 100644 --- a/tests/ImageSharp.Tests/Common/Tuple8.cs +++ b/tests/ImageSharp.Tests/Common/Tuple8.cs @@ -1,4 +1,7 @@ -using System.Runtime.InteropServices; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Common.Tuples { @@ -95,4 +98,4 @@ namespace SixLabors.ImageSharp.Common.Tuples } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 6b35bbb97..a68baf93f 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// @@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Tests public class ConfigurationTests { public Configuration ConfigurationEmpty { get; } + public Configuration DefaultConfiguration { get; } private readonly int expectedDefaultConfigurationCount = 5; @@ -87,7 +88,6 @@ namespace SixLabors.ImageSharp.Tests } } - [Fact] public void ConstructorCallConfigureOnFormatProvider() { @@ -112,11 +112,11 @@ namespace SixLabors.ImageSharp.Tests { Configuration config = this.DefaultConfiguration; - Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance); - Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); } [Fact] @@ -124,14 +124,14 @@ namespace SixLabors.ImageSharp.Tests { Configuration config = Configuration.CreateDefaultInstance(); - Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count()); + Assert.Equal(this.expectedDefaultConfigurationCount, config.ImageFormats.Count()); } [Fact] public void WorkingBufferSizeHint_DefaultIsCorrect() { Configuration config = this.DefaultConfiguration; - Assert.True(config.WorkingBufferSizeHintInBytes > 1024); + Assert.True(config.WorkingBufferSizeHintInBytes > 1024); } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index d1fb54703..c3bc2f2ae 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)] public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image background = provider.GetImage()) using (var source = Image.Load(TestFile.Create(TestImages.Png.Ducky).Bytes)) @@ -44,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing appendSourceFileOrDescription: false); var comparer = ImageComparer.TolerantPercentage(0.01F); - background.CompareToReferenceOutput(comparer, + background.CompareToReferenceOutput( + comparer, provider, new { mode = mode }, appendPixelTypeToFileName: false, @@ -69,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing string brushImage, PixelColorBlendingMode mode, float opacity) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) @@ -88,7 +89,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing } image.DebugSave(provider, testInfo, encoder: encoder); - image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), provider, testInfo); } @@ -97,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void DrawImageOfDifferentPixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes; @@ -126,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image background = provider.GetImage()) using (var overlay = new Image(50, 50)) { - overlay.GetPixelSpan().Fill(Rgba32.Black); + overlay.GetPixelSpan().Fill(Color.Black); background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); @@ -147,7 +149,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing [Theory] [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] public void DrawTransformed(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) @@ -166,7 +168,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing image.Mutate(x => x.DrawImage(blend, position, .75F)); image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); - image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.002f), + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.002f), provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); @@ -181,7 +184,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing public void NonOverlappingImageThrows(TestImageProvider provider, int x, int y) { using (Image background = provider.GetImage()) - using (var overlay = new Image(Configuration.Default, 10, 10, Rgba32.Black)) + using (var overlay = new Image(Configuration.Default, 10, 10, Color.Black)) { ImageProcessingException ex = Assert.Throws(Test); @@ -193,7 +196,5 @@ namespace SixLabors.ImageSharp.Tests.Drawing } } } - - } } diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 4f8475738..12f7636a2 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -71,22 +71,23 @@ namespace SixLabors.ImageSharp.Tests /// protected static readonly List Files = new List { - TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), - //TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Bad.BadEOF), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only - //TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only - TestFile.Create(TestImages.Bmp.Car), +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), + // TestFile.Create(TestImages.Jpeg.Baseline.Turtle), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Ycck), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Cmyk), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Floorplan), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Festzug), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Bad.BadEOF), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.Bad.ExifUndefType), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Fb), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Progress), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Baseline.GammaDalaiLamaGray), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Jpeg.Progressive.Bad.BadEOF), // Perf: Enable for local testing only + TestFile.Create(TestImages.Bmp.Car), // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only // TestFile.Create(TestImages.Bmp.CoreHeader), // Perf: Enable for local testing only - TestFile.Create(TestImages.Png.Splash), + TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.SnakeGame), // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only @@ -104,10 +105,11 @@ namespace SixLabors.ImageSharp.Tests // TestFile.Create(TestImages.Png.FilterVar), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.P1), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Pd), // Perf: Enable for local testing only - TestFile.Create(TestImages.Gif.Rings), + TestFile.Create(TestImages.Gif.Rings), // TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only // TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only }; +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index ecec6f0a7..a8376f51b 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -3,15 +3,18 @@ using System; using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Formats.Bmp { using SixLabors.ImageSharp.Metadata; @@ -25,35 +28,65 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly string[] BitfieldsBmpFiles = BitFields; + private static BmpDecoder BmpDecoder => new BmpDecoder(); + public static readonly TheoryData RatioFiles = new TheoryData { - { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; [Theory] - [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, false)] + [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { - image.DebugSave(provider); + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + } + + using Image image = provider.GetImage(BmpDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + if (TestEnvironment.IsWindows) { image.CompareToOriginal(provider); } } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .Dispose(); + } + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + [WithFile(Bit16, PixelTypes.Rgba32)] + public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(BmpDecoder)); + Assert.IsType(ex.InnerException); } [Theory] [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -64,9 +97,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit16Inverted, PixelTypes.Rgba32)] [WithFile(Bit8Inverted, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -77,9 +110,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit1, PixelTypes.Rgba32)] [WithFile(Bit1Pal1, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder()); @@ -89,11 +122,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { @@ -105,9 +139,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit8, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -117,9 +151,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit16, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -129,9 +163,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -141,9 +175,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Rgba32v4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -155,11 +189,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(RLE4Delta, PixelTypes.Rgba32)] [WithFile(Rle4Delta320240, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { @@ -171,11 +206,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(RLE4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { @@ -190,7 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Rle8Delta320240, PixelTypes.Rgba32)] [WithFile(Rle8Blank160120, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { @@ -206,7 +242,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(RLE8Cut, PixelTypes.Rgba32)] [WithFile(RLE8Delta, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) { @@ -216,11 +252,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE8, PixelTypes.Rgba32)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(RLE8, PixelTypes.Rgba32, false)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] + [WithFile(RLE8, PixelTypes.Rgba32, true)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { + if (enforceDiscontiguousBuffers) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) { image.DebugSave(provider); @@ -229,12 +272,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE24, PixelTypes.Rgba32)] - [WithFile(RLE24Cut, PixelTypes.Rgba32)] - [WithFile(RLE24Delta, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFile(RLE24, PixelTypes.Rgba32, false)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, false)] + [WithFile(RLE24, PixelTypes.Rgba32, true)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, true)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider, bool enforceNonContiguous) + where TPixel : unmanaged, IPixel { + if (enforceNonContiguous) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); @@ -247,9 +298,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -261,9 +312,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgba, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -273,9 +324,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Rgba321010102, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -292,9 +343,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv2, PixelTypes.Rgba32)] [WithFile(CoreHeader, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -304,9 +355,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(WinBmpv3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -316,9 +367,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -329,9 +380,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(OversizedPalette, PixelTypes.Rgba32)] [WithFile(Rgb24LargePalette, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); if (TestEnvironment.IsWindows) @@ -344,26 +395,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)] public void BmpDecoder_ThrowsImageFormatException_OnInvalidPaletteSize(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Assert.Throws( () => { using (provider.GetImage(new BmpDecoder())) { } }); + Assert.Throws(() => + { + using (provider.GetImage(BmpDecoder)) + { + } + }); } [Theory] [WithFile(Rgb24jpeg, PixelTypes.Rgba32)] [WithFile(Rgb24png, PixelTypes.Rgba32)] public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - Assert.Throws(() => { using (provider.GetImage(new BmpDecoder())) { } }); + Assert.Throws(() => + { + using (provider.GetImage(BmpDecoder)) + { + } + }); } [Theory] [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -373,9 +434,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3_WithAlpha(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, new MagickReferenceDecoder()); @@ -385,9 +446,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(WinBmpv4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -398,9 +459,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv5, PixelTypes.Rgba32)] [WithFile(V5Header, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -410,9 +471,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Pal8Offset, PixelTypes.Rgba32)] public void BmpDecoder_RespectsFileHeaderOffset(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -422,9 +483,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(F, CommonNonDefaultPixelTypes)] public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -434,9 +495,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit8Palette4, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider); @@ -509,9 +570,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Os2v2Short, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -523,9 +584,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Os2v2, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); @@ -547,9 +608,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Os2BitmapArrayWarpd, PixelTypes.Rgba32)] [WithFile(Os2BitmapArrayPines, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new BmpDecoder())) + using (Image image = provider.GetImage(BmpDecoder)) { image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index fd9f50a29..235ecabf2 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -15,7 +15,6 @@ using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Formats.Bmp { using static TestImages.Bmp; @@ -32,8 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly TheoryData RatioFiles = new TheoryData { - { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, - { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter }, + { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, + { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter } }; @@ -100,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); [Theory] [WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)] @@ -109,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)] public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] @@ -117,8 +116,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] public void Encode_32Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - // if supportTransparency is false, a v3 bitmap header will be written - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + // If supportTransparency is false, a v3 bitmap header will be written. + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] @@ -126,50 +126,48 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] public void Encode_32Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] - // WinBmpv3 is a 24 bits per pixel image - [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] // WinBmpv3 is a 24 bits per pixel image. [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] public void Encode_24Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] public void Encode_24Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] public void Encode_16Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] public void Encode_16Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] public void Encode_8BitGray_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => TestBmpEncoderCore( provider, bitsPerPixel, @@ -178,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => TestBmpEncoderCore( provider, bitsPerPixel, @@ -187,7 +185,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new WuQuantizer(256) + Quantizer = new WuQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); @@ -213,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -225,7 +223,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new OctreeQuantizer(256) + Quantizer = new OctreeQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); @@ -240,14 +238,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + [WithFile(Car, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(V5Header, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(100); + TestBmpEncoderCore(provider, bitsPerPixel); + } private static void TestBmpEncoderCore( TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true, ImageComparer customComparer = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index 4eac33730..9818f9d41 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Formats.Bmp; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Formats.Bmp { using static TestImages.Bmp; diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index ba3587a7a..e6b6e43c1 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -1,27 +1,28 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using System.Linq; +using System.Reflection; + using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + using Xunit; namespace SixLabors.ImageSharp.Tests { - using System; - using System.Linq; - using System.Reflection; - using SixLabors.ImageSharp.Processing; - using SixLabors.ImageSharp.Processing.Processors.Quantization; - using SixLabors.ImageSharp.Memory; - public class GeneralFormatTests : FileTestBase { [Theory] [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] public void ResolutionShouldChange(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)] [WithFile(TestImages.Png.Bike, nameof(QuantizerNames), PixelTypes.Rgba32)] public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImageProvider provider, string quantizerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); @@ -90,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests private static IQuantizer GetQuantizer(string name) { PropertyInfo property = typeof(KnownQuantizers).GetTypeInfo().GetProperty(name); - return (IQuantizer)property.GetMethod.Invoke(null, new object[0]); + return (IQuantizer)property.GetMethod.Invoke(null, Array.Empty()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 99dc2d06d..fa2899372 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -4,11 +4,16 @@ using System; using System.Collections.Generic; using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; + using Xunit; // ReSharper disable InconsistentNaming @@ -18,6 +23,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; + private static GifDecoder GifDecoder => new GifDecoder(); + public static readonly string[] MultiFrameTestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Kumin @@ -52,7 +59,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFileCollection(nameof(MultiFrameTestFiles), PixelTypes.Rgba32)] public void Decode_VerifyAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -72,9 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { using (var stream = new UnmanagedMemoryStream(data, length)) { - var decoder = new GifDecoder(); - - using (Image image = decoder.Decode(Configuration.Default, stream)) + using (Image image = GifDecoder.Decode(Configuration.Default, stream)) { Assert.Equal((200, 200), (image.Width, image.Height)); } @@ -85,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Trans, TestPixelTypes)] public void GifDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -97,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFileCollection(nameof(BasicVerificationFiles), PixelTypes.Rgba32)] public void Decode_VerifyRootFrameAndFrameCount(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!BasicVerificationFrameCount.TryGetValue(provider.SourceFileOrDescription, out int expectedFrameCount)) { @@ -115,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeJustOneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.First })) { @@ -126,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void CanDecodeAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new GifDecoder { DecodingMode = FrameDecodingMode.All })) { @@ -163,5 +168,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(GifDecoder)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + [WithFile(TestImages.Gif.Kumin, PixelTypes.Rgba32)] + public void GifDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(GifDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index eb39c2847..588f65254 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { public class GifEncoderTests @@ -20,23 +20,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; [Theory] - [WithTestPatternImages(100, 100, TestPixelTypes)] - public void EncodeGeneratedPatterns(TestImageProvider provider) - where TPixel : struct, IPixel + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + [WithTestPatternImages(100, 100, TestPixelTypes, false)] + public void EncodeGeneratedPatterns(TestImageProvider provider, bool limitAllocationBuffer) + where TPixel : unmanaged, IPixel { + if (limitAllocationBuffer) + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + } + using (Image image = provider.GetImage()) { var encoder = new GifEncoder { // Use the palette quantizer without dithering to ensure results // are consistent - Quantizer = new WebSafePaletteQuantizer(false) + Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -103,14 +109,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif [Theory] [WithFile(TestImages.Gif.Cheers, PixelTypes.Rgba32)] public void EncodeGlobalPaletteReturnsSmallerFile(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { var encoder = new GifEncoder { ColorTableMode = GifColorTableMode.Global, - Quantizer = new OctreeQuantizer(false) + Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -141,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif var encoder = new GifEncoder { ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(frameMetadata.ColorTableLength) + Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = frameMetadata.ColorTableLength }) }; image.Save(outStream, encoder); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 7f1acf71e..ddb5608da 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -18,11 +18,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Gif.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; + public static readonly TheoryData RepeatFiles = + new TheoryData + { + { TestImages.Gif.Cheers, 0 }, + { TestImages.Gif.Receipt, 1 }, + { TestImages.Gif.Rings, 1 } + }; + [Fact] public void CloneIsDeep() { @@ -152,5 +160,35 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif } } } + + [Theory] + [MemberData(nameof(RepeatFiles))] + public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + IImageInfo image = decoder.Identify(Configuration.Default, stream); + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); + } + } + + [Theory] + [MemberData(nameof(RepeatFiles))] + public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + var decoder = new GifDecoder(); + using (Image image = decoder.Decode(Configuration.Default, stream)) + { + GifMetadata meta = image.Metadata.GetGifMetadata(); + Assert.Equal(repeatCount, meta.RepeatCount); + } + } + } } } diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 9a15e1c1b..d011a6330 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -13,12 +13,12 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; - namespace SixLabors.ImageSharp.Tests { public class ImageFormatManagerTests { public ImageFormatManager FormatsManagerEmpty { get; } + public ImageFormatManager DefaultFormatsManager { get; } public ImageFormatManagerTests() @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void IfAutoloadWellKnownFormatsIsTrueAllFormatsAreLoaded() + public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() { Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs index 6b803c3ae..f55e46c3d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.CopyToBufferArea.cs @@ -1,17 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -//#define BENCHMARKING - +// #define BENCHMARKING using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class Block8x8FTests @@ -29,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { for (int x = 0; x < 20; x++) { - if (x < subX || x >= subX + 8 * horizontalFactor || y < subY || y >= subY + 8 * verticalFactor) + if (x < subX || x >= subX + (8 * horizontalFactor) || y < subY || y >= subY + (8 * verticalFactor)) { Assert.Equal(0, buffer[x, y]); } @@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(20, 20, AllocationOptions.Clean)) { BufferArea area = buffer.GetArea(5, 10, 8, 8); - block.Copy1x1Scale(area); + block.Copy1x1Scale(ref area.GetReferenceToOrigin(), area.Stride); Assert.Equal(block[0, 0], buffer[5, 10]); Assert.Equal(block[1, 0], buffer[6, 10]); @@ -73,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(100, 100, AllocationOptions.Clean)) { BufferArea area = buffer.GetArea(start.X, start.Y, 8 * horizontalFactor, 8 * verticalFactor); - block.CopyTo(area, horizontalFactor, verticalFactor); + block.ScaledCopyTo(area, horizontalFactor, verticalFactor); for (int y = 0; y < 8 * verticalFactor; y++) { @@ -94,4 +93,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 21b9b6cab..b3c911f56 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -2,8 +2,7 @@ // Licensed under the Apache License, Version 2.0. // Uncomment this to turn unit tests into benchmarks: -//#define BENCHMARKING - +// #define BENCHMARKING using System; using System.Diagnostics; @@ -30,11 +29,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private bool SkipOnNonAvx2Runner() { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("AVX2 not supported, skipping!"); return true; } + return false; } @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var block = new Block8x8F(); + var block = default(Block8x8F); for (int i = 0; i < Block8x8F.Size; i++) { @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg () => { // Block8x8F block = new Block8x8F(); - var block = new float[64]; + float[] block = new float[64]; for (int i = 0; i < Block8x8F.Size; i++) { block[i] = i; @@ -90,8 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void Load_Store_FloatArray() { - var data = new float[Block8x8F.Size]; - var mirror = new float[Block8x8F.Size]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; for (int i = 0; i < Block8x8F.Size; i++) { @@ -102,9 +102,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var b = new Block8x8F(); + var b = default(Block8x8F); b.LoadFrom(data); - b.CopyTo(mirror); + b.ScaledCopyTo(mirror); }); Assert.Equal(data, mirror); @@ -115,8 +115,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public unsafe void Load_Store_FloatArray_Ptr() { - var data = new float[Block8x8F.Size]; - var mirror = new float[Block8x8F.Size]; + float[] data = new float[Block8x8F.Size]; + float[] mirror = new float[Block8x8F.Size]; for (int i = 0; i < Block8x8F.Size; i++) { @@ -127,9 +127,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var b = new Block8x8F(); + var b = default(Block8x8F); Block8x8F.LoadFrom(&b, data); - Block8x8F.CopyTo(&b, mirror); + Block8x8F.ScaledCopyTo(&b, mirror); }); Assert.Equal(data, mirror); @@ -140,8 +140,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void Load_Store_IntArray() { - var data = new int[Block8x8F.Size]; - var mirror = new int[Block8x8F.Size]; + int[] data = new int[Block8x8F.Size]; + int[] mirror = new int[Block8x8F.Size]; for (int i = 0; i < Block8x8F.Size; i++) { @@ -152,9 +152,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Times, () => { - var v = new Block8x8F(); + var v = default(Block8x8F); v.LoadFrom(data); - v.CopyTo(mirror); + v.ScaledCopyTo(mirror); }); Assert.Equal(data, mirror); @@ -168,14 +168,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] expected = Create8x8FloatData(); ReferenceImplementations.Transpose8x8(expected); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(Create8x8FloatData()); - var dest = new Block8x8F(); + var dest = default(Block8x8F); source.TransposeInto(ref dest); - var actual = new float[64]; - dest.CopyTo(actual); + float[] actual = new float[64]; + dest.ScaledCopyTo(actual); Assert.Equal(expected, actual); } @@ -206,12 +206,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static float[] Create8x8ColorCropTestData() { - var result = new float[64]; + float[] result = new float[64]; for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = -300 + i * 100 + j * 10; + result[(i * 8) + j] = -300 + (i * 100) + (j * 10); } } @@ -230,8 +230,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F dest = block; dest.NormalizeColorsInplace(255); - var array = new float[64]; - dest.CopyTo(array); + float[] array = new float[64]; + dest.ScaledCopyTo(array); this.Output.WriteLine("Result:"); this.PrintLinearData(array); foreach (float val in array) @@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg expected.RoundInplace(); Block8x8F actual = source; - actual.NormalizeColorsAndRoundInplaceAvx2(255); + actual.NormalizeColorsAndRoundInplaceVector8(255); this.Output.WriteLine(expected.ToString()); this.Output.WriteLine(actual.ToString()); @@ -269,10 +269,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(2)] public unsafe void Quantize(int seed) { - var block = new Block8x8F(); + var block = default(Block8x8F); block.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); - var qt = new Block8x8F(); + var qt = default(Block8x8F); qt.LoadFrom(Create8x8RoundedRandomFloatData(-2000, 2000, seed)); var unzig = ZigZag.CreateUnzigTable(); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index da75e059f..af8ba83c3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -58,10 +58,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { sum += Block8x8.GetScalarAt(&block, i); } + Assert.Equal(sum, 64 * 63 / 2); } - [Fact] public void AsFloatBlock() { @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void IndexerXY() { Block8x8 block = default; - block[8 * 3 + 5] = 42; + block[(8 * 3) + 5] = 42; short value = block[5, 3]; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 91e2f43d3..683a79a8f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -1,4 +1,6 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -7,6 +9,7 @@ using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public static class DCTTests @@ -19,22 +22,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void iDCT2D8x4_LeftPart() + public void IDCT2D8x4_LeftPart() { float[] sourceArray = Create8x8FloatData(); var expectedDestArray = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray, expectedDestArray); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray, expectedDestArray); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(sourceArray); - var dest = new Block8x8F(); + var dest = default(Block8x8F); FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); var actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); + dest.ScaledCopyTo(actualDestArray); this.Print8x8Data(expectedDestArray); this.Output.WriteLine("**************"); @@ -44,22 +47,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Fact] - public void iDCT2D8x4_RightPart() + public void IDCT2D8x4_RightPart() { float[] sourceArray = Create8x8FloatData(); var expectedDestArray = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4)); - var source = new Block8x8F(); + var source = default(Block8x8F); source.LoadFrom(sourceArray); - var dest = new Block8x8F(); + var dest = default(Block8x8F); FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); var actualDestArray = new float[64]; - dest.CopyTo(actualDestArray); + dest.ScaledCopyTo(actualDestArray); this.Print8x8Data(expectedDestArray); this.Output.WriteLine("**************"); @@ -106,25 +109,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.CompareBlocks(expected, actual, 1f); } - [Theory] [InlineData(1)] [InlineData(2)] public void FDCT8x4_LeftPart(int seed) { Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); var expectedDest = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src, expectedDest); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest); FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); var actualDest = new float[64]; - destBlock.CopyTo(actualDest); + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } @@ -135,18 +137,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FDCT8x4_RightPart(int seed) { Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); var expectedDest = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); var actualDest = new float[64]; - destBlock.CopyTo(actualDest); + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } @@ -157,24 +159,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void TransformFDCT(int seed) { Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + var destBlock = default(Block8x8F); var expectedDest = new float[64]; var temp1 = new float[64]; - var temp2 = new Block8x8F(); + var temp2 = default(Block8x8F); - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); var actualDest = new float[64]; - destBlock.CopyTo(actualDest); + destBlock.ScaledCopyTo(actualDest); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs index 7c42af596..978ee7b2a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class GenericBlock8x8Tests { public static Image CreateTestImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var image = new Image(10, 10); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -36,12 +36,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 0, 0); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 0); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, rowOctet); TPixel a = s.Frames.RootFrame[0, 0]; TPixel b = d[0, 0]; @@ -60,12 +61,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] public void LoadAndStretchCorners_WithOffset(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image s = provider.GetImage()) { var d = default(GenericBlock8x8); - d.LoadAndStretchEdges(s.Frames.RootFrame, 6, 7); + var rowOctet = new RowOctet(s.GetRootFramePixelBuffer(), 7); + d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, rowOctet); Assert.Equal(s[6, 7], d[0, 0]); Assert.Equal(s[6, 8], d[0, 1]); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index caaad73c9..27c612aee 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -8,8 +8,8 @@ using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces.Conversion; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; -using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; using Xunit; using Xunit.Abstractions; @@ -99,23 +99,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromYCbCrSimdAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.IsAvx2CompatibleArchitecture) + if (!SimdUtils.HasVector8) { this.Output.WriteLine("No AVX2 present, skipping test!"); return; } - //JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - + // JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); ValidateRgbToYCbCrConversion( - new JpegColorConverter.FromYCbCrSimdAvx2(8), + new JpegColorConverter.FromYCbCrSimdVector8(8), 3, inputBufferLength, resultBufferLength, seed); } - [Theory] [MemberData(nameof(CommonConversionData))] public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) @@ -129,9 +127,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } // Benchmark, for local execution only - //[Theory] - //[InlineData(false)] - //[InlineData(true)] + // [Theory] + // [InlineData(false)] + // [InlineData(true)] public void BenchmarkYCbCr(bool simd) { int count = 2053; @@ -289,14 +287,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int j = 0; j < inputBufferLength; j++) { - values[j] = (float)rnd.NextDouble() * (maxVal - minVal) + minVal; + values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } // no need to dispose when buffer is not array owner var memory = new Memory(values); - var source = new MemorySource(memory); + var source = MemoryGroup.Wrap(memory); buffers[i] = new Buffer2D(source, values.Length, 1); } + return new JpegColorConverter.ComponentValues(buffers, 0); } @@ -308,7 +307,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { ValidateRgbToYCbCrConversion( - JpegColorConverter.GetConverter(colorSpace,8), + JpegColorConverter.GetConverter(colorSpace, 8), componentCount, inputBufferLength, resultBufferLength, @@ -333,4 +332,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index 59b6963eb..cf2e5c81b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -1,29 +1,37 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. - using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class JpegDecoderTests { [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] - public void DecodeBaselineJpeg(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32, true)] + [WithFile(TestImages.Jpeg.Baseline.Turtle420, PixelTypes.Rgba32, true)] + public void DecodeBaselineJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - static void RunTest(string providerDump) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InPixels(1000 * 8); + } + using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput( @@ -33,12 +41,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } string providerDump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, providerDump).Dispose(); + RunTest(providerDump, enforceDiscontiguousBuffers ? "Disco" : string.Empty); + + // RemoteExecutor.Invoke( + // RunTest, + // providerDump, + // enforceDiscontiguousBuffers ? "Disco" : string.Empty) + // .Dispose(); } [Theory] [WithFileCollection(nameof(UnrecoverableTestJpegs), PixelTypes.Rgba32)] - public void UnrecoverableImagesShouldThrowCorrectError(TestImageProvider provider) - where TPixel : struct, IPixel => Assert.Throws(provider.GetImage); + public void UnrecoverableImage_Throws_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(provider.GetImage); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index 37da32d76..a01f4d46c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -13,11 +13,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Cmyk, TestImages.Jpeg.Baseline.Ycck, TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Turtle420, TestImages.Jpeg.Baseline.Testorig420, // BUG: The following image has a high difference compared to the expected output: 1.0096% - // TestImages.Jpeg.Baseline.Jpeg420Small, - + TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Bad.BadEOF, @@ -65,8 +65,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C }; - public static string[] UnrecoverableTestJpegs = { - + public static string[] UnrecoverableTestJpegs = + { TestImages.Jpeg.Issues.CriticalEOF214, TestImages.Jpeg.Issues.Fuzz.NullReferenceException797, TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, @@ -96,9 +96,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Baseline: [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 1.1f / 100, + [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + // Progressive: [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 4b845c2cb..c2fc320af 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -31,7 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { false, TestImages.Jpeg.Baseline.Jpeg400, 8, false, false }, { false, TestImages.Jpeg.Baseline.Snake, 24, true, true }, { false, TestImages.Jpeg.Baseline.Jpeg420Exif, 24, true, false }, - { true, TestImages.Jpeg.Progressive.Progress, 24, false, false }, { true, TestImages.Jpeg.Progressive.Fb, 24, false, true }, { true, TestImages.Jpeg.Baseline.Cmyk, 32, false, true }, @@ -44,8 +43,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, - { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } }; @@ -236,12 +235,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(true)] public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) { - TestImageInfo(TestImages.Jpeg.Baseline.Floorplan, JpegDecoder, useIdentify, + TestImageInfo( + TestImages.Jpeg.Baseline.Floorplan, + JpegDecoder, + useIdentify, imageInfo => - { - Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); - Assert.Equal(300, imageInfo.Metadata.VerticalResolution); - }); + { + Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); + Assert.Equal(300, imageInfo.Metadata.VerticalResolution); + }); } [Theory] @@ -249,7 +251,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(true)] public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) { - TestImageInfo(TestImages.Jpeg.Baseline.Jpeg420Exif, JpegDecoder, useIdentify, + TestImageInfo( + TestImages.Jpeg.Baseline.Jpeg420Exif, + JpegDecoder, + useIdentify, imageInfo => { Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 4f155e9c3..4ecf987e9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -5,8 +5,8 @@ using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class JpegDecoderTests @@ -14,17 +14,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public const string DecodeProgressiveJpegOutputName = "DecodeProgressiveJpeg"; [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32)] - public void DecodeProgressiveJpeg(TestImageProvider provider) - where TPixel : struct, IPixel + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32, false)] + [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, true)] + public void DecodeProgressiveJpeg(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel { - static void RunTest(string providerDump) + static void RunTest(string providerDump, string nonContiguousBuffersStr) { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + } + using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); + image.DebugSave(provider, nonContiguousBuffersStr); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( @@ -33,8 +39,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg appendPixelTypeToFileName: false); } - string dump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, dump).Dispose(); + string providerDump = BasicSerializer.Serialize(provider); + + RemoteExecutor.Invoke( + RunTest, + providerDump, + enforceDiscontiguousBuffers ? "Disco" : string.Empty) + .Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 90caea387..25cf5dd37 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -27,13 +27,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private const float ProgressiveTolerance = 0.2F / 100; - static JpegDecoderTests() - { - TestEnvironment.PrepareRemoteExecutor(); - } - private static ImageComparer GetImageComparer(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string file = provider.SourceFileOrDescription; @@ -93,31 +88,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - static void RunTest(string providerDump) - { - TestImageProvider provider = - BasicSerializer.Deserialize>(providerDump); - using Image image = provider.GetImage(JpegDecoder); - image.DebugSave(provider); - - provider.Utility.TestName = DecodeBaselineJpegOutputName; - image.CompareToReferenceOutput(ImageComparer.Tolerant(BaselineTolerance), provider, appendPixelTypeToFileName: false); - } + using Image image = provider.GetImage(JpegDecoder); + image.DebugSave(provider); + + provider.Utility.TestName = DecodeBaselineJpegOutputName; + image.CompareToReferenceOutput( + ImageComparer.Tolerant(BaselineTolerance), + provider, + appendPixelTypeToFileName: false); + } - string dump = BasicSerializer.Serialize(provider); - RemoteExecutor.Invoke(RunTest, dump).Dispose(); + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Progressive.Festzug, PixelTypes.Rgba32)] + public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(10); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(JpegDecoder)); + this.Output.WriteLine(ex.Message); + Assert.IsType(ex.InnerException); } // DEBUG ONLY! // The PDF.js output should be saved by "tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm" // into "\tests\Images\ActualOutput\JpegDecoderTests\" - //[Theory] - //[WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] - public void ValidateProgressivePdfJsOutput(TestImageProvider provider, + // [Theory] + // [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32, "PdfJsOriginal_progress.png")] + public void ValidateProgressivePdfJsOutput( + TestImageProvider provider, string pdfJsOriginalResultImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // tests\ImageSharp.Tests\Formats\Jpg\pdfjs\jpeg-converter.htm string pdfJsOriginalResultPath = Path.Combine( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index ccfde3b46..bb79abf54 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -3,6 +3,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -15,31 +16,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public class JpegEncoderTests { public static readonly TheoryData QualityFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Calliphora, 80}, - { TestImages.Jpeg.Progressive.Fb, 75 } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Calliphora, 80 }, + { TestImages.Jpeg.Progressive.Fb, 75 } + }; public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData - { - { JpegSubsample.Ratio420, 40 }, - { JpegSubsample.Ratio420, 60 }, - { JpegSubsample.Ratio420, 100 }, - - { JpegSubsample.Ratio444, 40 }, - { JpegSubsample.Ratio444, 60 }, - { JpegSubsample.Ratio444, 100 }, - }; + new TheoryData + { + { JpegSubsample.Ratio420, 40 }, + { JpegSubsample.Ratio420, 60 }, + { JpegSubsample.Ratio420, 100 }, + { JpegSubsample.Ratio444, 40 }, + { JpegSubsample.Ratio444, 60 }, + { JpegSubsample.Ratio444, 100 }, + }; public static readonly TheoryData RatioFiles = - new TheoryData - { - { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1 , PixelResolutionUnit.AspectRatio}, - { TestImages.Jpeg.Baseline.Snake, 300, 300 , PixelResolutionUnit.PixelsPerInch}, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } - }; + new TheoryData + { + { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, + { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + }; [Theory] [MemberData(nameof(QualityFiles))] @@ -72,13 +72,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] [WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] + [WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)] public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, subsample, quality); [Theory] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality) - where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality); + where TPixel : unmanaged, IPixel => TestJpegEncoderCore(provider, subsample, quality); + + [Theory] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)] + [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)] + [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)] + public void EncodeBaseline_WorksWithDiscontiguousBuffers(TestImageProvider provider, JpegSubsample subsample) + where TPixel : unmanaged, IPixel + { + ImageComparer comparer = subsample == JpegSubsample.Ratio444 + ? ImageComparer.TolerantPercentage(0.1f) + : ImageComparer.TolerantPercentage(5f); + + provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); + TestJpegEncoderCore(provider, subsample, 100, comparer); + } /// /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation @@ -106,25 +123,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void TestJpegEncoderCore( TestImageProvider provider, JpegSubsample subsample, - int quality = 100) - where TPixel : struct, IPixel + int quality = 100, + ImageComparer comparer = null) + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage()) + using Image image = provider.GetImage(); + + // There is no alpha in Jpeg! + image.Mutate(c => c.MakeOpaque()); + + var encoder = new JpegEncoder { - // There is no alpha in Jpeg! - image.Mutate(c => c.MakeOpaque()); + Subsample = subsample, + Quality = quality + }; + string info = $"{subsample}-Q{quality}"; - var encoder = new JpegEncoder - { - Subsample = subsample, - Quality = quality - }; - string info = $"{subsample}-Q{quality}"; - ImageComparer comparer = GetComparer(quality, subsample); - - // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); - } + comparer ??= GetComparer(quality, subsample); + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs index b3219115d..32481e1f5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegImagePostProcessorTests.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } private static void SaveBuffer(JpegComponentPostProcessor cp, TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = cp.ColorBuffer.ToGrayscaleImage(1f / 255f)) { @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void DoProcessorStep(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string imageFile = provider.SourceFileOrDescription; using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) @@ -60,11 +60,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg SaveBuffer(cp[2], provider); } } - + [Theory] [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32)] public void PostProcess(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string imageFile = provider.SourceFileOrDescription; using (JpegDecoderCore decoder = JpegFixture.ParseJpegStream(imageFile)) @@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs index 3d09f4b38..a6f80f558 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/LibJpegToolsTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.IO; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void RunDumpJpegCoeffsTool() { - if (!TestEnvironment.IsWindows) return; + if (!TestEnvironment.IsWindows) + { + return; + } string inputFile = TestFile.GetInputFileFullPath(TestImages.Jpeg.Progressive.Progress); string outputDir = TestEnvironment.CreateOutputDirectory(nameof(SpectralJpegTests)); @@ -27,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.Calliphora, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Progressive.Progress, PixelTypes.Rgba32)] public void ExtractSpectralData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.IsWindows) { @@ -49,4 +55,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index 44545092f..6e04610d5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -76,6 +76,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg sb.AppendLine($"Luma: SAMP: {c0.SamplingFactors} BLOCKS: {c0.SizeInBlocks}"); sb.AppendLine($"Chroma: {c1.SamplingFactors} BLOCKS: {c1.SizeInBlocks}"); } + this.Output.WriteLine(sb.ToString()); } @@ -85,6 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { TestImages.Jpeg.Baseline.Jpeg420Exif, 3, new Size(2, 2), new Size(1, 1) }, { TestImages.Jpeg.Baseline.Jpeg420Small, 3, new Size(2, 2), new Size(1, 1) }, { TestImages.Jpeg.Baseline.Testorig420, 3, new Size(2, 2), new Size(1, 1) }, + // TODO: Find Ycck or Cmyk images with different subsampling { TestImages.Jpeg.Baseline.Ycck, 4, new Size(1, 1), new Size(1, 1) }, { TestImages.Jpeg.Baseline.Cmyk, 4, new Size(1, 1), new Size(1, 1) }, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs index c908abc50..fb09065b0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ProfileResolverTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Text; @@ -19,25 +19,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Fact] public void ProfileResolverHasCorrectJFifMarker() { - Assert.Equal(JFifMarker, ProfileResolver.JFifMarker); + Assert.Equal(JFifMarker, ProfileResolver.JFifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectExifMarker() { - Assert.Equal(ExifMarker, ProfileResolver.ExifMarker); + Assert.Equal(ExifMarker, ProfileResolver.ExifMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectIccMarker() { - Assert.Equal(IccMarker, ProfileResolver.IccMarker); + Assert.Equal(IccMarker, ProfileResolver.IccMarker.ToArray()); } [Fact] public void ProfileResolverHasCorrectAdobeMarker() { - Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker); + Assert.Equal(AdobeMarker, ProfileResolver.AdobeMarker.ToArray()); } [Fact] @@ -76,4 +76,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.False(ProfileResolver.IsProfile(AdobeMarker, ProfileResolver.IccMarker)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 60a019c29..f8afb3d0b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -31,8 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] dest = new float[64]; float[] temp = new float[64]; - ReferenceImplementations.LLM_FloatingPoint_DCT.fDCT2D_llm(src, dest, temp, true); - ReferenceImplementations.LLM_FloatingPoint_DCT.iDCT2D_llm(dest, src, temp); + ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, dest, temp, true); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(dest, src, temp); this.CompareBlocks(original, src, 0.1f); } @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float[] dest = new float[64]; - ReferenceImplementations.GT_FloatingPoint_DCT.iDCT8x8GT(floatSrc, dest); + ReferenceImplementations.GT_FloatingPoint_DCT.IDCT8x8GT(floatSrc, dest); this.CompareBlocks(intData.ConvertAllToFloat(), dest, 1f); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index f16d04bf6..ca4040380 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -1,4 +1,5 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; @@ -8,6 +9,7 @@ using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public partial class ReferenceImplementationsTests diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index d5a1fb7ba..3e125adac 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -1,15 +1,19 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg { public class SpectralJpegTests @@ -41,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory(Skip = "Debug only, enable manually!")] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); @@ -59,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void VerifySpectralCorrectness(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.IsWindows) { @@ -78,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.VerifySpectralCorrectnessImpl(provider, imageSharpData); } } - + private void VerifySpectralCorrectnessImpl( TestImageProvider provider, LibJpegTools.SpectralData imageSharpData) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { LibJpegTools.SpectralData libJpegData = LibJpegTools.ExtractSpectralData(provider.SourceFileOrDescription); @@ -110,8 +114,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: {diff}"); averageDifference += diff.average; totalDifference += diff.total; - tolerance += libJpegComponent.SpectralBlocks.MemorySource.GetSpan().Length; + tolerance += libJpegComponent.SpectralBlocks.GetSingleSpan().Length; } + averageDifference /= componentCount; tolerance /= 64; // fair enough? @@ -123,4 +128,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.True(totalDifference < tolerance); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 20830a33f..b7cf6a840 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Diagnostics; using System.IO; @@ -14,11 +12,13 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { public class JpegFixture : MeasureFixture { - public JpegFixture(ITestOutputHelper output) : base(output) + public JpegFixture(ITestOutputHelper output) + : base(output) { } @@ -30,9 +30,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = i * 10 + j; + result[(i * 8) + j] = (i * 10) + j; } } + return result; } @@ -44,9 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = i * 10 + j; + result[(i * 8) + j] = (i * 10) + j; } } + return result; } @@ -58,14 +60,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - short val = (short)(i * 10 + j); + short val = (short)((i * 10) + j); if ((i + j) % 2 == 0) { val *= -1; } - result[i * 8 + j] = val; + + result[(i * 8) + j] = val; } } + return result; } @@ -78,9 +82,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - result[i * 8 + j] = rnd.Next(minValue, maxValue); + result[(i * 8) + j] = rnd.Next(minValue, maxValue); } } + return result; } @@ -99,9 +104,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils val *= maxValue - minValue; val += minValue; - result[i * 8 + j] = (float)val; + result[(i * 8) + j] = (float)val; } } + return result; } @@ -120,8 +126,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { for (int j = 0; j < 8; j++) { - bld.Append($"{data[i * 8 + j],3} "); + bld.Append($"{data[(i * 8) + j],3} "); } + bld.AppendLine(); } @@ -132,13 +139,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal void PrintLinearData(Span data, int count = -1) { - if (count < 0) count = data.Length; + if (count < 0) + { + count = data.Length; + } var sb = new StringBuilder(); for (int i = 0; i < count; i++) { sb.Append($"{data[i],3} "); } + this.Output.WriteLine(sb.ToString()); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index a4929fe80..b9526994e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -61,8 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils var result = new ComponentData( c.WidthInBlocks, c.HeightInBlocks, - index - ); + index); for (int y = 0; y < result.HeightInBlocks; y++) { @@ -88,6 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.WriteToImage(bx, by, result); } } + return result; } @@ -105,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Rgba32 color = default; color.FromVector4(v); - int yy = by * 8 + y; - int xx = bx * 8 + x; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; image[xx, yy] = color; } } @@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils internal float GetBlockValue(Block8x8 block, int x, int y) { - float d = (this.MaxVal - this.MinVal); + float d = this.MaxVal - this.MinVal; float val = block[y, x]; val -= this.MinVal; val /= d; @@ -135,9 +135,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils bool ok = this.Index == other.Index && this.HeightInBlocks == other.HeightInBlocks && this.WidthInBlocks == other.WidthInBlocks; - //&& this.MinVal == other.MinVal - //&& this.MaxVal == other.MaxVal; - if (!ok) return false; + if (!ok) + { + return false; + } for (int y = 0; y < this.HeightInBlocks; y++) { @@ -145,31 +146,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { Block8x8 a = this.SpectralBlocks[x, y]; Block8x8 b = other.SpectralBlocks[x, y]; - if (!a.Equals(b)) return false; + if (!a.Equals(b)) + { + return false; + } } } + return true; } public override bool Equals(object obj) { - if (obj is null) return false; - if (object.ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; + if (obj is null) + { + return false; + } + + if (object.ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != this.GetType()) + { + return false; + } + return this.Equals((ComponentData)obj); } public override int GetHashCode() { - unchecked - { - int hashCode = this.Index; - hashCode = (hashCode * 397) ^ this.HeightInBlocks; - hashCode = (hashCode * 397) ^ this.WidthInBlocks; - hashCode = (hashCode * 397) ^ this.MinVal.GetHashCode(); - hashCode = (hashCode * 397) ^ this.MaxVal.GetHashCode(); - return hashCode; - } + return HashCode.Combine(this.Index, this.HeightInBlocks, this.WidthInBlocks, this.MinVal, this.MaxVal); } public ref Block8x8 GetBlockReference(int column, int row) @@ -179,12 +188,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static bool operator ==(ComponentData left, ComponentData right) { - return Object.Equals(left, right); + return object.Equals(left, right); } public static bool operator !=(ComponentData left, ComponentData right) { - return !Object.Equals(left, right); + return !object.Equals(left, right); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs index ac9e2835c..0fce671e5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.SpectralData.cs @@ -12,7 +12,6 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { - internal static partial class LibJpegTools { /// @@ -40,7 +39,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public Image TryCreateRGBSpectralImage() { - if (this.ComponentCount != 3) return null; + if (this.ComponentCount != 3) + { + return null; + } LibJpegTools.ComponentData c0 = this.Components[0]; LibJpegTools.ComponentData c1 = this.Components[1]; @@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils this.WriteToImage(bx, by, result); } } + return result; } @@ -73,9 +76,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Block8x8 block1 = c1.SpectralBlocks[bx, by]; Block8x8 block2 = c2.SpectralBlocks[bx, by]; - float d0 = (c0.MaxVal - c0.MinVal); - float d1 = (c1.MaxVal - c1.MinVal); - float d2 = (c2.MaxVal - c2.MinVal); + float d0 = c0.MaxVal - c0.MinVal; + float d1 = c1.MaxVal - c1.MinVal; + float d2 = c2.MaxVal - c2.MinVal; for (int y = 0; y < 8; y++) { @@ -89,8 +92,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Rgba32 color = default; color.FromVector4(v); - int yy = by * 8 + y; - int xx = bx * 8 + x; + int yy = (by * 8) + y; + int xx = (bx * 8) + x; image[xx, yy] = color; } } @@ -117,8 +120,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { LibJpegTools.ComponentData a = this.Components[i]; LibJpegTools.ComponentData b = other.Components[i]; - if (!a.Equals(b)) return false; + if (!a.Equals(b)) + { + return false; + } } + return true; } @@ -151,4 +158,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs index 31779df45..826335b65 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -1,8 +1,11 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; -using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; using System.Numerics; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs index 58fa4231e..fc0540c64 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.AccurateDCT.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -18,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// internal static class AccurateDCT { - private static double[,] CosLut = InitCosLut(); + private static readonly double[,] CosLut = InitCosLut(); public static Block8x8 TransformIDCT(ref Block8x8 block) { @@ -29,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static void TransformIDCTInplace(Span span) { - var temp = new Block8x8(); + var temp = default(Block8x8); temp.LoadFrom(span); Block8x8 result = TransformIDCT(ref temp); result.CopyTo(span); @@ -44,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static void TransformFDCTInplace(Span span) { - var temp = new Block8x8(); + var temp = default(Block8x8); temp.LoadFrom(span); Block8x8 result = TransformFDCT(ref temp); result.CopyTo(span); @@ -56,19 +59,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils double tmp, tmp2; Block8x8F res = default; - for (y=0; y<8; y++) { - for (x=0; x<8; x++) { + for (y = 0; y < 8; y++) + { + for (x = 0; x < 8; x++) + { tmp = 0.0; - for (v=0; v<8; v++) { + for (v = 0; v < 8; v++) + { tmp2 = 0.0; - for (u=0; u<8; u++) { - tmp2 += block[v * 8 + u] * CosLut[x, u]; + for (u = 0; u < 8; u++) + { + tmp2 += block[(v * 8) + u] * CosLut[x, u]; } + tmp += CosLut[y, v] * tmp2; } - res[y * 8 + x] = (float)tmp; + + res[(y * 8) + x] = (float)tmp; } } + return res; } @@ -88,11 +98,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils tmp2 = 0.0; for (x = 0; x < 8; x++) { - tmp2 += block[y * 8 + x] * CosLut[x,u]; + tmp2 += block[(y * 8) + x] * CosLut[x, u]; } + tmp += CosLut[y, v] * tmp2; } - res[v * 8 + u] = (float) tmp; + + res[(v * 8) + u] = (float)tmp; } } @@ -101,20 +113,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils private static double[,] InitCosLut() { - var coslu = new double[8, 8]; + double[,] coslu = new double[8, 8]; int a, b; double tmp; for (a = 0; a < 8; a++) - for (b = 0; b < 8; b++) { - tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); - if (b == 0) + for (b = 0; b < 8; b++) { - tmp /= Math.Sqrt(2.0); + tmp = Math.Cos((a + a + 1) * b * (3.14159265358979323846 / 16.0)); + if (b == 0) + { + tmp /= Math.Sqrt(2.0); + } + + coslu[a, b] = tmp * 0.5; } - coslu[a, b] = tmp * 0.5; } + return coslu; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs index 3742e45bd..1adcf0bc0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.GT_FloatingPoint_DCT.cs @@ -1,7 +1,9 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -9,24 +11,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Non-optimized method ported from: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L446 - /// + /// /// *** Paper *** /// Plonka, Gerlind, and Manfred Tasche. "Fast and numerically stable algorithms for discrete cosine transforms." Linear algebra and its applications 394 (2005) : 309 - 345. /// internal static class GT_FloatingPoint_DCT { - public static void idct81d_GT(Span src, Span dst) + public static void Idct81d_GT(Span src, Span dst) { for (int i = 0; i < 8; i++) { float mx00 = 1.4142135623731f * src[0]; - float mx01 = 1.38703984532215f * src[1] + 0.275899379282943f * src[7]; - float mx02 = 1.30656296487638f * src[2] + 0.541196100146197f * src[6]; - float mx03 = 1.17587560241936f * src[3] + 0.785694958387102f * src[5]; + float mx01 = (1.38703984532215f * src[1]) + (0.275899379282943f * src[7]); + float mx02 = (1.30656296487638f * src[2]) + (0.541196100146197f * src[6]); + float mx03 = (1.17587560241936f * src[3]) + (0.785694958387102f * src[5]); float mx04 = 1.4142135623731f * src[4]; - float mx05 = -0.785694958387102f * src[3] + 1.17587560241936f * src[5]; - float mx06 = 0.541196100146197f * src[2] - 1.30656296487638f * src[6]; - float mx07 = -0.275899379282943f * src[1] + 1.38703984532215f * src[7]; + float mx05 = (-0.785694958387102f * src[3]) + (1.17587560241936f * src[5]); + float mx06 = (0.541196100146197f * src[2]) - (1.30656296487638f * src[6]); + float mx07 = (-0.275899379282943f * src[1]) + (1.38703984532215f * src[7]); float mx09 = mx00 + mx04; float mx0a = mx01 + mx03; float mx0b = 1.4142135623731f * mx02; @@ -41,29 +43,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils float mx14 = 0.353553390593274f * (mx11 + mx12); float mx15 = 0.353553390593274f * (mx11 - mx12); float mx16 = 0.5f * mx13; - dst[0] = 0.25f * (mx09 + mx0b) + 0.353553390593274f * mx0a; + dst[0] = (0.25f * (mx09 + mx0b)) + (0.353553390593274f * mx0a); dst[1] = 0.707106781186547f * (mx0f + mx15); dst[2] = 0.707106781186547f * (mx0f - mx15); dst[3] = 0.707106781186547f * (mx0e + mx16); dst[4] = 0.707106781186547f * (mx0e - mx16); dst[5] = 0.707106781186547f * (mx10 - mx14); dst[6] = 0.707106781186547f * (mx10 + mx14); - dst[7] = 0.25f * (mx09 + mx0b) - 0.353553390593274f * mx0a; + dst[7] = (0.25f * (mx09 + mx0b)) - (0.353553390593274f * mx0a); dst = dst.Slice(8); src = src.Slice(8); } } - public static void iDCT8x8GT(Span s, Span d) + public static void IDCT8x8GT(Span s, Span d) { - idct81d_GT(s, d); + Idct81d_GT(s, d); Transpose8x8(d); - idct81d_GT(d, d); + Idct81d_GT(d, d); Transpose8x8(d); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs index 0c644e5c2..533ecaca1 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -1,4 +1,6 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -7,6 +9,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit.Abstractions; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -29,12 +32,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { public static Block8x8F TransformIDCT(ref Block8x8F source) { - var s = new float[64]; - source.CopyTo(s); - var d = new float[64]; - var temp = new float[64]; + float[] s = new float[64]; + source.ScaledCopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; - iDCT2D_llm(s, d, temp); + IDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return result; @@ -42,12 +45,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static Block8x8F TransformFDCT_UpscaleBy8(ref Block8x8F source) { - var s = new float[64]; - source.CopyTo(s); - var d = new float[64]; - var temp = new float[64]; + float[] s = new float[64]; + source.ScaledCopyTo(s); + float[] d = new float[64]; + float[] temp = new float[64]; - fDCT2D_llm(s, d, temp); + FDCT2D_llm(s, d, temp); Block8x8F result = default; result.LoadFrom(d); return result; @@ -61,12 +64,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public static float[] PrintConstants(ITestOutputHelper output) { - var r = new float[8]; + float[] r = new float[8]; for (int i = 0; i < 8; i++) { r[i] = (float)(Cos(i / 16.0 * M_PI) * M_SQRT2); output?.WriteLine($"float r{i} = {r[i]:R}f;"); } + return r; } @@ -75,15 +79,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L200 /// - /// - /// - private static void iDCT1Dllm_32f(Span y, Span x) + private static void IDCT1Dllm_32f(Span y, Span x) { float a0, a1, a2, a3, b0, b1, b2, b3; float z0, z1, z2, z3, z4; // see: PrintConstants() - float r0 = 1.41421354f; float r1 = 1.3870399f; float r2 = 1.306563f; @@ -101,19 +102,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils z0 = z0 * (-r3 + r7); z1 = z1 * (-r3 - r1); - z2 = z2 * (-r3 - r5) + z4; - z3 = z3 * (-r3 + r5) + z4; + z2 = (z2 * (-r3 - r5)) + z4; + z3 = (z3 * (-r3 + r5)) + z4; - b3 = y[7] * (-r1 + r3 + r5 - r7) + z0 + z2; - b2 = y[5] * (r1 + r3 - r5 + r7) + z1 + z3; - b1 = y[3] * (r1 + r3 + r5 - r7) + z1 + z2; - b0 = y[1] * (r1 + r3 - r5 - r7) + z0 + z3; + b3 = (y[7] * (-r1 + r3 + r5 - r7)) + z0 + z2; + b2 = (y[5] * (r1 + r3 - r5 + r7)) + z1 + z3; + b1 = (y[3] * (r1 + r3 + r5 - r7)) + z1 + z2; + b0 = (y[1] * (r1 + r3 - r5 - r7)) + z0 + z3; z4 = (y[2] + y[6]) * r6; z0 = y[0] + y[4]; z1 = y[0] - y[4]; - z2 = z4 - y[6] * (r2 + r6); - z3 = z4 + y[2] * (r2 - r6); + z2 = z4 - (y[6] * (r2 + r6)); + z3 = z4 + (y[2] * (r2 - r6)); a0 = z0 + z3; a3 = z0 - z3; a1 = z1 + z2; @@ -133,23 +134,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// Original: https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239 /// Applies IDCT transformation on "s" copying transformed values to "d", using temporary block "temp" /// - /// - /// - /// - internal static void iDCT2D_llm(Span s, Span d, Span temp) + internal static void IDCT2D_llm(Span s, Span d, Span temp) { int j; for (j = 0; j < 8; j++) { - iDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); + IDCT1Dllm_32f(s.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); for (j = 0; j < 8; j++) { - iDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + IDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); @@ -168,27 +166,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Source /// Destination - public static void fDCT2D8x4_32f(Span s, Span d) + public static void FDCT2D8x4_32f(Span s, Span d) { Vector4 c0 = _mm_load_ps(s, 0); Vector4 c1 = _mm_load_ps(s, 56); - Vector4 t0 = (c0 + c1); - Vector4 t7 = (c0 - c1); + Vector4 t0 = c0 + c1; + Vector4 t7 = c0 - c1; c1 = _mm_load_ps(s, 48); c0 = _mm_load_ps(s, 8); - Vector4 t1 = (c0 + c1); - Vector4 t6 = (c0 - c1); + Vector4 t1 = c0 + c1; + Vector4 t6 = c0 - c1; c1 = _mm_load_ps(s, 40); c0 = _mm_load_ps(s, 16); - Vector4 t2 = (c0 + c1); - Vector4 t5 = (c0 - c1); + Vector4 t2 = c0 + c1; + Vector4 t5 = c0 - c1; c0 = _mm_load_ps(s, 24); c1 = _mm_load_ps(s, 32); - Vector4 t3 = (c0 + c1); - Vector4 t4 = (c0 - c1); + Vector4 t3 = c0 + c1; + Vector4 t4 = c0 - c1; /* c1 = x[0]; c2 = x[7]; t0 = c1 + c2; t7 = c1 - c2; @@ -197,19 +195,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils c1 = x[3]; c2 = x[4]; t3 = c1 + c2; t4 = c1 - c2; */ - c0 = (t0 + t3); - Vector4 c3 = (t0 - t3); - c1 = (t1 + t2); - Vector4 c2 = (t1 - t2); + c0 = t0 + t3; + Vector4 c3 = t0 - t3; + c1 = t1 + t2; + Vector4 c2 = t1 - t2; /* c0 = t0 + t3; c3 = t0 - t3; c1 = t1 + t2; c2 = t1 - t2; */ - _mm_store_ps(d, 0, (c0 + c1)); + _mm_store_ps(d, 0, c0 + c1); - _mm_store_ps(d, 32, (c0 - c1)); + _mm_store_ps(d, 32, c0 - c1); /*y[0] = c0 + c1; y[4] = c0 - c1;*/ @@ -217,9 +215,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils var w0 = new Vector4(0.541196f); var w1 = new Vector4(1.306563f); - _mm_store_ps(d, 16, ((w0 * c2) + (w1 * c3))); + _mm_store_ps(d, 16, (w0 * c2) + (w1 * c3)); - _mm_store_ps(d, 48, ((w0 * c3) - (w1 * c2))); + _mm_store_ps(d, 48, (w0 * c3) - (w1 * c2)); /* y[2] = c2 * r[6] + c3 * r[2]; y[6] = c3 * r[6] - c2 * r[2]; @@ -227,8 +225,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils w0 = new Vector4(1.175876f); w1 = new Vector4(0.785695f); - c3 = ((w0 * t4) + (w1 * t7)); - c0 = ((w0 * t7) - (w1 * t4)); + c3 = (w0 * t4) + (w1 * t7); + c0 = (w0 * t7) - (w1 * t4); /* c3 = t4 * r[3] + t7 * r[5]; c0 = t7 * r[3] - t4 * r[5]; @@ -236,78 +234,83 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils w0 = new Vector4(1.387040f); w1 = new Vector4(0.275899f); - c2 = ((w0 * t5) + (w1 * t6)); - c1 = ((w0 * t6) - (w1 * t5)); + c2 = (w0 * t5) + (w1 * t6); + c1 = (w0 * t6) - (w1 * t5); /* c2 = t5 * r[1] + t6 * r[7]; c1 = t6 * r[1] - t5 * r[7]; */ - _mm_store_ps(d, 24, (c0 - c2)); + _mm_store_ps(d, 24, c0 - c2); - _mm_store_ps(d, 40, (c3 - c1)); - //y[5] = c3 - c1; y[3] = c0 - c2; + _mm_store_ps(d, 40, c3 - c1); + // y[5] = c3 - c1; y[3] = c0 - c2; var invsqrt2 = new Vector4(0.707107f); - c0 = ((c0 + c2) * invsqrt2); - c3 = ((c3 + c1) * invsqrt2); - //c0 = (c0 + c2) * invsqrt2; - //c3 = (c3 + c1) * invsqrt2; - - _mm_store_ps(d, 8, (c0 + c3)); + c0 = (c0 + c2) * invsqrt2; + c3 = (c3 + c1) * invsqrt2; - _mm_store_ps(d, 56, (c0 - c3)); - //y[1] = c0 + c3; y[7] = c0 - c3; + // c0 = (c0 + c2) * invsqrt2; + // c3 = (c3 + c1) * invsqrt2; + _mm_store_ps(d, 8, c0 + c3); + _mm_store_ps(d, 56, c0 - c3); + // y[1] = c0 + c3; y[7] = c0 - c3; /*for(i = 0;i < 8;i++) { y[i] *= invsqrt2h; }*/ } - public static void fDCT8x8_llm_sse(Span s, Span d, Span temp) + public static void FDCT8x8_llm_sse(Span s, Span d, Span temp) { Transpose8x8(s, temp); - fDCT2D8x4_32f(temp, d); + FDCT2D8x4_32f(temp, d); - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); Transpose8x8(d, temp); - fDCT2D8x4_32f(temp, d); + FDCT2D8x4_32f(temp, d); - fDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); + FDCT2D8x4_32f(temp.Slice(4), d.Slice(4)); var c = new Vector4(0.1250f); - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//0 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//1 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//2 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//3 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//4 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//5 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//6 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//7 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//8 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//9 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//10 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//11 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//12 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//13 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//14 - _mm_store_ps(d, 0, (_mm_load_ps(d, 0) * c)); d = d.Slice(4);//15 +#pragma warning disable SA1107 // Code should not contain multiple statements on one line + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 0 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 1 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 2 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 3 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 4 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 5 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 6 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 7 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 8 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 9 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 10 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 11 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 12 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 13 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 14 + _mm_store_ps(d, 0, _mm_load_ps(d, 0) * c); d = d.Slice(4); // 15 +#pragma warning restore SA1107 // Code should not contain multiple statements on one line } [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1300 // Element should begin with upper-case letter private static Vector4 _mm_load_ps(Span src, int offset) +#pragma warning restore SA1300 // Element should begin with upper-case letter { src = src.Slice(offset); return new Vector4(src[0], src[1], src[2], src[3]); } [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1300 // Element should begin with upper-case letter private static void _mm_store_ps(Span dest, int offset, Vector4 src) +#pragma warning restore SA1300 // Element should begin with upper-case letter { dest = dest.Slice(offset); dest[0] = src.X; @@ -318,7 +321,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils // Accurate variants of constants from: // https://github.com/mozilla/mozjpeg/blob/master/simd/jfdctint-altivec.c - +#pragma warning disable SA1309 // Field names should not begin with underscore private static readonly Vector4 _1_175876 = new Vector4(1.175875602f); private static readonly Vector4 _1_961571 = new Vector4(-1.961570560f); @@ -342,15 +345,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils private static readonly Vector4 _1_847759 = new Vector4(-1.847759065f); private static readonly Vector4 _0_765367 = new Vector4(0.765366865f); +#pragma warning restore SA1309 // Field names should not begin with underscore /// /// Original: /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 /// Does a part of the IDCT job on the given parts of the blocks /// - /// - /// - internal static void iDCT2D8x4_32f(Span y, Span x) + internal static void IDCT2D8x4_32f(Span y, Span x) { /* float a0,a1,a2,a3,b0,b1,b2,b3; float z0,z1,z2,z3,z4; float r[8]; int i; @@ -377,12 +379,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Vector4 mz1 = my3 + my5; Vector4 mz3 = my1 + my5; - Vector4 mz4 = ((mz0 + mz1) * _1_175876); - //z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; - //z4 = (z0 + z1) * r[3]; + Vector4 mz4 = (mz0 + mz1) * _1_175876; - mz2 = mz2 * _1_961571 + mz4; - mz3 = mz3 * _0_390181 + mz4; + // z0 = y[1] + y[7]; z1 = y[3] + y[5]; z2 = y[3] + y[7]; z3 = y[1] + y[5]; + // z4 = (z0 + z1) * r[3]; + mz2 = (mz2 * _1_961571) + mz4; + mz3 = (mz3 * _0_390181) + mz4; mz0 = mz0 * _0_899976; mz1 = mz1 * _2_562915; @@ -396,10 +398,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils z2 = z2 * (-r[3] - r[5]) + z4; z3 = z3 * (-r[3] + r[5]) + z4;*/ - Vector4 mb3 = my7 * _0_298631 + mz0 + mz2; - Vector4 mb2 = my5 * _2_053120 + mz1 + mz3; - Vector4 mb1 = my3 * _3_072711 + mz1 + mz2; - Vector4 mb0 = my1 * _1_501321 + mz0 + mz3; + Vector4 mb3 = (my7 * _0_298631) + mz0 + mz2; + Vector4 mb2 = (my5 * _2_053120) + mz1 + mz3; + Vector4 mb1 = (my3 * _3_072711) + mz1 + mz2; + Vector4 mb0 = (my1 * _1_501321) + mz0 + mz3; /* 0.298631 @@ -420,8 +422,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils mz0 = my0 + my4; mz1 = my0 - my4; - mz2 = mz4 + my6 * _1_847759; - mz3 = mz4 + my2 * _0_765367; + mz2 = mz4 + (my6 * _1_847759); + mz3 = mz4 + (my2 * _0_765367); my0 = mz0 + mz3; my3 = mz0 - mz3; @@ -462,13 +464,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils */ } - internal static void fDCT1Dllm_32f(Span x, Span y) + internal static void FDCT1Dllm_32f(Span x, Span y) { float t0, t1, t2, t3, t4, t5, t6, t7; float c0, c1, c2, c3; - var r = new float[8]; + float[] r = new float[8]; - //for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } + // for(i = 0;i < 8;i++){ r[i] = (float)(cos((double)i / 16.0 * M_PI) * M_SQRT2); } r[0] = 1.414214f; r[1] = 1.387040f; r[2] = 1.306563f; @@ -478,9 +480,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils r[6] = 0.541196f; r[7] = 0.275899f; - const float invsqrt2 = 0.707107f; //(float)(1.0f / M_SQRT2); - //const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; + const float invsqrt2 = 0.707107f; // (float)(1.0f / M_SQRT2); + // const float invsqrt2h = 0.353554f; //invsqrt2*0.5f; c1 = x[0]; c2 = x[7]; t0 = c1 + c2; @@ -505,13 +507,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y[0] = c0 + c1; y[4] = c0 - c1; - y[2] = c2 * r[6] + c3 * r[2]; - y[6] = c3 * r[6] - c2 * r[2]; + y[2] = (c2 * r[6]) + (c3 * r[2]); + y[6] = (c3 * r[6]) - (c2 * r[2]); - c3 = t4 * r[3] + t7 * r[5]; - c0 = t7 * r[3] - t4 * r[5]; - c2 = t5 * r[1] + t6 * r[7]; - c1 = t6 * r[1] - t5 * r[7]; + c3 = (t4 * r[3]) + (t7 * r[5]); + c0 = (t7 * r[3]) - (t4 * r[5]); + c2 = (t5 * r[1]) + (t6 * r[7]); + c1 = (t6 * r[1]) - (t5 * r[7]); y[5] = c3 - c1; y[3] = c0 - c2; @@ -521,7 +523,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y[7] = c0 - c3; } - internal static void fDCT2D_llm( + internal static void FDCT2D_llm( Span s, Span d, Span temp, @@ -532,14 +534,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int j = 0; j < 8; j++) { - fDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); + FDCT1Dllm_32f(sWorker.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); for (int j = 0; j < 8; j++) { - fDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); + FDCT1Dllm_32f(d.Slice(j * 8), temp.Slice(j * 8)); } Transpose8x8(temp, d); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs index a929e0eb0..c11edb67c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.StandardIntegerDCT.cs @@ -1,8 +1,11 @@ -// ReSharper disable InconsistentNaming +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { internal static partial class ReferenceImplementations @@ -40,18 +43,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// public static class StandardIntegerDCT { - private const int fix_0_298631336 = 2446; - private const int fix_0_390180644 = 3196; - private const int fix_0_541196100 = 4433; - private const int fix_0_765366865 = 6270; - private const int fix_0_899976223 = 7373; - private const int fix_1_175875602 = 9633; - private const int fix_1_501321110 = 12299; - private const int fix_1_847759065 = 15137; - private const int fix_1_961570560 = 16069; - private const int fix_2_053119869 = 16819; - private const int fix_2_562915447 = 20995; - private const int fix_3_072711026 = 25172; + private const int Fix_0_298631336 = 2446; + private const int Fix_0_390180644 = 3196; + private const int Fix_0_541196100 = 4433; + private const int Fix_0_765366865 = 6270; + private const int Fix_0_899976223 = 7373; + private const int Fix_1_175875602 = 9633; + private const int Fix_1_501321110 = 12299; + private const int Fix_1_847759065 = 15137; + private const int Fix_1_961570560 = 16069; + private const int Fix_2_053119869 = 16819; + private const int Fix_2_562915447 = 20995; + private const int Fix_3_072711026 = 25172; /// /// The number of bits @@ -127,25 +130,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[y8] = (tmp10 + tmp11 - (8 * CenterJSample)) << Pass1Bits; block[y8 + 4] = (tmp10 - tmp11) << Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; + int z1 = (tmp12 + tmp13) * Fix_0_541196100; z1 += 1 << (Bits - Pass1Bits - 1); - block[y8 + 2] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits - Pass1Bits); - block[y8 + 6] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits - Pass1Bits); + block[y8 + 2] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits - Pass1Bits); + block[y8 + 6] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits - Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 = (tmp12 + tmp13) * Fix_1_175875602; z1 += 1 << (Bits - Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; + tmp0 = tmp0 * Fix_1_501321110; + tmp1 = tmp1 * Fix_3_072711026; + tmp2 = tmp2 * Fix_2_053119869; + tmp3 = tmp3 * Fix_0_298631336; + tmp10 = tmp10 * -Fix_0_899976223; + tmp11 = tmp11 * -Fix_2_562915447; + tmp12 = tmp12 * -Fix_0_390180644; + tmp13 = tmp13 * -Fix_1_961570560; tmp12 += z1; tmp13 += z1; @@ -177,25 +180,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[x] = (tmp10 + tmp11) >> Pass1Bits; block[32 + x] = (tmp10 - tmp11) >> Pass1Bits; - int z1 = (tmp12 + tmp13) * fix_0_541196100; + int z1 = (tmp12 + tmp13) * Fix_0_541196100; z1 += 1 << (Bits + Pass1Bits - 1); - block[16 + x] = (z1 + (tmp12 * fix_0_765366865)) >> (Bits + Pass1Bits); - block[48 + x] = (z1 - (tmp13 * fix_1_847759065)) >> (Bits + Pass1Bits); + block[16 + x] = (z1 + (tmp12 * Fix_0_765366865)) >> (Bits + Pass1Bits); + block[48 + x] = (z1 - (tmp13 * Fix_1_847759065)) >> (Bits + Pass1Bits); tmp10 = tmp0 + tmp3; tmp11 = tmp1 + tmp2; tmp12 = tmp0 + tmp2; tmp13 = tmp1 + tmp3; - z1 = (tmp12 + tmp13) * fix_1_175875602; + z1 = (tmp12 + tmp13) * Fix_1_175875602; z1 += 1 << (Bits + Pass1Bits - 1); - tmp0 = tmp0 * fix_1_501321110; - tmp1 = tmp1 * fix_3_072711026; - tmp2 = tmp2 * fix_2_053119869; - tmp3 = tmp3 * fix_0_298631336; - tmp10 = tmp10 * -fix_0_899976223; - tmp11 = tmp11 * -fix_2_562915447; - tmp12 = tmp12 * -fix_0_390180644; - tmp13 = tmp13 * -fix_1_961570560; + tmp0 = tmp0 * Fix_1_501321110; + tmp1 = tmp1 * Fix_3_072711026; + tmp2 = tmp2 * Fix_2_053119869; + tmp3 = tmp3 * Fix_0_298631336; + tmp10 = tmp10 * -Fix_0_899976223; + tmp11 = tmp11 * -Fix_2_562915447; + tmp12 = tmp12 * -Fix_0_390180644; + tmp13 = tmp13 * -Fix_1_961570560; tmp12 += z1; tmp13 += z1; @@ -204,23 +207,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils block[40 + x] = (tmp2 + tmp11 + tmp12) >> (Bits + Pass1Bits); block[56 + x] = (tmp3 + tmp10 + tmp13) >> (Bits + Pass1Bits); } - } - private const int w1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) - private const int w2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) - private const int w3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) - private const int w5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) - private const int w6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) - private const int w7 = 565; // 2048*sqrt(2)*cos(7*pi/16) - - private const int w1pw7 = w1 + w7; - private const int w1mw7 = w1 - w7; - private const int w2pw6 = w2 + w6; - private const int w2mw6 = w2 - w6; - private const int w3pw5 = w3 + w5; - private const int w3mw5 = w3 - w5; - - private const int r2 = 181; // 256/sqrt(2) + + private const int W1 = 2841; // 2048*sqrt(2)*cos(1*pi/16) + private const int W2 = 2676; // 2048*sqrt(2)*cos(2*pi/16) + private const int W3 = 2408; // 2048*sqrt(2)*cos(3*pi/16) + private const int W5 = 1609; // 2048*sqrt(2)*cos(5*pi/16) + private const int W6 = 1108; // 2048*sqrt(2)*cos(6*pi/16) + private const int W7 = 565; // 2048*sqrt(2)*cos(7*pi/16) + + private const int W1pw7 = W1 + W7; + private const int W1mw7 = W1 - W7; + private const int W2pw6 = W2 + W6; + private const int W2mw6 = W2 - W6; + private const int W3pw5 = W3 + W5; + private const int W3mw5 = W3 - W5; + + private const int R2 = 181; // 256/sqrt(2) /// /// Performs a 2-D Inverse Discrete Cosine Transformation. @@ -235,7 +238,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// ASSP, Vol. ASSP- 32, pp. 803-816, Aug. 1984. /// /// The source block of coefficients - // [Obsolete("Looks like this method produces really bad results for bigger values!")] public static void TransformIDCTInplace(Span src) { // Horizontal 1-D IDCT. @@ -270,19 +272,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int x7 = src[y8 + 3]; // Stage 1. - int x8 = w7 * (x4 + x5); - x4 = x8 + (w1mw7 * x4); - x5 = x8 - (w1pw7 * x5); - x8 = w3 * (x6 + x7); - x6 = x8 - (w3mw5 * x6); - x7 = x8 - (w3pw5 * x7); + int x8 = W7 * (x4 + x5); + x4 = x8 + (W1mw7 * x4); + x5 = x8 - (W1pw7 * x5); + x8 = W3 * (x6 + x7); + x6 = x8 - (W3mw5 * x6); + x7 = x8 - (W3pw5 * x7); // Stage 2. x8 = x0 + x1; x0 -= x1; - x1 = w6 * (x3 + x2); - x2 = x1 - (w2pw6 * x2); - x3 = x1 + (w2mw6 * x3); + x1 = W6 * (x3 + x2); + x2 = x1 - (W2pw6 * x2); + x3 = x1 + (W2mw6 * x3); x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; @@ -293,8 +295,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils x8 -= x3; x3 = x0 + x2; x0 -= x2; - x2 = ((r2 * (x4 + x5)) + 128) >> 8; - x4 = ((r2 * (x4 - x5)) + 128) >> 8; + x2 = ((R2 * (x4 + x5)) + 128) >> 8; + x4 = ((R2 * (x4 - x5)) + 128) >> 8; // Stage 4. src[y8 + 0] = (x7 + x1) >> 8; @@ -325,19 +327,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int y7 = src[24 + x]; // Stage 1. - int y8 = (w7 * (y4 + y5)) + 4; - y4 = (y8 + (w1mw7 * y4)) >> 3; - y5 = (y8 - (w1pw7 * y5)) >> 3; - y8 = (w3 * (y6 + y7)) + 4; - y6 = (y8 - (w3mw5 * y6)) >> 3; - y7 = (y8 - (w3pw5 * y7)) >> 3; + int y8 = (W7 * (y4 + y5)) + 4; + y4 = (y8 + (W1mw7 * y4)) >> 3; + y5 = (y8 - (W1pw7 * y5)) >> 3; + y8 = (W3 * (y6 + y7)) + 4; + y6 = (y8 - (W3mw5 * y6)) >> 3; + y7 = (y8 - (W3pw5 * y7)) >> 3; // Stage 2. y8 = y0 + y1; y0 -= y1; - y1 = (w6 * (y3 + y2)) + 4; - y2 = (y1 - (w2pw6 * y2)) >> 3; - y3 = (y1 + (w2mw6 * y3)) >> 3; + y1 = (W6 * (y3 + y2)) + 4; + y2 = (y1 - (W2pw6 * y2)) >> 3; + y3 = (y1 + (W2mw6 * y3)) >> 3; y1 = y4 + y6; y4 -= y6; y6 = y5 + y7; @@ -348,8 +350,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils y8 -= y3; y3 = y0 + y2; y0 -= y2; - y2 = ((r2 * (y4 + y5)) + 128) >> 8; - y4 = ((r2 * (y4 - y5)) + 128) >> 8; + y2 = ((R2 * (y4 + y5)) + 128) >> 8; + y4 = ((R2 * (y4 - y5)) + 128) >> 8; // Stage 4. src[x] = (y7 + y1) >> 14; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index 527cc3fed..4de576b25 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -1,13 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils { /// @@ -34,7 +33,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Transpose 8x8 block stored linearly in a (inplace) /// - /// internal static void Transpose8x8(Span data) { for (int i = 1; i < 8; i++) @@ -43,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int j = 0; j < i; j++) { float tmp = data[i8 + j]; - data[i8 + j] = data[j * 8 + i]; - data[j * 8 + i] = tmp; + data[i8 + j] = data[(j * 8) + i]; + data[(j * 8) + i] = tmp; } } } @@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils int i8 = i * 8; for (int j = 0; j < 8; j++) { - dest[j * 8 + i] = src[i8 + j]; + dest[(j * 8) + i] = src[i8 + j]; } } } @@ -67,9 +65,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils /// /// Copies color values from block to the destination image buffer. /// - /// - /// - /// internal static unsafe void CopyColorsTo(ref Block8x8F block, Span buffer, int stride) { fixed (Block8x8F* p = &block) @@ -128,11 +123,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } /// - /// Rounds a rational number defined as dividend/divisor into an integer + /// Rounds a rational number defined as dividend/divisor into an integer. /// - /// The dividend - /// The divisor - /// + /// The dividend. + /// The divisor. + /// The rounded value. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int RationalRound(int dividend, int divisor) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs index 16fd352a9..13685c8e8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Collections.Generic; using System.Linq; @@ -48,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils TestImageProvider provider, LibJpegTools.SpectralData data, ITestOutputHelper output = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (LibJpegTools.ComponentData comp in data.Components) { @@ -71,4 +74,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs index 64a394cc9..2e8c0de27 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngChunkTypeTests.cs @@ -1,4 +1,7 @@ -using System.Buffers.Binary; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers.Binary; using System.Text; using SixLabors.ImageSharp.Formats.Png; using Xunit; @@ -26,4 +29,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png return (PngChunkType)BinaryPrimitives.ReadInt32BigEndian(Encoding.ASCII.GetBytes(text)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 660d5b724..ee4001c20 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Buffers.Binary; using System.IO; using System.Text; @@ -6,8 +9,8 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { public partial class PngDecoderTests @@ -15,19 +18,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // Contains the png marker, IHDR and pHYs chunks of a 1x1 pixel 32bit png 1 a single black pixel. private static readonly byte[] Raw1X1PngIhdrAndpHYs = { - // PNG Identifier + // PNG Identifier 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // IHDR 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00, 0x00, + // IHDR CRC 0x90, 0x77, 0x53, 0xDE, // pHYS 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, + // pHYS CRC 0xC7, 0x6F, 0xA8, 0x64 }; @@ -53,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [InlineData((uint)PngChunkType.Header)] // IHDR [InlineData((uint)PngChunkType.Palette)] // PLTE - // [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this + /* [InlineData(PngChunkTypes.Data)] TODO: Figure out how to test this */ public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType) { string chunkName = GetChunkTypeName(chunkType); @@ -84,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteHeaderChunk(MemoryStream memStream) { - // Writes a 1x1 32bit png header chunk containing a single black pixel + // Writes a 1x1 32bit png header chunk containing a single black pixel. memStream.Write(Raw1X1PngIhdrAndpHYs, 0, Raw1X1PngIhdrAndpHYs.Length); } @@ -99,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png private static void WriteDataChunk(MemoryStream memStream) { - // Writes a 1x1 32bit png data chunk containing a single black pixel + // Writes a 1x1 32bit png data chunk containing a single black pixel. memStream.Write(Raw1X1PngIdatAndIend, 0, Raw1X1PngIdatAndIend.Length); memStream.Position = 0; } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 3b05b00ce..46112bdd8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,22 +1,28 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System.IO; +using Microsoft.DotNet.RemoteExecutor; + using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { public partial class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private static PngDecoder PngDecoder => new PngDecoder(); + public static readonly string[] CommonTestImages = { TestImages.Png.Splash, @@ -52,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }; public static readonly string[] TestImages64Bpp = -{ + { TestImages.Png.Rgba64Bpp, TestImages.Png.Rgb48BppTrans }; @@ -86,9 +92,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); @@ -110,9 +116,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.Interlaced, PixelTypes.Rgba32)] public void Decode_Interlaced_ImageIsCorrect(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -122,9 +128,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(TestImages48Bpp), PixelTypes.Rgb48)] public void Decode_48Bpp(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -134,9 +140,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(TestImages64Bpp), PixelTypes.Rgba64)] public void Decode_64Bpp(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -146,9 +152,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(TestImagesL8BitInterlaced), PixelTypes.Rgba32)] public void Decoder_L8bitInterlaced(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -158,9 +164,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(TestImagesL16Bit), PixelTypes.Rgb48)] public void Decode_L16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -170,9 +176,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(TestImagesGrayAlpha16Bit), PixelTypes.Rgba64)] public void Decode_GrayAlpha16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -182,9 +188,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes)] public void Decoder_CanDecodeGrey8bitWithAlpha(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -194,9 +200,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.Splash, PixelTypes)] public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -223,12 +229,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] public void Issue1014(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel + { + System.Exception ex = Record.Exception( + () => + { + using (Image image = provider.GetImage(PngDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); + } + }); + Assert.Null(ex); + } + + [Theory] + [WithFile(TestImages.Png.Issue1127, PixelTypes.Rgba32)] + public void Issue1127(TestImageProvider provider) + where TPixel : unmanaged, IPixel { System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(new PngDecoder())) + using (Image image = provider.GetImage(PngDecoder)) { image.DebugSave(provider); image.CompareToOriginal(provider, ImageComparer.Exact); @@ -236,5 +259,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }); Assert.Null(ex); } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(PngDecoder)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + public void PngDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + image.CompareToOriginal(provider); + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 1fa131c91..5a31d2d93 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -85,8 +85,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, - { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithSolidFilledImages(nameof(PngColorTypes), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(PngColorTypes), 7, 5, PixelTypes.Rgba32)] public void WorksWithDifferentSizes(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TestPngEncoderCore( provider, @@ -112,7 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(nameof(PngColorTypes), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)] public void IsNotBoundToSinglePixelType(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)] public void WorksWithAllFilterMethods(TestImageProvider provider, PngFilterMethod pngFilterMethod) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(nameof(CompressionLevels), 24, 24, PixelTypes.Rgba32)] public void WorksWithAllCompressionLevels(TestImageProvider provider, int compressionLevel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithTestPatternImages(24, 24, PixelTypes.Rgba32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] [WithTestPatternImages(24, 24, PixelTypes.Rgba64, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] public void WorksWithAllBitDepths(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [WithBlankImages(1, 1, PixelTypes.La16, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit8)] [WithBlankImages(1, 1, PixelTypes.La32, PngColorType.GrayscaleWithAlpha, PngBitDepth.Bit16)] public void InfersColorTypeAndBitDepth(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Stream stream = new MemoryStream()) { @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.Palette8Bpp, nameof(PaletteLargeOnly), PixelTypes.Rgba32)] public void PaletteColorType_WuQuantizer(TestImageProvider provider, int paletteSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // TODO: Investigate WuQuantizer to see if we can reduce memory pressure. if (TestEnvironment.RunsOnCI && !TestEnvironment.Is64BitProcess) @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void WritesFileMarker(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) using (var ms = new MemoryStream()) @@ -284,7 +284,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png image.Save(ms, new PngEncoder()); byte[] data = ms.ToArray().Take(8).ToArray(); - byte[] expected = { + byte[] expected = + { 0x89, // Set the high bit. 0x50, // P 0x4E, // N @@ -403,6 +404,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png } } + [Theory] + [WithTestPatternImages(587, 821, PixelTypes.Rgba32)] + [WithTestPatternImages(677, 683, PixelTypes.Rgba32)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); + foreach (PngInterlaceMode interlaceMode in InterlaceMode) + { + TestPngEncoderCore( + provider, + PngColorType.Rgb, + PngFilterMethod.Adaptive, + PngBitDepth.Bit8, + interlaceMode, + appendPixelType: true, + appendPngColorType: true); + } + } + private static void TestPngEncoderCore( TestImageProvider provider, PngColorType pngColorType, @@ -417,7 +438,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png bool appendCompressionLevel = false, bool appendPaletteSize = false, bool appendPngBitDepth = false) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -427,7 +448,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png FilterMethod = pngFilterMethod, CompressionLevel = compressionLevel, BitDepth = bitDepth, - Quantizer = new WuQuantizer(paletteSize), + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = paletteSize }), InterlaceMethod = interlaceMode }; @@ -444,6 +465,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png // Compare to the Magick reference decoder. IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + // We compare using both our decoder and the reference decoder as pixel transformation // occurs within the encoder itself leaving the input image unaffected. // This means we are benefiting from testing our decoder also. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 4b11ad3e2..5f5d5fd3d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -16,8 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public static readonly TheoryData RatioFiles = new TheoryData { - { TestImages.Png.Splash, 11810, 11810 , PixelResolutionUnit.PixelsPerMeter}, - { TestImages.Png.Ratio1x4, 1, 4 , PixelResolutionUnit.AspectRatio}, + { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, + { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, { TestImages.Png.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] public void Decoder_CanReadTextData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new PngDecoder())) { @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] public void Encoder_PreservesTextData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new PngDecoder(); using (Image input = provider.GetImage(decoder)) @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.InvalidTextData, PixelTypes.Rgba32)] public void Decoder_IgnoresInvalidTextData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new PngDecoder())) { @@ -120,15 +120,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] public void Encode_UseCompression_WhenTextIsGreaterThenThreshold_Works(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var decoder = new PngDecoder(); using (Image input = provider.GetImage(decoder)) using (var memoryStream = new MemoryStream()) { - // this will be a zTXt chunk. + // This will be a zTXt chunk. var expectedText = new PngTextData("large-text", new string('c', 100), string.Empty, string.Empty); - // this will be a iTXt chunk. + + // This will be a iTXt chunk. var expectedTextNoneLatin = new PngTextData("large-text-non-latin", new string('Ф', 100), "language-tag", "translated-keyword"); PngMetadata inputMetadata = input.Metadata.GetFormatMetadata(PngFormat.Instance); inputMetadata.TextData.Add(expectedText); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 1f8147ea9..a50b1059f 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System.IO; -using Xunit; - +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Formats.Png; +using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { @@ -16,104 +15,104 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void GeneralTest(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // does saving a file then reopening mean both files are identical??? using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) + using (var img2 = Image.Load(ms, new PngDecoder())) { ImageComparer.Tolerant().VerifySimilarity(image, img2); + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); } } } - // JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the - // paletted image has alpha of 0 - //[Theory] - //[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - //public void CanSaveIndexedPng(TestImageProvider provider) - // where TPixel : struct, IPixel - //{ - // // does saving a file then reopening mean both files are identical??? - // using (Image image = provider.GetImage()) - // using (MemoryStream ms = new MemoryStream()) - // { - // // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - // image.Save(ms, new PngEncoder() { PaletteSize = 256 }); - // ms.Position = 0; - // using (Image img2 = Image.Load(ms, new PngDecoder())) - // { - // ImageComparer.VerifySimilarity(image, img2, 0.03f); - // } - // } - //} + /* JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the + paletted image has alpha of 0 + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + public void CanSaveIndexedPng(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // does saving a file then reopening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + image.Save(ms, new PngEncoder() { PaletteSize = 256 }); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + ImageComparer.VerifySimilarity(image, img2, 0.03f); + } + } + }*/ - // JJS: Commented out for now since the test does not take into lossy nature of indexing. - //[Theory] - //[WithTestPatternImages(100, 100, PixelTypes.Color)] - //public void CanSaveIndexedPngTwice(TestImageProvider provider) - // where TPixel : struct, IPixel - //{ - // // does saving a file then reopening mean both files are identical??? - // using (Image source = provider.GetImage()) - // using (MemoryStream ms = new MemoryStream()) - // { - // source.Metadata.Quality = 256; - // source.Save(ms, new PngEncoder(), new PngEncoderOptions { - // Threshold = 200 - // }); - // ms.Position = 0; - // using (Image img1 = Image.Load(ms, new PngDecoder())) - // { - // using (MemoryStream ms2 = new MemoryStream()) - // { - // img1.Save(ms2, new PngEncoder(), new PngEncoderOptions - // { - // Threshold = 200 - // }); - // ms2.Position = 0; - // using (Image img2 = Image.Load(ms2, new PngDecoder())) - // { - // using (PixelAccessor pixels1 = img1.Lock()) - // using (PixelAccessor pixels2 = img2.Lock()) - // { - // for (int y = 0; y < img1.Height; y++) - // { - // for (int x = 0; x < img1.Width; x++) - // { - // Assert.Equal(pixels1[x, y], pixels2[x, y]); - // } - // } - // } - // } - // } - // } - // } - //} + /* JJS: Commented out for now since the test does not take into lossy nature of indexing. + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Color)] + public void CanSaveIndexedPngTwice(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + // does saving a file then reopening mean both files are identical??? + using (Image source = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + source.Metadata.Quality = 256; + source.Save(ms, new PngEncoder(), new PngEncoderOptions { + Threshold = 200 + }); + ms.Position = 0; + using (Image img1 = Image.Load(ms, new PngDecoder())) + { + using (MemoryStream ms2 = new MemoryStream()) + { + img1.Save(ms2, new PngEncoder(), new PngEncoderOptions + { + Threshold = 200 + }); + ms2.Position = 0; + using (Image img2 = Image.Load(ms2, new PngDecoder())) + { + using (PixelAccessor pixels1 = img1.Lock()) + using (PixelAccessor pixels2 = img2.Lock()) + { + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Assert.Equal(pixels1[x, y], pixels2[x, y]); + } + } + } + } + } + } + } + }*/ [Theory] [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void Resize(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // does saving a file then reopening mean both files are identical??? using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { // image.Save(provider.Utility.GetTestOutputFileName("png")); image.Mutate(x => x.Resize(100, 100)); - // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); + // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); image.Save(ms, new PngEncoder()); ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) + using (var img2 = Image.Load(ms, new PngDecoder())) { ImageComparer.Tolerant().VerifySimilarity(image, img2); } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 03ad10de4..bcd98d714 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -1,25 +1,29 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming +using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Tga; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { using static TestImages.Tga; public class TgaDecoderTests { + private static TgaDecoder TgaDecoder => new TgaDecoder(); + [Theory] [WithFile(Grey, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Uncompressed_MonoChrome(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -29,9 +33,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit15, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Uncompressed_15Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -41,9 +45,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit15Rle, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -53,9 +57,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit16, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Uncompressed_16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -65,9 +69,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit16PalRle, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -77,9 +81,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit24, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Uncompressed_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -89,9 +93,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -101,9 +105,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit24TopLeft, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -113,9 +117,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_Uncompressed_32Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -125,9 +129,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(GreyRle, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_MonoChrome(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -137,9 +141,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit16Rle, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -149,9 +153,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit24Rle, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -161,9 +165,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit32Rle, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_RunLengthEncoded_32Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -173,9 +177,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit16Pal, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_WithPalette_16Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); @@ -185,13 +189,54 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit24Pal, PixelTypes.Rgba32)] public void TgaDecoder_CanDecode_WithPalette_24Bit(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TgaDecoder())) + using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); TgaTestUtils.CompareWithReferenceDecoder(provider, image); } } + + [Theory] + [WithFile(Bit16, PixelTypes.Rgba32)] + [WithFile(Bit24, PixelTypes.Rgba32)] + [WithFile(Bit32, PixelTypes.Rgba32)] + public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(10); + ImageFormatException ex = Assert.Throws(() => provider.GetImage(TgaDecoder)); + Assert.IsType(ex.InnerException); + } + + [Theory] + [WithFile(Bit24, PixelTypes.Rgba32)] + [WithFile(Bit32, PixelTypes.Rgba32)] + public void TgaDecoder_CanDecode_WithLimitedAllocatorBufferCapacity(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + static void RunTest(string providerDump, string nonContiguousBuffersStr) + { + TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); + + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + + using Image image = provider.GetImage(TgaDecoder); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider); + } + } + + string providerDump = BasicSerializer.Serialize(provider); + RemoteExecutor.Invoke( + RunTest, + providerDump, + "Disco") + .Dispose(); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 9d34684f7..f123370d1 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System.IO; using SixLabors.ImageSharp.Formats.Tga; @@ -10,6 +8,7 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { using static TestImages.Tga; @@ -70,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { input.Save(memStream, options); memStream.Position = 0; - using (Image output = Image.Load(memStream)) + using (var output = Image.Load(memStream)) { TgaMetadata meta = output.Metadata.GetTgaMetadata(); Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel); @@ -82,44 +81,56 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit8_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8) - // using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); + + // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit8_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8) - // using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); + + // Using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok. + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16) - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24) - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); [Theory] [WithFile(Bit32, PixelTypes.Rgba32)] public void Encode_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32) - where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + where TPixel : unmanaged, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + + [Theory] + [WithFile(Bit32, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel32)] + [WithFile(Bit24, PixelTypes.Rgba32, TgaBitsPerPixel.Pixel24)] + public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + provider.LimitAllocatorBufferCapacity().InPixelsSqrt(100); + TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); + } private static void TestTgaEncoderCore( TestImageProvider provider, @@ -127,11 +138,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga TgaCompression compression = TgaCompression.None, bool useExactComparer = true, float compareTolerance = 0.01f) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression}; + var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression }; using (var memStream = new MemoryStream()) { diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs index c227b7957..4797397e1 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs @@ -11,11 +11,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { public class TgaFileHeaderTests { - private static readonly byte[] Data = { + private static readonly byte[] Data = + { 0, 0, 15 // invalid tga image type - }; + }; private MemoryStream Stream { get; } = new MemoryStream(Data); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs index a2f2e86d7..cb3986b1f 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.IO; @@ -11,11 +14,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { public static class TgaTestUtils { - public static void CompareWithReferenceDecoder(TestImageProvider provider, - Image image, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : struct, IPixel + public static void CompareWithReferenceDecoder( + TestImageProvider provider, + Image image, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel { string path = TestImageProvider.GetFilePathOrNull(provider); if (path == null) @@ -23,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); } - TestFile testFile = TestFile.Create(path); + var testFile = TestFile.Create(path); Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); if (useExactComparer) { @@ -36,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var magickImage = new MagickImage(fileInfo)) { diff --git a/tests/ImageSharp.Tests/GlobalSuppressions.cs b/tests/ImageSharp.Tests/GlobalSuppressions.cs index 2709d32eb..95fba0dff 100644 --- a/tests/ImageSharp.Tests/GlobalSuppressions.cs +++ b/tests/ImageSharp.Tests/GlobalSuppressions.cs @@ -1,10 +1,12 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given // a specific target and scoped to a namespace, type, member, etc. - +#pragma warning disable SA1404 // Code analysis suppression should have justification [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1026:Theory methods should use all of their parameters")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "xUnit1013:Public method should be marked as test")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Assertions", "xUnit2013:Do not use equality check to check for collection size.")] - +#pragma warning restore SA1404 // Code analysis suppression should have justification diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs index e4892e561..851aba6ba 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Tests { public class GraphicsOptionsTests { - private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); private readonly GraphicsOptions newGraphicsOptions = new GraphicsOptions(); private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests GraphicsOptions actual = expected.DeepClone(); - Assert.Equal(expected, actual, graphicsOptionsComparer); + Assert.Equal(expected, actual, GraphicsOptionsComparer); } [Fact] @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests actual.BlendPercentage = .25F; actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; - Assert.NotEqual(expected, actual, graphicsOptionsComparer); + Assert.NotEqual(expected, actual, GraphicsOptionsComparer); } } } diff --git a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs index 3cfce6b8e..fbe259d2b 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelExecutionSettingsTests.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using Xunit; namespace SixLabors.ImageSharp.Tests.Helpers diff --git a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs similarity index 50% rename from tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs rename to tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 93ddaefe6..08d64a738 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelHelperTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -2,44 +2,48 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Collections.Concurrent; using System.Linq; using System.Numerics; using System.Threading; -using SixLabors.ImageSharp.Advanced.ParallelUtils; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - using Xunit; using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Helpers { - public class ParallelHelperTests + public class ParallelRowIteratorTests { - private readonly ITestOutputHelper Output; + public delegate void RowIntervalAction(RowInterval rows, Span span); + + private readonly ITestOutputHelper output; - public ParallelHelperTests(ITestOutputHelper output) + public ParallelRowIteratorTests(ITestOutputHelper output) { - this.Output = output; + this.output = output; } /// /// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength /// - public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = - new TheoryData - { - { 1, 0, 100, -1, 100 }, - { 2, 0, 9, 5, 4 }, - { 4, 0, 19, 5, 4 }, - { 2, 10, 19, 5, 4 }, - { 4, 0, 200, 50, 50 }, - { 4, 123, 323, 50, 50 }, - { 4, 0, 1201, 301, 298 }, - { 8, 10, 236, 29, 23 } - }; + public static TheoryData IterateRows_OverMinimumPixelsLimit_Data = + new TheoryData + { + { 1, 0, 100, -1, 100, 1 }, + { 2, 0, 9, 5, 4, 2 }, + { 4, 0, 19, 5, 4, 4 }, + { 2, 10, 19, 5, 4, 2 }, + { 4, 0, 200, 50, 50, 4 }, + { 4, 123, 323, 50, 50, 4 }, + { 4, 0, 1201, 301, 298, 4 }, + { 8, 10, 236, 29, 23, 8 }, + { 16, 0, 209, 14, 13, 15 }, + { 24, 0, 209, 9, 2, 24 }, + { 32, 0, 209, 7, 6, 30 }, + { 64, 0, 209, 4, 1, 53 }, + }; [Theory] [MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] @@ -48,7 +52,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -59,22 +64,26 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelHelper.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } - Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } [Theory] @@ -84,7 +93,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -93,20 +103,23 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, minY, 10, maxY - minY); - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelHelper.IterateRows( + void RowAction(RowInterval rows) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( rectangle, - parallelSettings, - rows => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); + in parallelSettings, + in operation); Assert.Equal(expectedData, actualData); } @@ -118,7 +131,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -127,30 +141,28 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, minY, 10, maxY - minY); - var bufferHashes = new ConcurrentBag(); - int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= minY); - Assert.True(rows.Max <= maxY); - bufferHashes.Add(buffer.GetHashCode()); + void RowAction(RowInterval rows, Span buffer) + { + Assert.True(rows.Min >= minY); + Assert.True(rows.Max <= maxY); - int step = rows.Max - rows.Min; - int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; + int step = rows.Max - rows.Min; + int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); - Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); - int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); - Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); + Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } [Theory] @@ -160,7 +172,8 @@ namespace SixLabors.ImageSharp.Tests.Helpers int minY, int maxY, int expectedStepLength, - int expectedLastStepLength) + int expectedLastStepLength, + int expectedNumberOfSteps) { var parallelSettings = new ParallelExecutionSettings( maxDegreeOfParallelism, @@ -172,32 +185,35 @@ namespace SixLabors.ImageSharp.Tests.Helpers int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); var actualData = new int[maxY]; - ParallelHelper.IterateRowsWithTempBuffer( + void RowAction(RowInterval rows, Span buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y] = y; + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - for (int y = rows.Min; y < rows.Max; y++) - { - actualData[y] = y; - } - }); + in parallelSettings, + in operation); Assert.Equal(expectedData, actualData); - } public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new TheoryData - { - { 2, 200, 50, 2, 1, -1, 2 }, - { 2, 200, 200, 1, 1, -1, 1 }, - { 4, 200, 100, 4, 2, 2, 2 }, - { 4, 300, 100, 8, 3, 3, 2 }, - { 2, 5000, 1, 4500, 1, -1, 4500 }, - { 2, 5000, 1, 5000, 1, -1, 5000 }, - { 2, 5000, 1, 5001, 2, 2501, 2500 }, - }; + { + { 2, 200, 50, 2, 1, -1, 2 }, + { 2, 200, 200, 1, 1, -1, 1 }, + { 4, 200, 100, 4, 2, 2, 2 }, + { 4, 300, 100, 8, 3, 3, 2 }, + { 2, 5000, 1, 4500, 1, -1, 4500 }, + { 2, 5000, 1, 5000, 1, -1, 5000 }, + { 2, 5000, 1, 5001, 2, 2501, 2500 }, + }; [Theory] [MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] @@ -219,20 +235,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers int actualNumberOfSteps = 0; - ParallelHelper.IterateRows( - rectangle, - parallelSettings, - rows => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); + void RowAction(RowInterval rows) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } @@ -256,33 +276,38 @@ namespace SixLabors.ImageSharp.Tests.Helpers var rectangle = new Rectangle(0, 0, width, height); int actualNumberOfSteps = 0; - ParallelHelper.IterateRowsWithTempBuffer( - rectangle, - parallelSettings, - (RowInterval rows, Memory buffer) => - { - Assert.True(rows.Min >= 0); - Assert.True(rows.Max <= height); - int step = rows.Max - rows.Min; - int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + void RowAction(RowInterval rows, Span buffer) + { + Assert.True(rows.Min >= 0); + Assert.True(rows.Max <= height); - Interlocked.Increment(ref actualNumberOfSteps); - Assert.Equal(expected, step); - }); + int step = rows.Max - rows.Min; + int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; + + Interlocked.Increment(ref actualNumberOfSteps); + Assert.Equal(expected, step); + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } public static readonly TheoryData IterateRectangularBuffer_Data = new TheoryData - { - { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox - { 2, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 291, 226 }, - { 16, 582, 453, 10, 10, 1, 226 }, - { 16, 1, 453, 0, 10, 1, 226 }, - }; + { + { 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox + { 2, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 291, 226 }, + { 16, 582, 453, 10, 10, 1, 226 }, + { 16, 1, 453, 0, 10, 1, 226 }, + }; [Theory] [MemberData(nameof(IterateRectangularBuffer_Data))] @@ -319,18 +344,24 @@ namespace SixLabors.ImageSharp.Tests.Helpers // Fill actual data using IterateRows: var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); - ParallelHelper.IterateRows(rect, settings, - rows => - { - this.Output.WriteLine(rows.ToString()); - for (int y = rows.Min; y < rows.Max; y++) - { - FillRow(y, actual); - } - }); + void RowAction(RowInterval rows) + { + this.output.WriteLine(rows.ToString()); + for (int y = rows.Min; y < rows.Max; y++) + { + FillRow(y, actual); + } + } + + var operation = new TestRowIntervalOperation(RowAction); + + ParallelRowIterator.IterateRowIntervals( + rect, + settings, + in operation); // Assert: - TestImageExtensions.CompareBuffers(expected.GetSpan(), actual.GetSpan()); + TestImageExtensions.CompareBuffers(expected.GetSingleSpan(), actual.GetSingleSpan()); } } @@ -341,12 +372,18 @@ namespace SixLabors.ImageSharp.Tests.Helpers [InlineData(10, -10)] public void IterateRowsRequiresValidRectangle(int width, int height) { - var parallelSettings = new ParallelExecutionSettings(); + var parallelSettings = default(ParallelExecutionSettings); var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRows(rect, parallelSettings, rows => { })); + () => ParallelRowIterator.IterateRowIntervals(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } @@ -358,14 +395,42 @@ namespace SixLabors.ImageSharp.Tests.Helpers [InlineData(10, -10)] public void IterateRowsWithTempBufferRequiresValidRectangle(int width, int height) { - var parallelSettings = new ParallelExecutionSettings(); + var parallelSettings = default(ParallelExecutionSettings); var rect = new Rectangle(0, 0, width, height); + void RowAction(RowInterval rows, Span memory) + { + } + + var operation = new TestRowIntervalOperation(RowAction); + ArgumentOutOfRangeException ex = Assert.Throws( - () => ParallelHelper.IterateRowsWithTempBuffer(rect, parallelSettings, (rows, memory) => { })); + () => ParallelRowIterator.IterateRowIntervals, Rgba32>(rect, in parallelSettings, in operation)); Assert.Contains(width <= 0 ? "Width" : "Height", ex.Message); } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + { + private readonly Action action; + + public TestRowIntervalOperation(Action action) + => this.action = action; + + public void Invoke(in RowInterval rows) => this.action(rows); + } + + private readonly struct TestRowIntervalOperation : IRowIntervalOperation + where TBuffer : unmanaged + { + private readonly RowIntervalAction action; + + public TestRowIntervalOperation(RowIntervalAction action) + => this.action = action; + + public void Invoke(in RowInterval rows, Span span) + => this.action(rows, span); + } } } diff --git a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs index 0bb3f49d6..fd1eb546b 100644 --- a/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs +++ b/tests/ImageSharp.Tests/Helpers/RowIntervalTests.cs @@ -10,31 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class RowIntervalTests { - [Theory] - [InlineData(10, 20, 5, 10)] - [InlineData(1, 10, 0, 10)] - [InlineData(1, 10, 5, 8)] - [InlineData(1, 1, 0, 1)] - [InlineData(10, 20, 9, 10)] - [InlineData(10, 20, 0, 1)] - public void GetMultiRowSpan(int width, int height, int min, int max) - { - using (Buffer2D buffer = Configuration.Default.MemoryAllocator.Allocate2D(width, height)) - { - var rows = new RowInterval(min, max); - - Span span = buffer.GetMultiRowSpan(rows); - - ref int expected0 = ref buffer.GetSpan()[min * width]; - int expectedLength = (max - min) * width; - - ref int actual0 = ref span[0]; - - Assert.Equal(span.Length, expectedLength); - Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); - } - } - [Fact] public void Slice1() { diff --git a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs index 6c7a1f275..e2486fb4a 100644 --- a/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs +++ b/tests/ImageSharp.Tests/Helpers/TolerantMathTests.cs @@ -4,8 +4,8 @@ using System; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Helpers { public class TolerantMathTests @@ -165,4 +165,4 @@ namespace SixLabors.ImageSharp.Tests.Helpers Assert.Equal(expected, actual); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs index f2e98b131..bc1ffda48 100644 --- a/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/Vector4UtilsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers { public class Vector4UtilsTests { - private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); [Theory] [InlineData(0)] @@ -21,11 +21,15 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => { Vector4Utils.Premultiply(ref v); return v; }).ToArray(); + Vector4[] expected = source.Select(v => + { + Vector4Utilities.Premultiply(ref v); + return v; + }).ToArray(); - Vector4Utils.Premultiply(source); + Vector4Utilities.Premultiply(source); - Assert.Equal(expected, source, this.ApproximateFloatComparer); + Assert.Equal(expected, source, this.approximateFloatComparer); } [Theory] @@ -36,11 +40,15 @@ namespace SixLabors.ImageSharp.Tests.Helpers { var rnd = new Random(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => { Vector4Utils.UnPremultiply(ref v); return v; }).ToArray(); + Vector4[] expected = source.Select(v => + { + Vector4Utilities.UnPremultiply(ref v); + return v; + }).ToArray(); - Vector4Utils.UnPremultiply(source); + Vector4Utilities.UnPremultiply(source); - Assert.Equal(expected, source, this.ApproximateFloatComparer); + Assert.Equal(expected, source, this.approximateFloatComparer); } } } diff --git a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs index 57e9dbad6..62e204843 100644 --- a/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs +++ b/tests/ImageSharp.Tests/IO/DoubleBufferedStreamReaderTests.cs @@ -108,7 +108,6 @@ namespace SixLabors.ImageSharp.Tests.IO for (int i = 0, o = 0; i < expected.Length / 2; i++, o += 2) { - Assert.Equal(2, reader.Read(buffer, 0, 2)); Assert.Equal(expected[o], buffer[0]); Assert.Equal(expected[o + 1], buffer[1]); diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs similarity index 100% rename from tests/ImageSharp.Tests/IO/LocalFileSystem.cs rename to tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 035babcb8..bc2eec79d 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index 80ab860ef..9ea6a305c 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Linq; @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame((ImageFrame)null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); } [Fact] @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(data); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(new Rgba32[0]); }); - Assert.StartsWith("Value 0 must be greater than or equal to 100.", ex.Message); + Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); } [Fact] @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.InsertFrame(1, null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"frame\" must be not null.", ex.Message); } [Fact] @@ -104,11 +104,7 @@ namespace SixLabors.ImageSharp.Tests { new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 1, 1) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 1, 1) }); }); Assert.StartsWith("Frame must have the same dimensions as the image.", ex.Message); @@ -134,11 +130,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); collection.RemoveFrame(0); Assert.Equal(1, collection.Count); @@ -149,11 +141,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); Assert.Equal(collection.RootFrame, collection[0]); } @@ -163,11 +151,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); Assert.Equal(2, collection.Count); } @@ -177,11 +161,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); collection.Dispose(); @@ -193,11 +173,7 @@ namespace SixLabors.ImageSharp.Tests { var collection = new ImageFrameCollection( this.Image, - new[] - { - new ImageFrame(Configuration.Default, 10, 10), - new ImageFrame(Configuration.Default, 10, 10) - }); + new[] { new ImageFrame(Configuration.Default, 10, 10), new ImageFrame(Configuration.Default, 10, 10) }); IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); collection.Dispose(); @@ -206,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests framesSnapShot, f => { - // the pixel source of the frame is null after its been disposed. + // The pixel source of the frame is null after its been disposed. Assert.Null(f.PixelBuffer); }); } @@ -214,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void CloneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -230,7 +206,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void ExtractFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -257,10 +233,10 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Rgba32.HotPink); + this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); - this.Image.Frames[1].ComparePixelBufferTo(Rgba32.HotPink); + this.Image.Frames[1].ComparePixelBufferTo(Color.HotPink); } [Fact] diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index e41bb4c17..08e6f8e1f 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Linq; @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); var actualFrame = (ImageFrame)this.Collection[1]; @@ -55,13 +55,12 @@ namespace SixLabors.ImageSharp.Tests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Rgba32.Blue, this.Image.Width * this.Image.Height).ToArray(); + Enumerable.Repeat((Rgba32)Color.Blue, this.Image.Width * this.Image.Height).ToArray(); Assert.Equal(2, this.Collection.Count); var actualFrame = (ImageFrame)this.Collection[0]; actualFrame.ComparePixelBufferTo(expectedAllBlue); - } [Fact] @@ -91,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests this.Collection.AddFrame(null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } [Fact] @@ -115,14 +114,12 @@ namespace SixLabors.ImageSharp.Tests this.Collection.InsertFrame(1, null); }); - Assert.StartsWith("Value cannot be null.", ex.Message); + Assert.StartsWith("Parameter \"source\" must be not null.", ex.Message); } - [Fact] public void RemoveAtFrame_ThrowIfRemovingLastFrame() { - InvalidOperationException ex = Assert.Throws( () => { @@ -149,7 +146,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32 | PixelTypes.Bgr24)] public void CloneFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -170,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(10, 10, PixelTypes.Rgba32)] public void ExtractFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -204,13 +201,13 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void CreateFrame_CustomFillColor() { - this.Image.Frames.CreateFrame(Rgba32.HotPink); + this.Image.Frames.CreateFrame(Color.HotPink); Assert.Equal(2, this.Image.Frames.Count); var frame = (ImageFrame)this.Image.Frames[1]; - frame.ComparePixelBufferTo(Rgba32.HotPink); + frame.ComparePixelBufferTo(Color.HotPink); } [Fact] @@ -267,16 +264,16 @@ namespace SixLabors.ImageSharp.Tests /// /// Integration test for end-to end API validation. /// + /// The pixel type of the image. [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void ConstructGif_FromDifferentPixelTypes(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (var dest = new Image(source.GetConfiguration(), source.Width, source.Height)) { // Giphy.gif has 5 frames - ImportFrameAs(source.Frames, dest.Frames, 0); ImportFrameAs(source.Frames, dest.Frames, 1); ImportFrameAs(source.Frames, dest.Frames, 2); @@ -297,7 +294,7 @@ namespace SixLabors.ImageSharp.Tests } private static void ImportFrameAs(ImageFrameCollection source, ImageFrameCollection destination, int index) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image temp = source.CloneFrame(index)) { @@ -311,7 +308,6 @@ namespace SixLabors.ImageSharp.Tests private static void CompareGifMetadata(ImageFrame a, ImageFrame b) { // TODO: all metadata classes should be equatable! - GifFrameMetadata aData = a.Metadata.GetGifMetadata(); GifFrameMetadata bData = b.Metadata.GetGifMetadata(); diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs index d475513fa..d81defbcd 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.PixelFormats; @@ -7,6 +10,7 @@ namespace SixLabors.ImageSharp.Tests public abstract partial class ImageFrameCollectionTests : IDisposable { protected Image Image { get; } + protected ImageFrameCollection Collection { get; } public ImageFrameCollectionTests() diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs new file mode 100644 index 000000000..58d7d7981 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class ImageFrameTests + { + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + Rgba32 val = frame[3, 4]; + Assert.Equal(default(Rgba32), val); + frame[3, 4] = Color.Red; + val = frame[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { false, -1 }, + { false, 10 }, + { true, -1 }, + { true, 10 }, + }; + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Get_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = frame[x, 3]); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[x, 3] = default); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeY(bool enforceDisco, int y) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ImageFrame frame = image.Frames.RootFrame; + ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index 25bc3f604..156e51578 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -3,22 +3,21 @@ using System; using System.IO; - +using Moq; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.PixelFormats; -using Moq; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// - /// Tests the class. + /// Tests the class. /// public class ImageSaveTests : IDisposable { - private readonly Image Image; + private readonly Image image; private readonly Mock fileSystem; private readonly Mock encoder; private readonly Mock encoderNotInFormat; @@ -42,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests }; config.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); config.ImageFormatsManager.SetEncoder(this.localImageFormat.Object, this.encoder.Object); - this.Image = new Image(config, 1, 1); + this.image = new Image(config, 1, 1); } [Fact] @@ -50,38 +49,37 @@ namespace SixLabors.ImageSharp.Tests { var stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); - this.Image.Save("path.png"); + this.image.Save("path.png"); - this.encoder.Verify(x => x.Encode(this.Image, stream)); + this.encoder.Verify(x => x.Encode(this.image, stream)); } - [Fact] public void SavePathWithEncoder() { var stream = new MemoryStream(); this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); - this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + this.image.Save("path.jpg", this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); + this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); } [Fact] public void ToBase64String() { - string str = this.Image.ToBase64String(this.localImageFormat.Object); + string str = this.image.ToBase64String(this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.Image, It.IsAny())); + this.encoder.Verify(x => x.Encode(this.image, It.IsAny())); } [Fact] public void SaveStreamWithMime() { var stream = new MemoryStream(); - this.Image.Save(stream, this.localImageFormat.Object); + this.image.Save(stream, this.localImageFormat.Object); - this.encoder.Verify(x => x.Encode(this.Image, stream)); + this.encoder.Verify(x => x.Encode(this.image, stream)); } [Fact] @@ -89,14 +87,14 @@ namespace SixLabors.ImageSharp.Tests { var stream = new MemoryStream(); - this.Image.Save(stream, this.encoderNotInFormat.Object); + this.image.Save(stream, this.encoderNotInFormat.Object); - this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream)); + this.encoderNotInFormat.Verify(x => x.Encode(this.image, stream)); } public void Dispose() { - this.Image.Dispose(); + this.image.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index 96747b0d2..dcf4dcfe8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -6,8 +6,8 @@ using System.IO; using SixLabors.ImageSharp.Formats; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public partial class ImageTests diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs new file mode 100644 index 000000000..2be950407 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats; + +using Xunit; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests +{ + public partial class ImageTests + { + /// + /// Tests the class. + /// + public class Identify : ImageLoadTestBase + { + private static readonly string ActualImagePath = TestFile.GetInputFileFullPath(TestImages.Bmp.F); + + private byte[] ActualImageBytes => TestFile.Create(TestImages.Bmp.F).Bytes; + + private byte[] ByteArray => this.DataStream.ToArray(); + + private IImageInfo LocalImageInfo => this.localImageInfoMock.Object; + + private IImageFormat LocalImageFormat => this.localImageFormatMock.Object; + + private static readonly IImageFormat ExpectedGlobalFormat = + Configuration.Default.ImageFormatsManager.FindFormatByFileExtension("bmp"); + + [Fact] + public void FromBytes_GlobalConfiguration() + { + IImageInfo info = Image.Identify(this.ActualImageBytes, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + + [Fact] + public void FromBytes_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.ByteArray, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromFileSystemPath_GlobalConfiguration() + { + IImageInfo info = Image.Identify(ActualImagePath, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + + [Fact] + public void FromFileSystemPath_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.MockFilePath, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void FromStream_GlobalConfiguration() + { + using (var stream = new MemoryStream(this.ActualImageBytes)) + { + IImageInfo info = Image.Identify(stream, out IImageFormat type); + + Assert.NotNull(info); + Assert.Equal(ExpectedGlobalFormat, type); + } + } + + [Fact] + public void FromStream_CustomConfiguration() + { + IImageInfo info = Image.Identify(this.LocalConfiguration, this.DataStream, out IImageFormat type); + + Assert.Equal(this.LocalImageInfo, info); + Assert.Equal(this.LocalImageFormat, type); + } + + [Fact] + public void WhenNoMatchingFormatFound_ReturnsNull() + { + IImageInfo info = Image.Identify(new Configuration(), this.DataStream, out IImageFormat type); + + Assert.Null(info); + Assert.Null(type); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs index ec6705d0e..d010f6023 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.ImageLoadTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests public abstract class ImageLoadTestBase : IDisposable { protected Image localStreamReturnImageRgba32; - + protected Image localStreamReturnImageAgnostic; protected Mock localDecoder; @@ -26,9 +26,11 @@ namespace SixLabors.ImageSharp.Tests protected Mock localImageFormatMock; + protected Mock localImageInfoMock; + protected readonly string MockFilePath = Guid.NewGuid().ToString(); - internal readonly Mock localFileSystemMock = new Mock(); + internal readonly Mock LocalFileSystemMock = new Mock(); protected readonly TestFileSystem topLevelFileSystem = new TestFileSystem(); @@ -53,11 +55,14 @@ namespace SixLabors.ImageSharp.Tests this.localStreamReturnImageRgba32 = new Image(1, 1); this.localStreamReturnImageAgnostic = new Image(1, 1); + this.localImageInfoMock = new Mock(); this.localImageFormatMock = new Mock(); - this.localDecoder = new Mock(); + var detector = new Mock(); + detector.Setup(x => x.Identify(It.IsAny(), It.IsAny())).Returns(this.localImageInfoMock.Object); + this.localDecoder = detector.As(); this.localMimeTypeDetector = new MockImageFormatDetector(this.localImageFormatMock.Object); - + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -68,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests } }) .Returns(this.localStreamReturnImageRgba32); - + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) .Callback((c, s) => { @@ -79,11 +84,8 @@ namespace SixLabors.ImageSharp.Tests } }) .Returns(this.localStreamReturnImageAgnostic); - - this.LocalConfiguration = new Configuration - { - }; + this.LocalConfiguration = new Configuration(); this.LocalConfiguration.ImageFormatsManager.AddImageFormatDetector(this.localMimeTypeDetector); this.LocalConfiguration.ImageFormatsManager.SetDecoder(this.localImageFormatMock.Object, this.localDecoder.Object); @@ -92,18 +94,18 @@ namespace SixLabors.ImageSharp.Tests this.Marker = Guid.NewGuid().ToByteArray(); this.DataStream = this.TestFormat.CreateStream(this.Marker); - this.localFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); + this.LocalFileSystemMock.Setup(x => x.OpenRead(this.MockFilePath)).Returns(this.DataStream); this.topLevelFileSystem.AddFile(this.MockFilePath, this.DataStream); - this.LocalConfiguration.FileSystem = this.localFileSystemMock.Object; + this.LocalConfiguration.FileSystem = this.LocalFileSystemMock.Object; this.TopLevelConfiguration.FileSystem = this.topLevelFileSystem; } public void Dispose() { - // clean up the global object; + // Clean up the global object; this.localStreamReturnImageRgba32?.Dispose(); this.localStreamReturnImageAgnostic?.Dispose(); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 7a5fa8729..399652851 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -16,18 +16,18 @@ namespace SixLabors.ImageSharp.Tests [InlineData(true)] public void FromPixels(bool useSpan) { - Rgba32[] data = { Rgba32.Black, Rgba32.White, Rgba32.White, Rgba32.Black, }; + Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, }; using (Image img = useSpan ? Image.LoadPixelData(data.AsSpan(), 2, 2) : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } @@ -48,13 +48,13 @@ namespace SixLabors.ImageSharp.Tests : Image.LoadPixelData(data, 2, 2)) { Assert.NotNull(img); - Assert.Equal(Rgba32.Black, img[0, 0]); - Assert.Equal(Rgba32.White, img[0, 1]); + Assert.Equal(Color.Black, (Color)img[0, 0]); + Assert.Equal(Color.White, (Color)img[0, 1]); - Assert.Equal(Rgba32.White, img[1, 0]); - Assert.Equal(Rgba32.Black, img[1, 1]); + Assert.Equal(Color.White, (Color)img[1, 0]); + Assert.Equal(Color.Black, (Color)img[1, 1]); } } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs index 58e19c9f7..cb3400758 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_PassLocalConfiguration.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws( () => { - Image.Load(this.TopLevelConfiguration,(string)null); + Image.Load(this.TopLevelConfiguration, (string)null); }); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs index 39795fbf3..4c6b92100 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FileSystemPath_UseDefaultConfiguration.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; @@ -16,12 +16,12 @@ namespace SixLabors.ImageSharp.Tests public class Load_FileSystemPath_UseDefaultConfiguration { private string Path { get; } = TestFile.GetInputFileFullPath(TestImages.Bmp.Bit8); - + private static void VerifyDecodedImage(Image img) { Assert.Equal(new Size(127, 64), img.Size()); } - + [Fact] public void Path_Specific() { @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Path_Agnostic() { @@ -39,8 +39,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - - + [Fact] public void Path_Decoder_Specific() { @@ -49,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Path_Decoder_Agnostic() { @@ -58,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Path_OutFormat_Specific() { @@ -78,6 +77,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(format); } } + [Fact] public void WhenFileNotFound_Throws() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs index 0632d7440..b8ed6e75b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -16,14 +16,14 @@ namespace SixLabors.ImageSharp.Tests public class Load_FromBytes_UseGlobalConfiguration { private static byte[] ByteArray { get; } = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - + private static Span ByteSpan => new Span(ByteArray); private static void VerifyDecodedImage(Image img) { Assert.Equal(new Size(127, 64), img.Size()); } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -46,7 +46,6 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [InlineData(false)] [InlineData(true)] @@ -81,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(format); } } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -96,4 +95,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs index d9d1eb7c4..0c722b4d6 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_UseDefaultConfiguration.cs @@ -17,14 +17,14 @@ namespace SixLabors.ImageSharp.Tests public class Load_FromStream_UseDefaultConfiguration : IDisposable { private static readonly byte[] Data = TestFile.Create(TestImages.Bmp.Bit8).Bytes; - + private MemoryStream Stream { get; } = new MemoryStream(Data); - + private static void VerifyDecodedImage(Image img) { Assert.Equal(new Size(127, 64), img.Size()); } - + [Fact] public void Stream_Specific() { @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_Agnostic() { @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_OutFormat_Specific() { @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests Assert.IsType(format); } } - + [Fact] public void Stream_Decoder_Specific() { @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_Decoder_Agnostic() { @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests VerifyDecodedImage(img); } } - + [Fact] public void Stream_OutFormat_Agnostic() { @@ -87,4 +87,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs index e00a70e39..dc65ecfef 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Save.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Save.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming - using System; using System.IO; @@ -12,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { using SixLabors.ImageSharp.Formats; diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index a018af6ed..423309dfb 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -24,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests /// A exposing the locked pixel memory of a instance. /// TODO: This should be an example in https://github.com/SixLabors/Samples /// - class BitmapMemoryManager : MemoryManager + public class BitmapMemoryManager : MemoryManager { private readonly Bitmap bitmap; @@ -116,7 +117,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { - Assert.Equal(memory, image.GetPixelMemory()); + Assert.Equal(memory, image.GetRootFramePixelBuffer().GetSingleMemory()); image.GetPixelSpan().Fill(bg); for (var i = 10; i < 20; i++) { @@ -151,7 +152,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { - Assert.Equal(memoryManager.Memory, image.GetPixelMemory()); + Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().GetSingleMemory()); image.GetPixelSpan().Fill(bg); for (var i = 10; i < 20; i++) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 1e48f14c8..c99b75733 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,14 +1,16 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { /// @@ -25,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.Equal(11*23, image.GetPixelSpan().Length); + Assert.Equal(11 * 23, image.GetPixelSpan().Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(Configuration.Default, image.GetConfiguration()); @@ -52,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests public void Configuration_Width_Height_BackgroundColor() { Configuration configuration = Configuration.Default.Clone(); - Rgba32 color = Rgba32.Aquamarine; + Rgba32 color = Color.Aquamarine; using (var image = new Image(configuration, 11, 23, color)) { @@ -74,7 +76,7 @@ namespace SixLabors.ImageSharp.Tests configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); var metadata = new ImageMetadata(); - using (Image image = Image.CreateUninitialized(configuration, 21, 22, metadata)) + using (var image = Image.CreateUninitialized(configuration, 21, 22, metadata)) { Assert.Equal(21, image.Width); Assert.Equal(22, image.Height); @@ -85,5 +87,84 @@ namespace SixLabors.ImageSharp.Tests } } } + + public class Indexer + { + private readonly Configuration configuration = Configuration.CreateDefaultInstance(); + + private void LimitBufferCapacity(int bufferCapacityInBytes) + { + var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + allocator.BufferCapacityInBytes = bufferCapacityInBytes; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetSet(bool enforceDisco) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + Rgba32 val = image[3, 4]; + Assert.Equal(default(Rgba32), val); + image[3, 4] = Color.Red; + val = image[3, 4]; + Assert.Equal(Color.Red.ToRgba32(), val); + } + + public static TheoryData OutOfRangeData = new TheoryData() + { + { false, -1 }, + { false, 10 }, + { true, -1 }, + { true, 10 }, + }; + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Get_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[x, 3]); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeX(bool enforceDisco, int x) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[x, 3] = default); + Assert.Equal("x", ex.ParamName); + } + + [Theory] + [MemberData(nameof(OutOfRangeData))] + public void Set_OutOfRangeY(bool enforceDisco, int y) + { + if (enforceDisco) + { + this.LimitBufferCapacity(100); + } + + using var image = new Image(this.configuration, 10, 10); + ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); + Assert.Equal("y", ex.ParamName); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs new file mode 100644 index 000000000..c8a8baf1d --- /dev/null +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public class LargeImageIntegrationTests + { + [Theory(Skip = "For local testing only.")] + [WithBasicTestPatternImages(width: 30000, height: 30000, PixelTypes.Rgba32)] + public void CreateAndResize(TestImageProvider provider) + { + using Image image = provider.GetImage(); + image.Mutate(c => c.Resize(1000, 1000)); + image.DebugSave(provider); + } + } +} diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 34cdca49a..fdb280ca9 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/tests/ImageSharp.Tests/Issues/Issue594.cs b/tests/ImageSharp.Tests/Issues/Issue594.cs index 927f0a5ed..8ddd9caf8 100644 --- a/tests/ImageSharp.Tests/Issues/Issue594.cs +++ b/tests/ImageSharp.Tests/Issues/Issue594.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,8 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Issues public void NormalizedByte4() { // Test PackedValue - Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); - Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); // Test ToVector4 @@ -46,48 +49,48 @@ namespace SixLabors.ImageSharp.Tests.Issues n.FromRgba32(new Rgba32(141, 90, 192, 39)); Assert.Equal(0xA740DA0D, n.PackedValue); - Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(958796544U, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - //var rgb = default(Rgb24); - //var rgba = default(Rgba32); - //var bgr = default(Bgr24); - //var bgra = default(Bgra32); - //var argb = default(Argb32); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new NormalizedByte4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(141, 90, 192)); + // new NormalizedByte4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(141, 90, 192)); - //new NormalizedByte4(x, y, z, w).ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); + // new NormalizedByte4(x, y, z, w).ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); - //new NormalizedByte4(x, y, z, w).ToBgr24(ref bgr); - //Assert.Equal(bgr, new Bgr24(141, 90, 192)); + // new NormalizedByte4(x, y, z, w).ToBgr24(ref bgr); + // Assert.Equal(bgr, new Bgr24(141, 90, 192)); - //new NormalizedByte4(x, y, z, w).ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) + // new NormalizedByte4(x, y, z, w).ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) - //new NormalizedByte4(x, y, z, w).ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(141, 90, 192, 39)); + // new NormalizedByte4(x, y, z, w).ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(141, 90, 192, 39)); // http://community.monogame.net/t/normalizedbyte4-texture2d-gives-different-results-from-xna/8012/8 - //var r = default(NormalizedByte4); - //r.FromRgba32(new Rgba32(9, 115, 202, 127)); - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); - - //r.PackedValue = 0xff4af389; - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); - - //r = default(NormalizedByte4); - //r.FromArgb32(new Argb32(9, 115, 202, 127)); - //r.ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(9, 115, 202, 127)); - - //r = default(NormalizedByte4); - //r.FromBgra32(new Bgra32(9, 115, 202, 127)); - //r.ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); + // var r = default(NormalizedByte4); + // r.FromRgba32(new Rgba32(9, 115, 202, 127)); + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + // r.PackedValue = 0xff4af389; + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + + // r = default(NormalizedByte4); + // r.FromArgb32(new Argb32(9, 115, 202, 127)); + // r.ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(9, 115, 202, 127)); + + // r = default(NormalizedByte4); + // r.FromBgra32(new Bgra32(9, 115, 202, 127)); + // r.ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); } // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue @@ -96,8 +99,8 @@ namespace SixLabors.ImageSharp.Tests.Issues public void NormalizedShort4() { // Test PackedValue - Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); // Test ToVector4 @@ -117,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Issues // Test FromScaledVector4. var pixel = default(NormalizedShort4); pixel.FromScaledVector4(scaled); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); // Test Ordering float x = 0.1f; @@ -125,43 +128,43 @@ namespace SixLabors.ImageSharp.Tests.Issues float z = 0.5f; float w = -0.7f; Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(x, y, z, w).PackedValue); - Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(4150390751449251866UL, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - //var rgb = default(Rgb24); - //var rgba = default(Rgba32); - //var bgr = default(Bgr24); - //var bgra = default(Bgra32); - //var argb = default(Argb32); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(141, 90, 192)); + // new NormalizedShort4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(141, 90, 192)); - //new NormalizedShort4(x, y, z, w).ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) + // new NormalizedShort4(x, y, z, w).ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(141, 90, 192, 39)); // this assert fails in Release build on linux (#594) - //new NormalizedShort4(x, y, z, w).ToBgr24(ref bgr); - //Assert.Equal(bgr, new Bgr24(141, 90, 192)); + // new NormalizedShort4(x, y, z, w).ToBgr24(ref bgr); + // Assert.Equal(bgr, new Bgr24(141, 90, 192)); - //new NormalizedShort4(x, y, z, w).ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); + // new NormalizedShort4(x, y, z, w).ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(141, 90, 192, 39)); - //new NormalizedShort4(x, y, z, w).ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(141, 90, 192, 39)); + // new NormalizedShort4(x, y, z, w).ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(141, 90, 192, 39)); - //var r = default(NormalizedShort4); - //r.FromRgba32(new Rgba32(9, 115, 202, 127)); - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); + // var r = default(NormalizedShort4); + // r.FromRgba32(new Rgba32(9, 115, 202, 127)); + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(9, 115, 202, 127)); - //r = default(NormalizedShort4); - //r.FromBgra32(new Bgra32(9, 115, 202, 127)); - //r.ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); + // r = default(NormalizedShort4); + // r.FromBgra32(new Bgra32(9, 115, 202, 127)); + // r.ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(9, 115, 202, 127)); - //r = default(NormalizedShort4); - //r.FromArgb32(new Argb32(9, 115, 202, 127)); - //r.ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(9, 115, 202, 127)); + // r = default(NormalizedShort4); + // r.FromArgb32(new Argb32(9, 115, 202, 127)); + // r.ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(9, 115, 202, 127)); } // This test fails for unknown reason in Release mode on linux and is meant to help reproducing the issue @@ -170,8 +173,8 @@ namespace SixLabors.ImageSharp.Tests.Issues public void Short4() { // Test the limits. - Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x0UL, new Short4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); // Test ToVector4. @@ -193,7 +196,7 @@ namespace SixLabors.ImageSharp.Tests.Issues // Test FromScaledVector4. var pixel = default(Short4); pixel.FromScaledVector4(scaled); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, pixel.PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, pixel.PackedValue); // Test clamping. Assert.Equal(Vector4.One * 0x7FFF, new Short4(Vector4.One * 1234567.0f).ToVector4()); @@ -210,43 +213,43 @@ namespace SixLabors.ImageSharp.Tests.Issues y = 12653; z = 29623; w = 193; - Assert.Equal((ulong)0x00c173b7316d2d1b, new Short4(x, y, z, w).PackedValue); + Assert.Equal(0x00c173b7316d2d1bUL, new Short4(x, y, z, w).PackedValue); - //var rgb = default(Rgb24); - //var rgba = default(Rgba32); - //var bgr = default(Bgr24); - //var bgra = default(Bgra32); - //var argb = default(Argb32); + // var rgb = default(Rgb24); + // var rgba = default(Rgba32); + // var bgr = default(Bgr24); + // var bgra = default(Bgra32); + // var argb = default(Argb32); - //new Short4(x, y, z, w).ToRgb24(ref rgb); - //Assert.Equal(rgb, new Rgb24(172, 177, 243)); // this assert fails in Release build on linux (#594) + // new Short4(x, y, z, w).ToRgb24(ref rgb); + // Assert.Equal(rgb, new Rgb24(172, 177, 243)); // this assert fails in Release build on linux (#594) - //new Short4(x, y, z, w).ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(172, 177, 243, 128)); + // new Short4(x, y, z, w).ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(172, 177, 243, 128)); - //new Short4(x, y, z, w).ToBgr24(ref bgr); - //Assert.Equal(bgr, new Bgr24(172, 177, 243)); + // new Short4(x, y, z, w).ToBgr24(ref bgr); + // Assert.Equal(bgr, new Bgr24(172, 177, 243)); - //new Short4(x, y, z, w).ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(172, 177, 243, 128)); + // new Short4(x, y, z, w).ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(172, 177, 243, 128)); - //new Short4(x, y, z, w).ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(172, 177, 243, 128)); + // new Short4(x, y, z, w).ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(172, 177, 243, 128)); - //var r = default(Short4); - //r.FromRgba32(new Rgba32(20, 38, 0, 255)); - //r.ToRgba32(ref rgba); - //Assert.Equal(rgba, new Rgba32(20, 38, 0, 255)); + // var r = default(Short4); + // r.FromRgba32(new Rgba32(20, 38, 0, 255)); + // r.ToRgba32(ref rgba); + // Assert.Equal(rgba, new Rgba32(20, 38, 0, 255)); - //r = default(Short4); - //r.FromBgra32(new Bgra32(20, 38, 0, 255)); - //r.ToBgra32(ref bgra); - //Assert.Equal(bgra, new Bgra32(20, 38, 0, 255)); + // r = default(Short4); + // r.FromBgra32(new Bgra32(20, 38, 0, 255)); + // r.ToBgra32(ref bgra); + // Assert.Equal(bgra, new Bgra32(20, 38, 0, 255)); - //r = default(Short4); - //r.FromArgb32(new Argb32(20, 38, 0, 255)); - //r.ToArgb32(ref argb); - //Assert.Equal(argb, new Argb32(20, 38, 0, 255)); + // r = default(Short4); + // r.FromArgb32(new Argb32(20, 38, 0, 255)); + // r.ToArgb32(ref argb); + // Assert.Equal(argb, new Argb32(20, 38, 0, 255)); } // Comparison helpers with small tolerance to allow for floating point rounding during computations. diff --git a/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs similarity index 81% rename from tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs rename to tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index 227d62778..8db79fca0 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -1,18 +1,15 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. -// ReSharper disable InconsistentNaming using System; using System.Buffers; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; -using Microsoft.Win32; -using SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Memory; using Xunit; -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { public class ArrayPoolMemoryAllocatorTests { @@ -21,21 +18,16 @@ namespace SixLabors.ImageSharp.Memory.Tests private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; /// - /// Contains SUT for in-process tests. + /// Gets the SUT for in-process tests. /// private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture(); /// - /// Contains SUT for tests executed by , + /// Gets the SUT for tests executed by , /// recreated in each external process. /// private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); - static ArrayPoolMemoryAllocatorTests() - { - TestEnvironment.PrepareRemoteExecutor(); - } - public class BufferTests : BufferTestSuite { public BufferTests() @@ -116,13 +108,13 @@ namespace SixLabors.ImageSharp.Memory.Tests MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) { - firstAlloc.GetSpan().Fill(666); + BufferExtensions.GetSpan(firstAlloc).Fill(666); } using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) { int expected = options == AllocationOptions.Clean ? 0 : 666; - Assert.Equal(expected, secondAlloc.GetSpan()[0]); + Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]); } } @@ -133,7 +125,7 @@ namespace SixLabors.ImageSharp.Memory.Tests { MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; IMemoryOwner buffer = memoryAllocator.Allocate(32); - ref int ptrToPrev0 = ref MemoryMarshal.GetReference(buffer.GetSpan()); + ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer)); if (!keepBufferAlive) { @@ -144,7 +136,7 @@ namespace SixLabors.ImageSharp.Memory.Tests buffer = memoryAllocator.Allocate(32); - Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.GetReference())); + Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer))); } [Fact] @@ -156,7 +148,6 @@ namespace SixLabors.ImageSharp.Memory.Tests buffer.Dispose(); } - [Fact] public void AllocationOverLargeArrayThreshold_UsesDifferentPool() { @@ -165,12 +156,12 @@ namespace SixLabors.ImageSharp.Memory.Tests const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); - ref int ptr2Small = ref small.GetReference(); + ref int ptr2Small = ref BufferExtensions.GetReference(small); small.Dispose(); IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); - Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.GetReference())); + Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large))); } RemoteExecutor.Invoke(RunTest).Dispose(); @@ -217,14 +208,34 @@ namespace SixLabors.ImageSharp.Memory.Tests [Theory] [InlineData(-1)] - [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] - public void AllocateIncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) + [InlineData(-111)] + public void Allocate_Negative_Throws_ArgumentOutOfRangeException(int length) { ArgumentOutOfRangeException ex = Assert.Throws(() => this.LocalFixture.MemoryAllocator.Allocate(length)); Assert.Equal("length", ex.ParamName); } + [Fact] + public void AllocateZero() + { + using IMemoryOwner buffer = this.LocalFixture.MemoryAllocator.Allocate(0); + Assert.Equal(0, buffer.Memory.Length); + } + + [Theory] + [InlineData(101)] + [InlineData((int.MaxValue / SizeOfLargeStruct) - 1)] + [InlineData(int.MaxValue / SizeOfLargeStruct)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 1)] + [InlineData((int.MaxValue / SizeOfLargeStruct) + 137)] + public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length) + { + this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct; + Assert.Throws(() => + this.LocalFixture.MemoryAllocator.Allocate(length)); + } + [Theory] [InlineData(-1)] public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) @@ -236,7 +247,7 @@ namespace SixLabors.ImageSharp.Memory.Tests private class MemoryAllocatorFixture { - public MemoryAllocator MemoryAllocator { get; set; } = + public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); /// @@ -245,12 +256,12 @@ namespace SixLabors.ImageSharp.Memory.Tests public bool CheckIsRentingPooledBuffer(int length) where T : struct { - IMemoryOwner buffer = MemoryAllocator.Allocate(length); - ref T ptrToPrevPosition0 = ref buffer.GetReference(); + IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); + ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer); buffer.Dispose(); - buffer = MemoryAllocator.Allocate(length); - bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref buffer.GetReference()); + buffer = this.MemoryAllocator.Allocate(length); + bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer)); buffer.Dispose(); return sameBuffers; diff --git a/tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs similarity index 93% rename from tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs rename to tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs index 8073d069d..9f8543fff 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/BufferExtensions.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferExtensions.cs @@ -6,7 +6,7 @@ using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { internal static class BufferExtensions { @@ -22,4 +22,4 @@ namespace SixLabors.ImageSharp.Memory.Tests where T : struct => ref MemoryMarshal.GetReference(buffer.GetSpan()); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs similarity index 99% rename from tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs rename to tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index 4590bbe97..6465e0b81 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -5,10 +5,11 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using Xunit; // ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { /// /// Inherit this class to test an implementation (provided by ). diff --git a/tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs similarity index 93% rename from tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs rename to tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 8e3b82be5..9e14bd1db 100644 --- a/tests/ImageSharp.Tests/Memory/Alocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -3,9 +3,10 @@ using System; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using Xunit; -namespace SixLabors.ImageSharp.Memory.Tests +namespace SixLabors.ImageSharp.Tests.Memory.Allocators { public class SimpleGcMemoryAllocatorTests { diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 3b296f926..ab04b3700 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -3,6 +3,8 @@ using System; using System.Buffers; +using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,7 +13,6 @@ using SixLabors.ImageSharp.Memory; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Memory { public class Buffer2DTests @@ -19,37 +20,74 @@ namespace SixLabors.ImageSharp.Tests.Memory // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert { - public static void SpanPointsTo(Span span, IMemoryOwner buffer, int bufferOffset = 0) + public static void SpanPointsTo(Span span, Memory buffer, int bufferOffset = 0) where T : struct { ref T actual = ref MemoryMarshal.GetReference(span); - ref T expected = ref Unsafe.Add(ref buffer.GetReference(), bufferOffset); + ref T expected = ref buffer.Span[bufferOffset]; True(Unsafe.AreSame(ref expected, ref actual), "span does not point to the expected position"); } } - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + private TestMemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); + + private const int Big = 99999; + + [Theory] + [InlineData(Big, 7, 42)] + [InlineData(Big, 1025, 17)] + [InlineData(300, 42, 777)] + public unsafe void Construct(int bufferCapacity, int width, int height) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) + { + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(width * height, buffer.FastMemoryGroup.TotalLength); + Assert.True(buffer.FastMemoryGroup.BufferLength % width == 0); + } + } [Theory] - [InlineData(7, 42)] - [InlineData(1025, 17)] - public void Construct(int width, int height) + [InlineData(Big, 0, 42)] + [InlineData(Big, 1, 0)] + [InlineData(60, 42, 0)] + [InlineData(3, 0, 0)] + public unsafe void Construct_Empty(int bufferCapacity, int width, int height) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Assert.Equal(width, buffer.Width); Assert.Equal(height, buffer.Height); - Assert.Equal(width * height, buffer.GetMemory().Length); + Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); + Assert.Equal(0, buffer.GetSingleSpan().Length); } } + [Theory] + [InlineData(50, 10, 20, 4)] + public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.MemoryAllocator.Allocate2DOveraligned(width, height, alignmentMultiplier); + MemoryGroup memoryGroup = buffer.FastMemoryGroup; + int expectedAlignment = width * alignmentMultiplier; + + Assert.Equal(expectedAlignment, memoryGroup.BufferLength); + } + [Fact] public void CreateClean() { using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(42, 42, AllocationOptions.Clean)) { - Span span = buffer.GetSpan(); + Span span = buffer.GetSingleSpan(); for (int j = 0; j < span.Length; j++) { Assert.Equal(0, span[j]); @@ -58,58 +96,147 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(7, 42, 0)] - [InlineData(7, 42, 10)] - [InlineData(17, 42, 41)] - public void GetRowSpanY(int width, int height, int y) + [InlineData(Big, 7, 42, 0, 0)] + [InlineData(Big, 7, 42, 10, 0)] + [InlineData(Big, 17, 42, 41, 0)] + [InlineData(500, 17, 42, 41, 1)] + [InlineData(200, 100, 30, 1, 0)] + [InlineData(200, 100, 30, 2, 1)] + [InlineData(200, 100, 30, 4, 2)] + public unsafe void GetRowSpanY(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { Span span = buffer.GetRowSpan(y); - // Assert.Equal(width * y, span.Start); Assert.Equal(width, span.Length); - Assert.SpanPointsTo(span, buffer.MemorySource.MemoryOwner, width * y); + + int expectedSubBufferOffset = (width * y) - (expectedBufferIndex * buffer.FastMemoryGroup.BufferLength); + Assert.SpanPointsTo(span, buffer.FastMemoryGroup[expectedBufferIndex], expectedSubBufferOffset); } } + public static TheoryData GetRowSpanY_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, -1 }, + { Big, 10, 8, 8 }, + { 20, 10, 8, -1 }, + { 20, 10, 8, 10 }, + }; + [Theory] - [InlineData(42, 8, 0, 0)] - [InlineData(400, 1000, 20, 10)] - [InlineData(99, 88, 98, 87)] - public void Indexer(int width, int height, int x, int y) + [MemberData(nameof(GetRowSpanY_OutOfRange_Data))] + public void GetRowSpan_OutOfRange(int bufferCapacity, int width, int height, int y) { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); + + Exception ex = Assert.ThrowsAny(() => buffer.GetRowSpan(y)); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } + + public static TheoryData Indexer_OutOfRange_Data = new TheoryData() + { + { Big, 10, 8, 1, -1 }, + { Big, 10, 8, 1, 8 }, + { Big, 10, 8, -1, 1 }, + { Big, 10, 8, 10, 1 }, + { 20, 10, 8, 1, -1 }, + { 20, 10, 8, 1, 10 }, + { 20, 10, 8, -1, 1 }, + { 20, 10, 8, 10, 1 }, + }; + + [Theory] + [MemberData(nameof(Indexer_OutOfRange_Data))] + public void Indexer_OutOfRange(int bufferCapacity, int width, int height, int x, int y) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); + + Exception ex = Assert.ThrowsAny(() => buffer[x, y]++); + Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); + } + + [Theory] + [InlineData(Big, 42, 8, 0, 0)] + [InlineData(Big, 400, 1000, 20, 10)] + [InlineData(Big, 99, 88, 98, 87)] + [InlineData(500, 200, 30, 42, 13)] + [InlineData(500, 200, 30, 199, 29)] + public unsafe void Indexer(int bufferCapacity, int width, int height, int x, int y) + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; + using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.MemorySource.GetSpan(); + int bufferIndex = (width * y) / buffer.FastMemoryGroup.BufferLength; + int subBufferStart = (width * y) - (bufferIndex * buffer.FastMemoryGroup.BufferLength); + + Span span = buffer.FastMemoryGroup[bufferIndex].Span.Slice(subBufferStart); ref TestStructs.Foo actual = ref buffer[x, y]; - ref TestStructs.Foo expected = ref span[y * width + x]; + ref TestStructs.Foo expected = ref span[x]; Assert.True(Unsafe.AreSame(ref expected, ref actual)); } } [Fact] - public void SwapOrCopyContent() + public void SwapOrCopyContent_WhenBothAllocated() { - using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5)) - using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7)) + using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) + using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) { - IMemoryOwner aa = a.MemorySource.MemoryOwner; - IMemoryOwner bb = b.MemorySource.MemoryOwner; + a[1, 3] = 666; + b[1, 3] = 444; + + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.MemorySource.MemoryOwner); - Assert.Equal(aa, b.MemorySource.MemoryOwner); + Assert.Equal(bb, a.FastMemoryGroup.Single()); + Assert.Equal(aa, b.FastMemoryGroup.Single()); Assert.Equal(new Size(3, 7), a.Size()); Assert.Equal(new Size(10, 5), b.Size()); + + Assert.Equal(666, b[1, 3]); + Assert.Equal(444, a[1, 3]); } } + [Fact] + public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() + { + using var destData = MemoryGroup.Wrap(new int[100]); + using var dest = new Buffer2D(destData, 10, 10); + + using (Buffer2D source = this.MemoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean)) + { + source[0, 0] = 1; + dest[0, 0] = 2; + + Buffer2D.SwapOrCopyContent(dest, source); + } + + int actual1 = dest.GetRowSpan(0)[0]; + int actual2 = dest.GetRowSpan(0)[0]; + int actual3 = dest.GetSafeRowMemory(0).Span[0]; + int actual4 = dest.GetFastRowMemory(0).Span[0]; + int actual5 = dest[0, 0]; + + Assert.Equal(1, actual1); + Assert.Equal(1, actual2); + Assert.Equal(1, actual3); + Assert.Equal(1, actual4); + Assert.Equal(1, actual5); + } + [Theory] [InlineData(100, 20, 0, 90, 10)] [InlineData(100, 3, 0, 50, 50)] @@ -122,7 +249,7 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(width, height)) { - rnd.RandomFill(b.GetSpan(), 0, 1); + rnd.RandomFill(b.GetSingleSpan(), 0, 1); b.CopyColumns(startIndex, destIndex, columnCount); @@ -144,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests.Memory var rnd = new Random(123); using (Buffer2D b = this.MemoryAllocator.Allocate2D(100, 100)) { - rnd.RandomFill(b.GetSpan(), 0, 1); + rnd.RandomFill(b.GetSingleSpan(), 0, 1); b.CopyColumns(0, 50, 22); b.CopyColumns(0, 50, 22); @@ -160,5 +287,18 @@ namespace SixLabors.ImageSharp.Tests.Memory } } } + + [Fact] + public void PublicMemoryGroup_IsMemoryGroupView() + { + using Buffer2D buffer1 = this.MemoryAllocator.Allocate2D(10, 10); + using Buffer2D buffer2 = this.MemoryAllocator.Allocate2D(10, 10); + IMemoryGroup mgBefore = buffer1.MemoryGroup; + + Buffer2D.SwapOrCopyContent(buffer1, buffer2); + + Assert.False(mgBefore.IsValid); + Assert.NotSame(mgBefore, buffer1.MemoryGroup); + } } } diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 7c93128b4..77e899a4c 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -9,27 +9,27 @@ namespace SixLabors.ImageSharp.Tests.Memory { public class BufferAreaTests { + private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator(); + [Fact] public void Construct() { - using (var buffer = Configuration.Default.MemoryAllocator.Allocate2D(10, 20)) - { - var rectangle = new Rectangle(3, 2, 5, 6); - var area = new BufferArea(buffer, rectangle); + using Buffer2D buffer = this.memoryAllocator.Allocate2D(10, 20); + var rectangle = new Rectangle(3, 2, 5, 6); + var area = new BufferArea(buffer, rectangle); - Assert.Equal(buffer, area.DestinationBuffer); - Assert.Equal(rectangle, area.Rectangle); - } + Assert.Equal(buffer, area.DestinationBuffer); + Assert.Equal(rectangle, area.Rectangle); } - private static Buffer2D CreateTestBuffer(int w, int h) + private Buffer2D CreateTestBuffer(int w, int h) { - var buffer = Configuration.Default.MemoryAllocator.Allocate2D(w, h); + Buffer2D buffer = this.memoryAllocator.Allocate2D(w, h); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - buffer[x, y] = y * 100 + x; + buffer[x, y] = (y * 100) + x; } } @@ -37,110 +37,122 @@ namespace SixLabors.ImageSharp.Tests.Memory } [Theory] - [InlineData(2, 3, 2, 2)] - [InlineData(5, 4, 3, 2)] - public void Indexer(int rx, int ry, int x, int y) + [InlineData(1000, 2, 3, 2, 2)] + [InlineData(1000, 5, 4, 3, 2)] + [InlineData(200, 2, 3, 2, 2)] + [InlineData(200, 5, 4, 3, 2)] + public void Indexer(int bufferCapacity, int rx, int ry, int x, int y) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - var r = new Rectangle(rx, ry, 5, 6); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, 5, 6); - BufferArea area = buffer.GetArea(r); + BufferArea area = buffer.GetArea(r); - int value = area[x, y]; - int expected = (ry + y) * 100 + rx + x; - Assert.Equal(expected, value); - } + int value = area[x, y]; + int expected = ((ry + y) * 100) + rx + x; + Assert.Equal(expected, value); } [Theory] - [InlineData(2, 3, 2, 5, 6)] - [InlineData(5, 4, 3, 6, 5)] - public void GetRowSpan(int rx, int ry, int y, int w, int h) + [InlineData(1000, 2, 3, 2, 5, 6)] + [InlineData(1000, 5, 4, 3, 6, 5)] + [InlineData(200, 2, 3, 2, 5, 6)] + [InlineData(200, 5, 4, 3, 6, 5)] + public void GetRowSpan(int bufferCapacity, int rx, int ry, int y, int w, int h) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - var r = new Rectangle(rx, ry, w, h); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - BufferArea area = buffer.GetArea(r); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + var r = new Rectangle(rx, ry, w, h); - Span span = area.GetRowSpan(y); + BufferArea area = buffer.GetArea(r); - Assert.Equal(w, span.Length); + Span span = area.GetRowSpan(y); - for (int i = 0; i < w; i++) - { - int expected = (ry + y) * 100 + rx + i; - int value = span[i]; + Assert.Equal(w, span.Length); - Assert.Equal(expected, value); - } + for (int i = 0; i < w; i++) + { + int expected = ((ry + y) * 100) + rx + i; + int value = span[i]; + + Assert.Equal(expected, value); } } [Fact] public void GetSubArea() { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); + BufferArea area1 = area0.GetSubArea(4, 4, 5, 5); - var expectedRect = new Rectangle(10, 12, 5, 5); + var expectedRect = new Rectangle(10, 12, 5, 5); - Assert.Equal(buffer, area1.DestinationBuffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(buffer, area1.DestinationBuffer); + Assert.Equal(expectedRect, area1.Rectangle); - int value00 = 12 * 100 + 10; - Assert.Equal(value00, area1[0, 0]); - } + int value00 = (12 * 100) + 10; + Assert.Equal(value00, area1[0, 0]); } - [Fact] - public void DangerousGetPinnableReference() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void GetReferenceToOrigin(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area0 = buffer.GetArea(6, 8, 10, 10); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - ref int r = ref area0.GetReferenceToOrigin(); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area0 = buffer.GetArea(6, 8, 10, 10); - int expected = buffer[6, 8]; - Assert.Equal(expected, r); - } + ref int r = ref area0.GetReferenceToOrigin(); + + int expected = buffer[6, 8]; + Assert.Equal(expected, r); } - [Fact] - public void Clear_FullArea() + [Theory] + [InlineData(1000)] + [InlineData(70)] + public void Clear_FullArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(22, 13)) + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; + + using Buffer2D buffer = this.CreateTestBuffer(22, 13); + var emptyRow = new int[22]; + buffer.GetArea().Clear(); + + for (int y = 0; y < 13; y++) { - buffer.GetArea().Clear(); - Span fullSpan = buffer.GetSpan(); - Assert.True(fullSpan.SequenceEqual(new int[fullSpan.Length])); + Span row = buffer.GetRowSpan(y); + Assert.True(row.SequenceEqual(emptyRow)); } } - [Fact] - public void Clear_SubArea() + [Theory] + [InlineData(1000)] + [InlineData(40)] + public void Clear_SubArea(int bufferCapacity) { - using (Buffer2D buffer = CreateTestBuffer(20, 30)) - { - BufferArea area = buffer.GetArea(5, 5, 10, 10); - area.Clear(); + this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * bufferCapacity; - Assert.NotEqual(0, buffer[4, 4]); - Assert.NotEqual(0, buffer[15, 15]); + using Buffer2D buffer = this.CreateTestBuffer(20, 30); + BufferArea area = buffer.GetArea(5, 5, 10, 10); + area.Clear(); - Assert.Equal(0, buffer[5, 5]); - Assert.Equal(0, buffer[14, 14]); + Assert.NotEqual(0, buffer[4, 4]); + Assert.NotEqual(0, buffer[15, 15]); - for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) - { - Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); - Assert.True(span.SequenceEqual(new int[area.Width])); - } + Assert.Equal(0, buffer[5, 5]); + Assert.Equal(0, buffer[14, 14]); + + for (int y = area.Rectangle.Y; y < area.Rectangle.Bottom; y++) + { + Span span = buffer.GetRowSpan(y).Slice(area.Rectangle.X, area.Width); + Assert.True(span.SequenceEqual(new int[area.Width])); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs new file mode 100644 index 000000000..555d641c7 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndex.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public struct MemoryGroupIndex : IEquatable + { + public override bool Equals(object obj) => obj is MemoryGroupIndex other && this.Equals(other); + + public override int GetHashCode() => HashCode.Combine(this.BufferLength, this.BufferIndex, this.ElementIndex); + + public int BufferLength { get; } + + public int BufferIndex { get; } + + public int ElementIndex { get; } + + public MemoryGroupIndex(int bufferLength, int bufferIndex, int elementIndex) + { + this.BufferLength = bufferLength; + this.BufferIndex = bufferIndex; + this.ElementIndex = elementIndex; + } + + public static MemoryGroupIndex operator +(MemoryGroupIndex idx, int val) + { + int nextElementIndex = idx.ElementIndex + val; + return new MemoryGroupIndex( + idx.BufferLength, + idx.BufferIndex + (nextElementIndex / idx.BufferLength), + nextElementIndex % idx.BufferLength); + } + + public bool Equals(MemoryGroupIndex other) + { + if (this.BufferLength != other.BufferLength) + { + throw new InvalidOperationException(); + } + + return this.BufferIndex == other.BufferIndex && this.ElementIndex == other.ElementIndex; + } + + public static bool operator ==(MemoryGroupIndex a, MemoryGroupIndex b) => a.Equals(b); + + public static bool operator !=(MemoryGroupIndex a, MemoryGroupIndex b) => !a.Equals(b); + + public static bool operator <(MemoryGroupIndex a, MemoryGroupIndex b) + { + if (a.BufferLength != b.BufferLength) + { + throw new InvalidOperationException(); + } + + if (a.BufferIndex < b.BufferIndex) + { + return true; + } + + if (a.BufferIndex == b.BufferIndex) + { + return a.ElementIndex < b.ElementIndex; + } + + return false; + } + + public static bool operator >(MemoryGroupIndex a, MemoryGroupIndex b) + { + if (a.BufferLength != b.BufferLength) + { + throw new InvalidOperationException(); + } + + if (a.BufferIndex > b.BufferIndex) + { + return true; + } + + if (a.BufferIndex == b.BufferIndex) + { + return a.ElementIndex > b.ElementIndex; + } + + return false; + } + } + + internal static class MemoryGroupIndexExtensions + { + public static T GetElementAt(this IMemoryGroup group, MemoryGroupIndex idx) + where T : struct + { + return group[idx.BufferIndex].Span[idx.ElementIndex]; + } + + public static void SetElementAt(this IMemoryGroup group, MemoryGroupIndex idx, T value) + where T : struct + { + group[idx.BufferIndex].Span[idx.ElementIndex] = value; + } + + public static MemoryGroupIndex MinIndex(this IMemoryGroup group) + where T : struct + { + return new MemoryGroupIndex(group.BufferLength, 0, 0); + } + + public static MemoryGroupIndex MaxIndex(this IMemoryGroup group) + where T : struct + { + return group.Count == 0 + ? new MemoryGroupIndex(group.BufferLength, 0, 0) + : new MemoryGroupIndex(group.BufferLength, group.Count - 1, group[group.Count - 1].Length); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs new file mode 100644 index 000000000..f0cc18f29 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupIndexTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public class MemoryGroupIndexTests + { + [Fact] + public void Equal() + { + var a = new MemoryGroupIndex(10, 1, 3); + var b = new MemoryGroupIndex(10, 1, 3); + + Assert.True(a.Equals(b)); + Assert.True(a == b); + Assert.False(a != b); + Assert.False(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerBufferIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 5, 3); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void SmallerElementIndex() + { + var a = new MemoryGroupIndex(10, 3, 3); + var b = new MemoryGroupIndex(10, 3, 9); + + Assert.False(a == b); + Assert.True(a != b); + Assert.True(a < b); + Assert.False(a > b); + } + + [Fact] + public void Increment() + { + var a = new MemoryGroupIndex(10, 3, 3); + a += 1; + Assert.Equal(new MemoryGroupIndex(10, 3, 4), a); + } + + [Fact] + public void Increment_OverflowBuffer() + { + var a = new MemoryGroupIndex(10, 5, 3); + var b = new MemoryGroupIndex(10, 5, 9); + a += 8; + b += 1; + + Assert.Equal(new MemoryGroupIndex(10, 6, 1), a); + Assert.Equal(new MemoryGroupIndex(10, 6, 0), b); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs new file mode 100644 index 000000000..298b5a93f --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -0,0 +1,128 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class Allocate : MemoryGroupTestsBase + { +#pragma warning disable SA1509 + public static TheoryData AllocateData = + new TheoryData() + { + { default(S5), 22, 4, 4, 1, 4, 4 }, + { default(S5), 22, 4, 7, 2, 4, 3 }, + { default(S5), 22, 4, 8, 2, 4, 4 }, + { default(S5), 22, 4, 21, 6, 4, 1 }, + + // empty: + { default(S5), 22, 0, 0, 1, -1, 0 }, + { default(S5), 22, 4, 0, 1, -1, 0 }, + + { default(S4), 50, 12, 12, 1, 12, 12 }, + { default(S4), 50, 7, 12, 2, 7, 5 }, + { default(S4), 50, 6, 12, 1, 12, 12 }, + { default(S4), 50, 5, 12, 2, 10, 2 }, + { default(S4), 50, 4, 12, 1, 12, 12 }, + { default(S4), 50, 3, 12, 1, 12, 12 }, + { default(S4), 50, 2, 12, 1, 12, 12 }, + { default(S4), 50, 1, 12, 1, 12, 12 }, + + { default(S4), 50, 12, 13, 2, 12, 1 }, + { default(S4), 50, 7, 21, 3, 7, 7 }, + { default(S4), 50, 7, 23, 4, 7, 2 }, + { default(S4), 50, 6, 13, 2, 12, 1 }, + + { default(short), 200, 50, 49, 1, 49, 49 }, + { default(short), 200, 50, 1, 1, 1, 1 }, + { default(byte), 1000, 512, 2047, 4, 512, 511 } + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; + + // Act: + using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); + + // Assert: + Assert.Equal(expectedNumberOfBuffers, g.Count); + + if (expectedBufferSize >= 0) + { + Assert.Equal(expectedBufferSize, g.BufferLength); + } + + if (g.Count == 0) + { + return; + } + + for (int i = 0; i < g.Count - 1; i++) + { + Assert.Equal(g[i].Length, expectedBufferSize); + } + + Assert.Equal(g.Last().Length, expectedSizeOfLastBuffer); + } + + [Fact] + public void WhenBlockAlignmentIsOverCapacity_Throws_InvalidMemoryOperationException() + { + this.MemoryAllocator.BufferCapacityInBytes = 84; // 42 * Int16 + + Assert.Throws(() => + { + MemoryGroup.Allocate(this.MemoryAllocator, 50, 43); + }); + } + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) + { + this.MemoryAllocator.BufferCapacityInBytes = 200; + + HashSet bufferHashes; + + int expectedBlockCount = 5; + using (var g = MemoryGroup.Allocate(this.MemoryAllocator, 500, 100, allocationOptions)) + { + IReadOnlyList allocationLog = this.MemoryAllocator.AllocationLog; + Assert.Equal(expectedBlockCount, allocationLog.Count); + bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); + Assert.Equal(expectedBlockCount, bufferHashes.Count); + Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count); + + for (int i = 0; i < expectedBlockCount; i++) + { + Assert.Equal(allocationOptions, allocationLog[i].AllocationOptions); + Assert.Equal(100, allocationLog[i].Length); + Assert.Equal(200, allocationLog[i].LengthInBytes); + } + } + + Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); + Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs new file mode 100644 index 000000000..ab69a3077 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class CopyTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.CopyTo(trg); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.CopyTo(trg)); + } + + [Theory] + [InlineData(30, 10, 40)] + [InlineData(42, 23, 42)] + [InlineData(1, 3, 10)] + [InlineData(0, 4, 0)] + public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) + { + using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); + var trg = new int[spanLength]; + src.CopyTo(trg); + + int expected = 1; + foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) + { + Assert.Equal(expected, val); + expected++; + } + } + + [Theory] + [InlineData(20, 7, 19)] + [InlineData(2, 1, 1)] + public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) + { + using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); + var trg = new int[spanLength]; + Assert.ThrowsAny(() => src.CopyTo(trg)); + } + + [Theory] + [InlineData(30, 35, 10)] + [InlineData(42, 23, 42)] + [InlineData(10, 3, 1)] + [InlineData(0, 3, 0)] + public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) + { + var src = new int[spanLength]; + for (int i = 0; i < src.Length; i++) + { + src[i] = i + 1; + } + + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); + src.AsSpan().CopyTo(trg); + + int position = 0; + for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) + { + int expected = position + 1; + Assert.Equal(expected, trg.GetElementAt(i)); + } + } + + [Theory] + [InlineData(10, 3, 11)] + [InlineData(0, 3, 1)] + public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) + { + var src = new int[spanLength]; + using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); + Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs new file mode 100644 index 000000000..c10fdc15d --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs @@ -0,0 +1,107 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class SwapOrCopyContent : MemoryGroupTestsBase + { + [Fact] + public void WhenBothAreMemoryOwners_ShouldSwap() + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; + using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 50); + using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 50); + + Memory a0 = a[0]; + Memory a1 = a[1]; + Memory b0 = b[0]; + Memory b1 = b[1]; + + bool swap = MemoryGroup.SwapOrCopyContent(a, b); + + Assert.True(swap); + Assert.Equal(b0, a[0]); + Assert.Equal(b1, a[1]); + Assert.Equal(a0, b[0]); + Assert.Equal(a1, b[1]); + Assert.NotEqual(a[0], b[0]); + } + + [Fact] + public void WhenBothAreMemoryOwners_ShouldReplaceViews() + { + using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 100); + using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 100); + + a[0].Span[42] = 1; + b[0].Span[33] = 2; + MemoryGroupView aView0 = a.View; + MemoryGroupView bView0 = b.View; + + MemoryGroup.SwapOrCopyContent(a, b); + Assert.False(aView0.IsValid); + Assert.False(bView0.IsValid); + Assert.ThrowsAny(() => _ = aView0[0].Span); + Assert.ThrowsAny(() => _ = bView0[0].Span); + + Assert.True(a.View.IsValid); + Assert.True(b.View.IsValid); + Assert.Equal(2, a.View[0].Span[33]); + Assert.Equal(1, b.View[0].Span[42]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + using var dest = MemoryGroup.Wrap(destOwner.Memory); + + using MemoryGroup source = this.MemoryAllocator.AllocateGroup(21, 30); + + source[0].Span[10] = color; + + // Act: + bool swap = MemoryGroup.SwapOrCopyContent(dest, source); + + // Assert: + Assert.False(swap); + Assert.Equal(color, dest[0].Span[10]); + Assert.NotEqual(source[0], dest[0]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + var dest = MemoryGroup.Wrap(destOwner.Memory); + + using MemoryGroup source = this.MemoryAllocator.AllocateGroup(22, 30); + + source[0].Span[10] = color; + + // Act: + Assert.ThrowsAny(() => MemoryGroup.SwapOrCopyContent(dest, source)); + + Assert.Equal(color, source[0].Span[10]); + Assert.NotEqual(color, dest[0].Span[10]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs new file mode 100644 index 000000000..8884037a5 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.View.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests + { + public class View : MemoryGroupTestsBase + { + [Fact] + public void RefersToOwnerGroupContent() + { + using MemoryGroup group = this.CreateTestGroup(240, 80, true); + + MemoryGroupView view = group.View; + Assert.True(view.IsValid); + Assert.Equal(group.Count, view.Count); + Assert.Equal(group.BufferLength, view.BufferLength); + Assert.Equal(group.TotalLength, view.TotalLength); + int cnt = 1; + foreach (Memory memory in view) + { + Span span = memory.Span; + foreach (int t in span) + { + Assert.Equal(cnt, t); + cnt++; + } + } + } + + [Fact] + public void IsInvalidatedOnOwnerGroupDispose() + { + MemoryGroupView view; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) + { + view = group.View; + } + + Assert.False(view.IsValid); + + Assert.ThrowsAny(() => + { + _ = view.Count; + }); + + Assert.ThrowsAny(() => + { + _ = view.BufferLength; + }); + + Assert.ThrowsAny(() => + { + _ = view.TotalLength; + }); + + Assert.ThrowsAny(() => + { + _ = view[0]; + }); + } + + [Fact] + public void WhenInvalid_CanNotUseMemberMemory() + { + Memory memory; + using (MemoryGroup group = this.CreateTestGroup(240, 80, true)) + { + memory = group.View[0]; + } + + Assert.ThrowsAny(() => + { + _ = memory.Span; + }); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs new file mode 100644 index 000000000..694c4d32f --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -0,0 +1,214 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Linq; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public partial class MemoryGroupTests : MemoryGroupTestsBase + { + [Fact] + public void IsValid_TrueAfterCreation() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + + Assert.True(g.IsValid); + } + + [Fact] + public void IsValid_FalseAfterDisposal() + { + using var g = MemoryGroup.Allocate(this.MemoryAllocator, 10, 100); + + g.Dispose(); + Assert.False(g.IsValid); + } + +#pragma warning disable SA1509 + private static readonly TheoryData CopyAndTransformData = + new TheoryData() + { + { 20, 10, 20, 10 }, + { 20, 5, 20, 4 }, + { 20, 4, 20, 5 }, + { 18, 6, 20, 5 }, + { 19, 10, 20, 10 }, + { 21, 10, 22, 2 }, + { 1, 5, 5, 4 }, + + { 30, 12, 40, 5 }, + { 30, 5, 40, 12 }, + }; + + public class TransformTo : MemoryGroupTestsBase + { + public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = + CopyAndTransformData; + + [Theory] + [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] + public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + { + using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); + using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); + + src.TransformTo(trg, MultiplyAllBy2); + + int pos = 0; + MemoryGroupIndex i = src.MinIndex(); + MemoryGroupIndex j = trg.MinIndex(); + for (; i < src.MaxIndex(); i += 1, j += 1, pos++) + { + int a = src.GetElementAt(i); + int b = trg.GetElementAt(j); + + Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); + } + } + + [Fact] + public void WhenTargetBufferTooShort_Throws() + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + using MemoryGroup trg = this.CreateTestGroup(5, 20, false); + + Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); + } + } + + [Theory] + [InlineData(100, 5)] + [InlineData(100, 101)] + public void TransformInplace(int totalLength, int bufferLength) + { + using MemoryGroup src = this.CreateTestGroup(10, 20, true); + + src.TransformInplace(s => MultiplyAllBy2(s, s)); + + int cnt = 1; + for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) + { + int val = src.GetElementAt(i); + Assert.Equal(expected: cnt * 2, val); + cnt++; + } + } + + [Fact] + public void Wrap() + { + int[] data0 = { 1, 2, 3, 4 }; + int[] data1 = { 5, 6, 7, 8 }; + int[] data2 = { 9, 10 }; + using var mgr0 = new TestMemoryManager(data0); + using var mgr1 = new TestMemoryManager(data1); + + using var group = MemoryGroup.Wrap(mgr0.Memory, mgr1.Memory, data2); + + Assert.Equal(3, group.Count); + Assert.Equal(4, group.BufferLength); + Assert.Equal(10, group.TotalLength); + + Assert.True(group[0].Span.SequenceEqual(data0)); + Assert.True(group[1].Span.SequenceEqual(data1)); + Assert.True(group[2].Span.SequenceEqual(data2)); + } + + public static TheoryData GetBoundedSlice_SuccessData = new TheoryData() + { + { 300, 100, 110, 80 }, + { 300, 100, 100, 100 }, + { 280, 100, 201, 79 }, + { 42, 7, 0, 0 }, + { 42, 7, 0, 1 }, + { 42, 7, 0, 7 }, + { 42, 9, 9, 9 }, + }; + + [Theory] + [MemberData(nameof(GetBoundedSlice_SuccessData))] + public void GetBoundedSlice_WhenArgsAreCorrect(long totalLength, int bufferLength, long start, int length) + { + using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); + + Memory slice = group.GetBoundedSlice(start, length); + + Assert.Equal(length, slice.Length); + + int expected = (int)start + 1; + foreach (int val in slice.Span) + { + Assert.Equal(expected, val); + expected++; + } + } + + public static TheoryData GetBoundedSlice_ErrorData = new TheoryData() + { + { 300, 100, 110, 91 }, + { 42, 7, 0, 8 }, + { 42, 7, 1, 7 }, + { 42, 7, 1, 30 }, + }; + + [Theory] + [MemberData(nameof(GetBoundedSlice_ErrorData))] + public void GetBoundedSlice_WhenOverlapsBuffers_Throws(long totalLength, int bufferLength, long start, int length) + { + using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); + Assert.ThrowsAny(() => group.GetBoundedSlice(start, length)); + } + + [Fact] + public void Fill() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Fill(42); + + int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + [Fact] + public void Clear() + { + using MemoryGroup group = this.CreateTestGroup(100, 10, true); + group.Clear(); + + var expectedRow = new int[10]; + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(expectedRow)); + } + } + + private static void MultiplyAllBy2(ReadOnlySpan source, Span target) + { + Assert.Equal(source.Length, target.Length); + for (int k = 0; k < source.Length; k++) + { + target[k] = source[k] * 2; + } + } + + [StructLayout(LayoutKind.Sequential, Size = 5)] + private struct S5 + { + public override string ToString() => "S5"; + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + private struct S4 + { + public override string ToString() => "S4"; + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs new file mode 100644 index 000000000..8dd28653c --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTestsBase.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers +{ + public abstract class MemoryGroupTestsBase + { + internal readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + /// + /// Create a group, either uninitialized or filled with incrementing numbers starting with 1. + /// + internal MemoryGroup CreateTestGroup(long totalLength, int bufferLength, bool fillSequence = false) + { + this.MemoryAllocator.BufferCapacityInBytes = bufferLength * sizeof(int); + var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferLength); + + if (!fillSequence) + { + return g; + } + + int j = 1; + for (MemoryGroupIndex i = g.MinIndex(); i < g.MaxIndex(); i += 1) + { + g.SetElementAt(i, j); + j++; + } + + return g; + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs b/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs deleted file mode 100644 index d940aa987..000000000 --- a/tests/ImageSharp.Tests/Memory/MemorySourceTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Tests.Memory -{ - public class MemorySourceTests - { - public class Construction - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void InitializeAsOwner(bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg, isInternalMemorySource); - - Assert.Equal(mmg, a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.Equal(isInternalMemorySource, a.HasSwappableContents); - } - - [Fact] - public void InitializeAsObserver_MemoryOwner_IsNull() - { - var data = new Rgba32[21]; - var mmg = new TestMemoryManager(data); - - var a = new MemorySource(mmg.Memory); - - Assert.Null(a.MemoryOwner); - Assert.Equal(mmg.Memory, a.Memory); - Assert.False(a.HasSwappableContents); - } - } - - public class Dispose - { - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenOwnershipIsTransferred_ShouldDisposeMemoryOwner(bool isInternalMemorySource) - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg, isInternalMemorySource); - - bmg.Dispose(); - Assert.True(mmg.IsDisposed); - } - - [Fact] - public void WhenMemoryObserver_ShouldNotDisposeAnything() - { - var mmg = new TestMemoryManager(new int[10]); - var bmg = new MemorySource(mmg.Memory); - - bmg.Dispose(); - Assert.False(mmg.IsDisposed); - } - } - - public class SwapOrCopyContent - { - private MemoryAllocator MemoryAllocator { get; } = new TestMemoryAllocator(); - - private MemorySource AllocateMemorySource(int length, AllocationOptions options = AllocationOptions.None) - where T : struct - { - IMemoryOwner owner = this.MemoryAllocator.Allocate(length, options); - return new MemorySource(owner, true); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() - { - MemorySource a = this.AllocateMemorySource(13); - MemorySource b = this.AllocateMemorySource(17); - - IMemoryOwner aa = a.MemoryOwner; - IMemoryOwner bb = b.MemoryOwner; - - Memory aaa = a.Memory; - Memory bbb = b.Memory; - - MemorySource.SwapOrCopyContent(ref a, ref b); - - Assert.Equal(bb, a.MemoryOwner); - Assert.Equal(aa, b.MemoryOwner); - - Assert.Equal(bbb, a.Memory); - Assert.Equal(aaa, b.Memory); - Assert.NotEqual(a.Memory, b.Memory); - } - - [Theory] - [InlineData(false, false)] - [InlineData(true, true)] - [InlineData(true, false)] - public void WhenDestIsNotMemoryOwner_SameSize_ShouldCopy(bool sourceIsOwner, bool isInternalMemorySource) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(21); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, isInternalMemorySource) - : new MemorySource(sourceOwner.Memory); - - sourceOwner.Memory.Span[10] = color; - - // Act: - MemorySource.SwapOrCopyContent(ref dest, ref source); - - // Assert: - Assert.Equal(color, dest.Memory.Span[10]); - Assert.NotEqual(sourceOwner, dest.MemoryOwner); - Assert.NotEqual(destOwner, source.MemoryOwner); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - var destOwner = new TestMemoryManager(data); - var dest = new MemorySource(destOwner.Memory); - - IMemoryOwner sourceOwner = this.MemoryAllocator.Allocate(22); - - MemorySource source = sourceIsOwner - ? new MemorySource(sourceOwner, true) - : new MemorySource(sourceOwner.Memory); - sourceOwner.Memory.Span[10] = color; - - // Act: - Assert.ThrowsAny( - () => MemorySource.SwapOrCopyContent(ref dest, ref source) - ); - - Assert.Equal(color, source.Memory.Span[10]); - Assert.NotEqual(color, dest.Memory.Span[10]); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/TestStructs.cs b/tests/ImageSharp.Tests/Memory/TestStructs.cs index 2c9417b11..858bb8e64 100644 --- a/tests/ImageSharp.Tests/Memory/TestStructs.cs +++ b/tests/ImageSharp.Tests/Memory/TestStructs.cs @@ -6,8 +6,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Memory { - - public static class TestStructs { public struct Foo : IEquatable @@ -29,6 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Memory { result[i] = new Foo(i + 1, i + 1); } + return result; } @@ -39,16 +38,15 @@ namespace SixLabors.ImageSharp.Tests.Memory public override int GetHashCode() { int hashCode = -1817952719; - hashCode = hashCode * -1521134295 + base.GetHashCode(); - hashCode = hashCode * -1521134295 + this.A.GetHashCode(); - hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + hashCode = (hashCode * -1521134295) + base.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); return hashCode; } public override string ToString() => $"({this.A},{this.B})"; } - /// /// sizeof(AlignedFoo) == sizeof(long) /// @@ -80,17 +78,18 @@ namespace SixLabors.ImageSharp.Tests.Memory { result[i] = new AlignedFoo(i + 1, i + 1); } + return result; } public override int GetHashCode() { int hashCode = -1817952719; - hashCode = hashCode * -1521134295 + base.GetHashCode(); - hashCode = hashCode * -1521134295 + this.A.GetHashCode(); - hashCode = hashCode * -1521134295 + this.B.GetHashCode(); + hashCode = (hashCode * -1521134295) + base.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.A.GetHashCode(); + hashCode = (hashCode * -1521134295) + this.B.GetHashCode(); return hashCode; } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index d2448f3d2..7069b0346 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -18,7 +18,14 @@ namespace SixLabors.ImageSharp.Tests { public enum TestImageWriteFormat { + /// + /// Writes a jpg file. + /// Jpeg, + + /// + /// Writes a png file. + /// Png } @@ -257,18 +264,18 @@ namespace SixLabors.ImageSharp.Tests exifProfile.Sync(metaData); - Assert.Equal(100, (metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, (metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); metaData.VerticalResolution = 150; - Assert.Equal(100, (metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(300, (metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(300, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); exifProfile.Sync(metaData); - Assert.Equal(100, (metaData.ExifProfile.GetValue(ExifTag.XResolution).Value).ToDouble()); - Assert.Equal(150, (metaData.ExifProfile.GetValue(ExifTag.YResolution).Value).ToDouble()); + Assert.Equal(100, metaData.ExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); + Assert.Equal(150, metaData.ExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); } [Fact] @@ -387,7 +394,7 @@ namespace SixLabors.ImageSharp.Tests public void ProfileToByteArray() { // Arrange - byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker; + byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); ExifProfile expectedProfile = CreateExifProfile(); var expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index a6ad8df8b..64219fce0 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -6,7 +6,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests { - public class ExifDescriptionAttributeTests + public class ExifTagDescriptionAttributeTests { [Fact] public void TestExifTag() diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index 231ad8f0d..495057455 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using SixLabors.ImageSharp.Metadata.Profiles.Exif; using Xunit; diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs similarity index 86% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs index b6fa98b61..4ca7f84a5 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.CurvesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderCurvesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadOneDimensionalCurve(byte[] data, IccOneDimensionalCurve expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccOneDimensionalCurve output = reader.ReadOneDimensionalCurve(); @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadResponseCurve(byte[] data, IccResponseCurve expected, int channelCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccResponseCurve output = reader.ReadResponseCurve(channelCount); @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadParametricCurve(byte[] data, IccParametricCurve expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccParametricCurve output = reader.ReadParametricCurve(); @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadCurveSegment(byte[] data, IccCurveSegment expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccCurveSegment output = reader.ReadCurveSegment(); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadFormulaCurveElement(byte[] data, IccFormulaCurveElement expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccFormulaCurveElement output = reader.ReadFormulaCurveElement(); @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void ReadSampledCurveElement(byte[] data, IccSampledCurveElement expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccSampledCurveElement output = reader.ReadSampledCurveElement(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.LutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs similarity index 86% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.LutTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs index 04284cb49..96c897537 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.LutTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderLutTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] internal void ReadClut(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, bool isFloat) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccClut output = reader.ReadClut(inChannelCount, outChannelCount, isFloat); @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] internal void ReadClut8(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccClut output = reader.ReadClut8(inChannelCount, outChannelCount, gridPointCount); @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] internal void ReadClut16(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccClut output = reader.ReadClut16(inChannelCount, outChannelCount, gridPointCount); @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] internal void ReadClutF32(byte[] data, IccClut expected, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccClut output = reader.ReadClutF32(inChannelCount, outChannelCount, gridPointCount); @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] internal void ReadLut8(byte[] data, IccLut expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccLut output = reader.ReadLut8(); @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] internal void ReadLut16(byte[] data, IccLut expected, int count) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccLut output = reader.ReadLut16(count); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs similarity index 86% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs index 7987e9410..8245d26e0 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.MatrixTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMatrixTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void ReadMatrix2D(byte[] data, int xCount, int yCount, bool isSingle, float[,] expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); float[,] output = reader.ReadMatrix(xCount, yCount, isSingle); @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void ReadMatrix1D(byte[] data, int yCount, bool isSingle, float[] expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); float[] output = reader.ReadMatrix(yCount, isSingle); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs similarity index 66% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs index f9e5428cd..412d5fc07 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.MultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderMultiProcessElementTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -9,10 +9,10 @@ namespace SixLabors.ImageSharp.Tests.Icc public class IccDataReaderMultiProcessElementTests { [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadMultiProcessElement(byte[] data, IccMultiProcessElement expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccMultiProcessElement output = reader.ReadMultiProcessElement(); @@ -20,10 +20,10 @@ namespace SixLabors.ImageSharp.Tests.Icc } [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadCurveSetProcessElement(byte[] data, IccCurveSetProcessElement expected, int inChannelCount, int outChannelCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccCurveSetProcessElement output = reader.ReadCurveSetProcessElement(inChannelCount, outChannelCount); @@ -31,10 +31,10 @@ namespace SixLabors.ImageSharp.Tests.Icc } [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadMatrixProcessElement(byte[] data, IccMatrixProcessElement expected, int inChannelCount, int outChannelCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccMatrixProcessElement output = reader.ReadMatrixProcessElement(inChannelCount, outChannelCount); @@ -42,10 +42,10 @@ namespace SixLabors.ImageSharp.Tests.Icc } [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void ReadClutProcessElement(byte[] data, IccClutProcessElement expected, int inChannelCount, int outChannelCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccClutProcessElement output = reader.ReadClutProcessElement(inChannelCount, outChannelCount); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs similarity index 85% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs index 6296390a0..a050f3859 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.NonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderNonPrimitivesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void ReadDateTime(byte[] data, DateTime expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); DateTime output = reader.ReadDateTime(); @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void ReadVersionNumber(byte[] data, IccVersion expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccVersion output = reader.ReadVersionNumber(); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void ReadXyzNumber(byte[] data, Vector3 expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); Vector3 output = reader.ReadXyzNumber(); @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadProfileId(byte[] data, IccProfileId expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccProfileId output = reader.ReadProfileId(); @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadPositionNumber(byte[] data, IccPositionNumber expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccPositionNumber output = reader.ReadPositionNumber(); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadResponseNumber(byte[] data, IccResponseNumber expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccResponseNumber output = reader.ReadResponseNumber(); @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadNamedColor(byte[] data, IccNamedColor expected, uint coordinateCount) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccNamedColor output = reader.ReadNamedColor(coordinateCount); @@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionReadTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadProfileDescription(byte[] data, IccProfileDescription expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccProfileDescription output = reader.ReadProfileDescription(); @@ -102,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ColorantTableEntryTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadColorantTableEntry(byte[] data, IccColorantTableEntry expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccColorantTableEntry output = reader.ReadColorantTableEntry(); @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void ReadScreeningChannel(byte[] data, IccScreeningChannel expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); IccScreeningChannel output = reader.ReadScreeningChannel(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs similarity index 82% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs index 027530329..bd9eb1ea8 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.PrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderPrimitivesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.AsciiTestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadAsciiString(byte[] textBytes, int length, string expected) { - IccDataReader reader = CreateReader(textBytes); + IccDataReader reader = this.CreateReader(textBytes); string output = reader.ReadAsciiString(length); @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void ReadAsciiStringWithNegativeLengthThrowsArgumentException() { - IccDataReader reader = CreateReader(new byte[4]); + IccDataReader reader = this.CreateReader(new byte[4]); Assert.Throws(() => reader.ReadAsciiString(-1)); } @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void ReadUnicodeStringWithNegativeLengthThrowsArgumentException() { - IccDataReader reader = CreateReader(new byte[4]); + IccDataReader reader = this.CreateReader(new byte[4]); Assert.Throws(() => reader.ReadUnicodeString(-1)); } @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadFix16(byte[] data, float expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); float output = reader.ReadFix16(); @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadUFix16(byte[] data, float expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); float output = reader.ReadUFix16(); @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadU1Fix15(byte[] data, float expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); float output = reader.ReadU1Fix15(); @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] public void ReadUFix8(byte[] data, float expected) { - IccDataReader reader = CreateReader(data); + IccDataReader reader = this.CreateReader(data); float output = reader.ReadUFix8(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs similarity index 99% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs index dc2c5b6ac..a18fb1ab8 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataReader/IccDataReaderTagDataEntryTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs similarity index 87% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs index 585bda648..39ebf3374 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.CurvesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterCurvesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.OneDimensionalCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteOneDimensionalCurve(byte[] expected, IccOneDimensionalCurve data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteOneDimensionalCurve(data); byte[] output = writer.GetData(); @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ResponseCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteResponseCurve(byte[] expected, IccResponseCurve data, int channelCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteResponseCurve(data); byte[] output = writer.GetData(); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.ParametricCurveTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteParametricCurve(byte[] expected, IccParametricCurve data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteParametricCurve(data); byte[] output = writer.GetData(); @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.CurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteCurveSegment(byte[] expected, IccCurveSegment data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteCurveSegment(data); byte[] output = writer.GetData(); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.FormulaCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteFormulaCurveElement(byte[] expected, IccFormulaCurveElement data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteFormulaCurveElement(data); byte[] output = writer.GetData(); @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataCurves.SampledCurveSegmentTestData), MemberType = typeof(IccTestDataCurves))] internal void WriteSampledCurveElement(byte[] expected, IccSampledCurveElement data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteSampledCurveElement(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs similarity index 86% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs index 621673ce4..6245d8bb6 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.LutTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteClut(data); byte[] output = writer.GetData(); @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteClut8(data); byte[] output = writer.GetData(); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteClut16(data); byte[] output = writer.GetData(); @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteClutF32(data); byte[] output = writer.GetData(); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut8(byte[] expected, IccLut data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteLut8(data); byte[] output = writer.GetData(); @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] internal void WriteLut16(byte[] expected, IccLut data, int count) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteLut16(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs new file mode 100644 index 000000000..15cd27b94 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests1.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterLutTests1 + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs new file mode 100644 index 000000000..7c301c754 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterLutTests2.cs @@ -0,0 +1,88 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Icc +{ + public class IccDataWriterLutTests2 + { + [Theory] + [MemberData(nameof(IccTestDataLut.ClutTestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutAll(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, bool isFloat) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut8(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Clut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClut16(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.ClutF32TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteClutF32(byte[] expected, IccClut data, int inChannelCount, int outChannelCount, byte[] gridPointCount) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteClutF32(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut8TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut8(byte[] expected, IccLut data) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut8(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + [Theory] + [MemberData(nameof(IccTestDataLut.Lut16TestData), MemberType = typeof(IccTestDataLut))] + internal void WriteLut16(byte[] expected, IccLut data, int count) + { + IccDataWriter writer = this.CreateWriter(); + + writer.WriteLut16(data); + byte[] output = writer.GetData(); + + Assert.Equal(expected, output); + } + + private IccDataWriter CreateWriter() + { + return new IccDataWriter(); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs similarity index 88% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs index 3667fda14..0873874af 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MatrixTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMatrixTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix2D_FloatArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix2D_Array(byte[] expected, int xCount, int yCount, bool isSingle, float[,] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix2D_Matrix4x4TestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix2D_Matrix4x4(byte[] expected, int xCount, int yCount, bool isSingle, Matrix4x4 data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix2D_DenseMatrixTestData), MemberType = typeof(IccTestDataMatrix))] internal void WriteMatrix2D_DenseMatrix(byte[] expected, int xCount, int yCount, bool isSingle, in DenseMatrix data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix1D_ArrayTestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix1D_Array(byte[] expected, int yCount, bool isSingle, float[] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataMatrix.Matrix1D_Vector3TestData), MemberType = typeof(IccTestDataMatrix))] public void WriteMatrix1D_Vector3(byte[] expected, int yCount, bool isSingle, Vector3 data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMatrix(data, isSingle); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs similarity index 66% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs index 829b556af..2888958b0 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.MultiProcessElementTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterMultiProcessElementTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -9,10 +9,10 @@ namespace SixLabors.ImageSharp.Tests.Icc public class IccDataWriterMultiProcessElementTests { [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.MultiProcessElementTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteMultiProcessElement(byte[] expected, IccMultiProcessElement data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMultiProcessElement(data); byte[] output = writer.GetData(); @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Tests.Icc } [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.CurveSetTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteCurveSetProcessElement(byte[] expected, IccCurveSetProcessElement data, int inChannelCount, int outChannelCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteCurveSetProcessElement(data); byte[] output = writer.GetData(); @@ -33,10 +33,10 @@ namespace SixLabors.ImageSharp.Tests.Icc } [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.MatrixTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteMatrixProcessElement(byte[] expected, IccMatrixProcessElement data, int inChannelCount, int outChannelCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMatrixProcessElement(data); byte[] output = writer.GetData(); @@ -45,10 +45,10 @@ namespace SixLabors.ImageSharp.Tests.Icc } [Theory] - [MemberData(nameof(IccTestDataMultiProcessElement.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElement))] + [MemberData(nameof(IccTestDataMultiProcessElements.ClutTestData), MemberType = typeof(IccTestDataMultiProcessElements))] internal void WriteClutProcessElement(byte[] expected, IccClutProcessElement data, int inChannelCount, int outChannelCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteClutProcessElement(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs similarity index 87% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs index ed8a10551..c88ea3a95 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.NonPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterNonPrimitivesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.DateTimeTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void WriteDateTime(byte[] expected, DateTime data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteDateTime(data); byte[] output = writer.GetData(); @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.VersionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void WriteVersionNumber(byte[] expected, IccVersion data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteVersionNumber(data); byte[] output = writer.GetData(); @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.XyzNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] public void WriteXyzNumber(byte[] expected, Vector3 data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteXyzNumber(data); byte[] output = writer.GetData(); @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileIdTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteProfileId(byte[] expected, IccProfileId data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteProfileId(data); byte[] output = writer.GetData(); @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.PositionNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WritePositionNumber(byte[] expected, IccPositionNumber data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WritePositionNumber(data); byte[] output = writer.GetData(); @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ResponseNumberTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteResponseNumber(byte[] expected, IccResponseNumber data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteResponseNumber(data); byte[] output = writer.GetData(); @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.NamedColorTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteNamedColor(byte[] expected, IccNamedColor data, uint coordinateCount) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteNamedColor(data); byte[] output = writer.GetData(); @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ProfileDescriptionWriteTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteProfileDescription(byte[] expected, IccProfileDescription data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteProfileDescription(data); byte[] output = writer.GetData(); @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataNonPrimitives.ScreeningChannelTestData), MemberType = typeof(IccTestDataNonPrimitives))] internal void WriteScreeningChannel(byte[] expected, IccScreeningChannel data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteScreeningChannel(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs similarity index 86% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs index 464a70141..20e4a7141 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.PrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.AsciiWriteTestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteAsciiString(byte[] expected, string data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteAsciiString(data); byte[] output = writer.GetData(); @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.AsciiPaddingTestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteAsciiStringPadded(byte[] expected, int length, string data, bool ensureNullTerminator) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteAsciiString(data, length, ensureNullTerminator); byte[] output = writer.GetData(); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteAsciiStringWithNullWritesEmpty() { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); int count = writer.WriteAsciiString(null); byte[] output = writer.GetData(); @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteAsciiStringWithNegativeLengthThrowsArgumentException() { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); Assert.Throws(() => writer.WriteAsciiString("abcd", -1, false)); } @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteUnicodeStringWithNullWritesEmpty() { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); int count = writer.WriteUnicodeString(null); byte[] output = writer.GetData(); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.Fix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteFix16(byte[] expected, float data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteFix16(data); byte[] output = writer.GetData(); @@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix16TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteUFix16(byte[] expected, float data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUFix16(data); byte[] output = writer.GetData(); @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.U1Fix15TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteU1Fix15(byte[] expected, float data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteU1Fix15(data); byte[] output = writer.GetData(); @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataPrimitives.UFix8TestData), MemberType = typeof(IccTestDataPrimitives))] public void WriteUFix8(byte[] expected, float data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUFix8(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs similarity index 88% rename from tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs rename to tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs index 269a8d561..85e11c856 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntryTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTagDataEntryTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UnknownTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUnknownTagDataEntry(byte[] expected, IccUnknownTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUnknownTagDataEntry(data); byte[] output = writer.GetData(); @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ChromaticityTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteChromaticityTagDataEntry(byte[] expected, IccChromaticityTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteChromaticityTagDataEntry(data); byte[] output = writer.GetData(); @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ColorantOrderTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteColorantOrderTagDataEntry(byte[] expected, IccColorantOrderTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteColorantOrderTagDataEntry(data); byte[] output = writer.GetData(); @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ColorantTableTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteColorantTableTagDataEntry(byte[] expected, IccColorantTableTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteColorantTableTagDataEntry(data); byte[] output = writer.GetData(); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.CurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteCurveTagDataEntry(byte[] expected, IccCurveTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteCurveTagDataEntry(data); byte[] output = writer.GetData(); @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.DataTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteDataTagDataEntry(byte[] expected, IccDataTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteDataTagDataEntry(data); byte[] output = writer.GetData(); @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.DateTimeTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteDateTimeTagDataEntry(byte[] expected, IccDateTimeTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteDateTimeTagDataEntry(data); byte[] output = writer.GetData(); @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.Lut16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLut16TagDataEntry(byte[] expected, IccLut16TagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteLut16TagDataEntry(data); byte[] output = writer.GetData(); @@ -108,11 +108,11 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.Lut8TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLut8TagDataEntry(byte[] expected, IccLut8TagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteLut8TagDataEntry(data); byte[] output = writer.GetData(); - + Assert.Equal(expected, output); } @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.LutAToBTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLutAToBTagDataEntry(byte[] expected, IccLutAToBTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteLutAtoBTagDataEntry(data); byte[] output = writer.GetData(); @@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.LutBToATagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteLutBToATagDataEntry(byte[] expected, IccLutBToATagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteLutBtoATagDataEntry(data); byte[] output = writer.GetData(); @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.MeasurementTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteMeasurementTagDataEntry(byte[] expected, IccMeasurementTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMeasurementTagDataEntry(data); byte[] output = writer.GetData(); @@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.MultiLocalizedUnicodeTagDataEntryTestData_Write), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteMultiLocalizedUnicodeTagDataEntry(byte[] expected, IccMultiLocalizedUnicodeTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMultiLocalizedUnicodeTagDataEntry(data); byte[] output = writer.GetData(); @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.MultiProcessElementsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteMultiProcessElementsTagDataEntry(byte[] expected, IccMultiProcessElementsTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteMultiProcessElementsTagDataEntry(data); byte[] output = writer.GetData(); @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.NamedColor2TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteNamedColor2TagDataEntry(byte[] expected, IccNamedColor2TagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteNamedColor2TagDataEntry(data); byte[] output = writer.GetData(); @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ParametricCurveTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteParametricCurveTagDataEntry(byte[] expected, IccParametricCurveTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteParametricCurveTagDataEntry(data); byte[] output = writer.GetData(); @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceDescTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteProfileSequenceDescTagDataEntry(byte[] expected, IccProfileSequenceDescTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteProfileSequenceDescTagDataEntry(data); byte[] output = writer.GetData(); @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ProfileSequenceIdentifierTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteProfileSequenceIdentifierTagDataEntry(byte[] expected, IccProfileSequenceIdentifierTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteProfileSequenceIdentifierTagDataEntry(data); byte[] output = writer.GetData(); @@ -228,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ResponseCurveSet16TagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteResponseCurveSet16TagDataEntry(byte[] expected, IccResponseCurveSet16TagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteResponseCurveSet16TagDataEntry(data); byte[] output = writer.GetData(); @@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.Fix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteFix16ArrayTagDataEntry(byte[] expected, IccFix16ArrayTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteFix16ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.SignatureTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteSignatureTagDataEntry(byte[] expected, IccSignatureTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteSignatureTagDataEntry(data); byte[] output = writer.GetData(); @@ -264,7 +264,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.TextTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteTextTagDataEntry(byte[] expected, IccTextTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteTextTagDataEntry(data); byte[] output = writer.GetData(); @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UFix16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUFix16ArrayTagDataEntry(byte[] expected, IccUFix16ArrayTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUFix16ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -288,7 +288,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt16ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt16ArrayTagDataEntry(byte[] expected, IccUInt16ArrayTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUInt16ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt32ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt32ArrayTagDataEntry(byte[] expected, IccUInt32ArrayTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUInt32ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -312,7 +312,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt64ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt64ArrayTagDataEntry(byte[] expected, IccUInt64ArrayTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUInt64ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UInt8ArrayTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUInt8ArrayTagDataEntry(byte[] expected, IccUInt8ArrayTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUInt8ArrayTagDataEntry(data); byte[] output = writer.GetData(); @@ -336,7 +336,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ViewingConditionsTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteViewingConditionsTagDataEntry(byte[] expected, IccViewingConditionsTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteViewingConditionsTagDataEntry(data); byte[] output = writer.GetData(); @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.XYZTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteXyzTagDataEntry(byte[] expected, IccXyzTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteXyzTagDataEntry(data); byte[] output = writer.GetData(); @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.TextDescriptionTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteTextDescriptionTagDataEntry(byte[] expected, IccTextDescriptionTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteTextDescriptionTagDataEntry(data); byte[] output = writer.GetData(); @@ -372,7 +372,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.CrdInfoTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteCrdInfoTagDataEntry(byte[] expected, IccCrdInfoTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteCrdInfoTagDataEntry(data); byte[] output = writer.GetData(); @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.ScreeningTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteScreeningTagDataEntry(byte[] expected, IccScreeningTagDataEntry data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteScreeningTagDataEntry(data); byte[] output = writer.GetData(); @@ -396,7 +396,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataTagDataEntry.UcrBgTagDataEntryTestData), MemberType = typeof(IccTestDataTagDataEntry))] internal void WriteUcrBgTagDataEntry(byte[] expected, IccUcrBgTagDataEntry data, uint size) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteUcrBgTagDataEntry(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs index b7b446699..7249e03aa 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteEmpty() { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteEmpty(4); byte[] output = writer.GetData(); @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [InlineData(4, 4)] public void WritePadding(int writePosition, int expectedLength) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteEmpty(writePosition); writer.WritePadding(); @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt8TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt8(byte[] data, byte[] expected) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt16TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt16(byte[] expected, ushort[] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.Int16TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayInt16(byte[] expected, short[] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt32TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt32(byte[] expected, uint[] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.Int32TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayInt32(byte[] expected, int[] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [MemberData(nameof(IccTestDataArray.UInt64TestData), MemberType = typeof(IccTestDataArray))] public void WriteArrayUInt64(byte[] expected, ulong[] data) { - IccDataWriter writer = CreateWriter(); + IccDataWriter writer = this.CreateWriter(); writer.WriteArray(data); byte[] output = writer.GetData(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs index b4ed52a3d..1502b8b30 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccReaderTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void ReadProfile_NoEntries() { - IccReader reader = CreateReader(); + IccReader reader = this.CreateReader(); IccProfile output = reader.Read(IccTestDataProfiles.Header_Random_Array); @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void ReadProfile_DuplicateEntry() { - IccReader reader = CreateReader(); + IccReader reader = this.CreateReader(); IccProfile output = reader.Read(IccTestDataProfiles.Profile_Random_Array); @@ -50,7 +50,6 @@ namespace SixLabors.ImageSharp.Tests.Icc Assert.True(ReferenceEquals(output.Entries[0], output.Entries[1])); } - private IccReader CreateReader() { return new IccReader(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs index e66554b85..c4ac921b1 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/IccWriterTests.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteProfile_NoEntries() { - IccWriter writer = CreateWriter(); + IccWriter writer = this.CreateWriter(); var profile = new IccProfile { @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Icc [Fact] public void WriteProfile_DuplicateEntry() { - IccWriter writer = CreateWriter(); + IccWriter writer = this.CreateWriter(); byte[] output = writer.Write(IccTestDataProfiles.Profile_Random_Val); diff --git a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs index 1ccf485fe..74b9ef1c8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Argb32Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Argb32_PackedValue() { Assert.Equal(0x80001a00u, new Argb32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); - Assert.Equal((uint)0x0, new Argb32(Vector4.Zero).PackedValue); + Assert.Equal(0x0U, new Argb32(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Argb32(Vector4.One).PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs index 7638c5f86..172349739 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr24Tests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); var color2 = new Bgr24(byte.MaxValue, 0, byte.MaxValue); - + Assert.Equal(color1, color2); } @@ -77,7 +77,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.False(a.Equals((object)b)); } - [Fact] public void FromRgba32() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs index 3043626ca..4dbd00cbb 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgr565Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -45,6 +45,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(6160, new Bgr565(0.1F, -0.3F, 0.5F).PackedValue); Assert.Equal(0x0, new Bgr565(Vector3.Zero).PackedValue); Assert.Equal(0xFFFF, new Bgr565(Vector3.One).PackedValue); + // Make sure the swizzle is correct. Assert.Equal(0xF800, new Bgr565(Vector3.UnitX).PackedValue); Assert.Equal(0x07E0, new Bgr565(Vector3.UnitY).PackedValue); @@ -191,10 +192,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgr = default(Bgr565); ushort expected = ushort.MaxValue; - + // act bgr.FromBgr24(new Bgr24(byte.MaxValue, byte.MaxValue, byte.MaxValue)); - + // assert Assert.Equal(expected, bgr.PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index 28c022709..6ee14c015 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); var color2 = new Bgra32(byte.MaxValue, byte.MaxValue, byte.MaxValue); - + Assert.Equal(color1, color2); } @@ -89,7 +89,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.False(x.Equals((object)y)); } - [Fact] public void FromRgba32() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs index 41ebfc955..e36d54b52 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra5551Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var color2 = new Bgra5551(new Vector4(0.0f)); var color3 = new Bgra5551(new Vector4(1.0f, 0.0f, 0.0f, 1.0f)); var color4 = new Bgra5551(1.0f, 0.0f, 0.0f, 1.0f); - + Assert.Equal(color1, color2); Assert.Equal(color3, color4); } @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgra = new Bgra5551(Vector4.One); - // act + // act Vector4 actual = bgra.ToScaledVector4(); // assert @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra5551); var expected = new Bgra5551(1.0f, 0.0f, 1.0f, 1.0f); - // act + // act bgra.FromBgra5551(expected); actual.FromBgra5551(bgra); @@ -171,10 +171,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var bgra = default(Bgra5551); ushort expectedPackedValue = ushort.MaxValue; - + // act bgra.FromArgb32(new Argb32(255, 255, 255, 255)); - + // assert Assert.Equal(expectedPackedValue, bgra.PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs index 2eb5553d7..487adc241 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Byte4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -42,9 +42,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Byte4_PackedValue() { - Assert.Equal((uint)128, new Byte4(127.5f, -12.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal((uint)0x1a7b362d, new Byte4(0x2d, 0x36, 0x7b, 0x1a).PackedValue); - Assert.Equal((uint)0x0, new Byte4(Vector4.Zero).PackedValue); + Assert.Equal(128U, new Byte4(127.5f, -12.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal(0x1a7b362dU, new Byte4(0x2d, 0x36, 0x7b, 0x1a).PackedValue); + Assert.Equal(0x0U, new Byte4(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Byte4(Vector4.One * 255).PackedValue); } @@ -195,10 +195,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var byte4 = default(Byte4); uint expectedPackedValue1 = uint.MaxValue; - + // act byte4.FromRgba32(new Rgba32(255, 255, 255, 255)); - + // assert Assert.Equal(expectedPackedValue1, byte4.PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 85a3b8b32..b1ae7fd13 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var halfSingle = new HalfSingle(-1F); - // act + // act Vector4 actual = halfSingle.ToScaledVector4(); // assert @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void HalfSingle_FromScaledVector4() { - // arrange + // arrange Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); int expected = 48128; var halfSingle = default(HalfSingle); diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index 57da5438c..1712a6e1e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { // arrange var halfVector2 = default(HalfVector2); - + // act halfVector2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index ed1a0b720..c529e1b51 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // arrange var halfVector4 = new HalfVector4(-Vector4.One); - // act + // act Vector4 actual = halfVector4.ToScaledVector4(); // assert @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); ulong expected = 13547034390470638592uL; - // act + // act halfVector4.FromScaledVector4(scaled); ulong actual = halfVector4.PackedValue; diff --git a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs index 13999ee3b..f9bb084de 100644 --- a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs @@ -146,7 +146,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } - [Theory] [MemberData(nameof(LuminanceData))] public void L8_ToRgba32(byte luminance) @@ -199,7 +198,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(original, mirror); } - [Theory] [MemberData(nameof(LuminanceData))] public void Rgba32_ToL8_IsInverseOf_L8_ToRgba32(byte luminance) @@ -224,10 +222,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Rgba32 rgba = default; original.ToRgba32(ref rgba); - var L8Vector = original.ToVector4(); + var l8Vector = original.ToVector4(); var rgbaVector = original.ToVector4(); - Assert.Equal(L8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); } [Theory] @@ -239,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Rgba32 rgba = default; original.ToRgba32(ref rgba); - Vector4 rgbaVector = original.ToVector4(); + var rgbaVector = original.ToVector4(); L8 mirror = default; mirror.FromVector4(rgbaVector); @@ -256,10 +254,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Rgba32 rgba = default; original.ToRgba32(ref rgba); - Vector4 L8Vector = original.ToScaledVector4(); + Vector4 l8Vector = original.ToScaledVector4(); Vector4 rgbaVector = original.ToScaledVector4(); - Assert.Equal(L8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(l8Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); } [Theory] diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs index 366335006..3ad2dccdd 100644 --- a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs @@ -149,7 +149,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(255, gray.A); } - [Theory] [MemberData(nameof(LuminanceData))] public void La16_ToRgba32(byte luminance) @@ -203,7 +202,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(original, mirror); } - [Theory] [MemberData(nameof(LuminanceData))] public void Rgba32_ToLa16_IsInverseOf_La16_ToRgba32(byte luminance) @@ -228,10 +226,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Rgba32 rgba = default; original.ToRgba32(ref rgba); - var La16Vector = original.ToVector4(); + var la16Vector = original.ToVector4(); var rgbaVector = original.ToVector4(); - Assert.Equal(La16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); } [Theory] @@ -260,10 +258,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Rgba32 rgba = default; original.ToRgba32(ref rgba); - Vector4 La16Vector = original.ToScaledVector4(); + Vector4 la16Vector = original.ToScaledVector4(); Vector4 rgbaVector = original.ToScaledVector4(); - Assert.Equal(La16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(la16Vector, rgbaVector, new ApproximateFloatComparer(1e-5f)); } [Theory] diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 7f02493b4..0ab703398 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -43,9 +43,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void NormalizedByte4_PackedValues() { Assert.Equal(0xA740DA0D, new NormalizedByte4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal((uint)958796544, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - Assert.Equal((uint)0x0, new NormalizedByte4(Vector4.Zero).PackedValue); - Assert.Equal((uint)0x7F7F7F7F, new NormalizedByte4(Vector4.One).PackedValue); + Assert.Equal(958796544U, new NormalizedByte4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(0x0U, new NormalizedByte4(Vector4.Zero).PackedValue); + Assert.Equal(0x7F7F7F7FU, new NormalizedByte4(Vector4.One).PackedValue); Assert.Equal(0x81818181, new NormalizedByte4(-Vector4.One).PackedValue); } @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new NormalizedByte4(-Vector4.One).ToScaledVector4(); uint expected = 0x81818181; - // act + // act pixel.FromScaledVector4(scaled); uint actual = pixel.PackedValue; diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index ff9350b70..a726cee4e 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -14,9 +14,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { Assert.Equal(0xE6672CCC, new NormalizedShort2(0.35f, -0.2f).PackedValue); Assert.Equal(3650751693, new NormalizedShort2(0.1f, -0.3f).PackedValue); - Assert.Equal((uint)0x0, new NormalizedShort2(Vector2.Zero).PackedValue); - Assert.Equal((uint)0x7FFF7FFF, new NormalizedShort2(Vector2.One).PackedValue); + Assert.Equal(0x0U, new NormalizedShort2(Vector2.Zero).PackedValue); + Assert.Equal(0x7FFF7FFFU, new NormalizedShort2(Vector2.One).PackedValue); Assert.Equal(0x80018001, new NormalizedShort2(-Vector2.One).PackedValue); + // TODO: I don't think this can ever pass since the bytes are already truncated. // Assert.Equal(3650751693, n.PackedValue); } @@ -34,8 +35,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void NormalizedShort2_ToVector4() { - Assert.Equal(new Vector4(1, 1, 0, 1), (new NormalizedShort2(Vector2.One)).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), (new NormalizedShort2(Vector2.Zero)).ToVector4()); + Assert.Equal(new Vector4(1, 1, 0, 1), new NormalizedShort2(Vector2.One).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), new NormalizedShort2(Vector2.Zero).ToVector4()); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index 834bae685..96334be02 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -43,9 +43,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void NormalizedShort4_PackedValues() { Assert.Equal(0xa6674000d99a0ccd, new NormalizedShort4(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); - Assert.Equal((ulong)4150390751449251866, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); - Assert.Equal((ulong)0x0, new NormalizedShort4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new NormalizedShort4(Vector4.One).PackedValue); + Assert.Equal(4150390751449251866UL, new NormalizedShort4(0.0008f, 0.15f, 0.30f, 0.45f).PackedValue); + Assert.Equal(0x0UL, new NormalizedShort4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new NormalizedShort4(Vector4.One).PackedValue); Assert.Equal(0x8001800180018001, new NormalizedShort4(-Vector4.One).PackedValue); } @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Vector4 scaled = new NormalizedShort4(Vector4.One).ToScaledVector4(); ulong expected = 0x7FFF7FFF7FFF7FFF; - // act + // act pixel.FromScaledVector4(scaled); ulong actual = pixel.PackedValue; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs similarity index 66% rename from tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs rename to tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 74360e857..e2d370cc0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -22,8 +22,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - + { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, @@ -33,12 +32,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - }; + }; [Theory] [MemberData(nameof(BlenderMappings))] public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); Assert.IsType(type, blender); @@ -46,16 +45,15 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public static TheoryData ColorBlendingExpectedResults = new TheoryData { - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Normal, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, - + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Normal, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) }, }; [Theory] @@ -63,28 +61,26 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult) { PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver); - Rgba32 actualResult = blender.Blend(backdrop, source, opacity); // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); } public static TheoryData AlphaCompositionExpectedResults = new TheoryData { - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, - { Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Color.MistyRose }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, + { Color.MistyRose, Color.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue }, }; [Theory] @@ -96,7 +92,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Rgba32 actualResult = blender.Blend(backdrop, source, opacity); // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults - Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 693dd6bd8..a91ea0977 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders PixelAlphaCompositionMode.DestOut, PixelAlphaCompositionMode.Clear, PixelAlphaCompositionMode.Xor - }; + }; [Theory] [WithFile(TestImages.Png.PDDest, nameof(CompositingOperators), PixelTypes.Rgba32)] @@ -46,7 +46,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { string combinedMode = mode.ToString(); - if (combinedMode != "Src" && combinedMode.StartsWith("Src")) combinedMode = combinedMode.Substring(3); + if (combinedMode != "Src" && combinedMode.StartsWith("Src")) + { + combinedMode = combinedMode.Substring(3); + } res.DebugSave(provider, combinedMode); res.CompareToReferenceOutput(provider, combinedMode); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index e397f70b0..7831dc124 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -10,9 +10,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders { public class PorterDuffFunctionsTests { - public static TheoryData NormalBlendFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + public static TheoryData NormalBlendFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) } }; [Theory] @@ -23,15 +24,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders Assert.Equal(expected, actual); } - public static TheoryData MultiplyFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, - { - new TestVector4(0.9f,0.9f,0.9f,0.9f), - new TestVector4(0.4f,0.4f,0.4f,0.4f), - .5f, - new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) - }, + public static TheoryData MultiplyFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + { new TestVector4(0.9f, 0.9f, 0.9f, 0.9f), new TestVector4(0.4f, 0.4f, 0.4f, 0.4f), .5f, new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) } }; [Theory] @@ -42,15 +39,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData AddFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2075676f, .2075676f, .2075676f, .37f) - }, + public static TheoryData AddFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2075676f, .2075676f, .2075676f, .37f) } }; [Theory] @@ -61,15 +54,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData SubtractFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(0,0,0,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2027027f, .2027027f, .2027027f, .37f) - }, + public static TheoryData SubtractFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2027027f, .2027027f, .2027027f, .37f) } }; [Theory] @@ -80,15 +69,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData ScreenFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2383784f, .2383784f, .2383784f, .37f) - }, + public static TheoryData ScreenFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2383784f, .2383784f, .2383784f, .37f) } }; [Theory] @@ -99,15 +84,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData DarkenFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f,.6f,.6f, 1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2189189f, .2189189f, .2189189f, .37f) - }, + public static TheoryData DarkenFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2189189f, .2189189f, .2189189f, .37f) } }; [Theory] @@ -118,15 +99,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData LightenFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.227027f, .227027f, .227027f, .37f) - }, + public static TheoryData LightenFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.227027f, .227027f, .227027f, .37f) }, }; [Theory] @@ -137,15 +114,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData OverlayFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2124324f, .2124324f, .2124324f, .37f) - }, + public static TheoryData OverlayFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(1, 1, 1, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2124324f, .2124324f, .2124324f, .37f) }, }; [Theory] @@ -156,15 +129,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 5); } - public static TheoryData HardLightFunctionData = new TheoryData { - { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, - { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f,0.6f,0.6f,1f) }, - { - new TestVector4(0.2f,0.2f,0.2f,0.3f), - new TestVector4(0.3f,0.3f,0.3f,0.2f), - .5f, - new TestVector4(.2124324f, .2124324f, .2124324f, .37f) - }, + public static TheoryData HardLightFunctionData = new TheoryData + { + { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, + { new TestVector4(1, 1, 1, 1), new TestVector4(0, 0, 0, .8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1f) }, + { new TestVector4(0.2f, 0.2f, 0.2f, 0.3f), new TestVector4(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestVector4(.2124324f, .2124324f, .2124324f, .37f) }, }; [Theory] diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs similarity index 74% rename from tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs rename to tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs index 6706e4077..f41fbc022 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -17,9 +17,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders return new Span(new[] { value }); } - public static TheoryData NormalBlendFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + public static TheoryData NormalBlendFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) } }; private Configuration Configuration => Configuration.Default; @@ -27,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(NormalBlendFunctionData))] public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.NormalSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -36,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(NormalBlendFunctionData))] public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -45,19 +46,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(NormalBlendFunctionData))] public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData MultiplyFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + public static TheoryData MultiplyFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, { - new TestPixel(0.9f,0.9f,0.9f,0.9f), - new TestPixel(0.4f,0.4f,0.4f,0.4f), + new TestPixel(0.9f, 0.9f, 0.9f, 0.9f), + new TestPixel(0.4f, 0.4f, 0.4f, 0.4f), .5f, new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) }, @@ -66,7 +68,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(MultiplyFunctionData))] public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.MultiplySrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -75,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(MultiplyFunctionData))] public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -84,28 +86,39 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(MultiplyFunctionData))] public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData AddFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1f, 1f, 1f, 1f) }, + public static TheoryData AddFunctionData = new TheoryData + { + { + new TestPixel(1, 1, 1, 1), + new TestPixel(1, 1, 1, 1), + 1, + new TestPixel(1, 1, 1, 1) + }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(1, 1, 1, 1), + new TestPixel(0, 0, 0, .8f), .5f, - new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) + new TestPixel(1f, 1f, 1f, 1f) }, + { + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), + .5f, + new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) + } }; [Theory] [MemberData(nameof(AddFunctionData))] public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.AddSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -114,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(AddFunctionData))] public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -123,19 +136,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(AddFunctionData))] public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData SubtractFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(0,0,0,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + public static TheoryData SubtractFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(0, 0, 0, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestPixel(.2027027f, .2027027f, .2027027f, .37f) }, @@ -144,7 +158,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(SubtractFunctionData))] public void SubtractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.SubtractSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -153,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(SubtractFunctionData))] public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -162,19 +176,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(SubtractFunctionData))] public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData ScreenFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + public static TheoryData ScreenFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestPixel(.2383784f, .2383784f, .2383784f, .37f) }, @@ -183,7 +198,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(ScreenFunctionData))] public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.ScreenSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -192,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(ScreenFunctionData))] public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -201,19 +216,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(ScreenFunctionData))] public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData DarkenFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f,.6f,.6f, 1f) }, + public static TheoryData DarkenFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(.6f, .6f, .6f, 1f) }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestPixel(.2189189f, .2189189f, .2189189f, .37f) }, @@ -222,7 +238,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(DarkenFunctionData))] public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.DarkenSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -231,7 +247,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(DarkenFunctionData))] public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -240,28 +256,29 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(DarkenFunctionData))] public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData LightenFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + public static TheoryData LightenFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestPixel(.227027f, .227027f, .227027f, .37f) - }, + } }; [Theory] [MemberData(nameof(LightenFunctionData))] public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.LightenSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -270,7 +287,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(LightenFunctionData))] public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -279,28 +296,29 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(LightenFunctionData))] public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData OverlayFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + public static TheoryData OverlayFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(1, 1, 1, 1f) }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestPixel(.2124324f, .2124324f, .2124324f, .37f) - }, + } }; [Theory] [MemberData(nameof(OverlayFunctionData))] public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.OverlaySrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -309,7 +327,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(OverlayFunctionData))] public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -318,19 +336,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(OverlayFunctionData))] public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected.AsPixel(), dest[0], 2); } - public static TheoryData HardLightFunctionData = new TheoryData { - { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f,0.6f,0.6f,1f) }, + public static TheoryData HardLightFunctionData = new TheoryData + { + { new TestPixel(1, 1, 1, 1), new TestPixel(1, 1, 1, 1), 1, new TestPixel(1, 1, 1, 1) }, + { new TestPixel(1, 1, 1, 1), new TestPixel(0, 0, 0, .8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1f) }, { - new TestPixel(0.2f,0.2f,0.2f,0.3f), - new TestPixel(0.3f,0.3f,0.3f,0.2f), + new TestPixel(0.2f, 0.2f, 0.2f, 0.3f), + new TestPixel(0.3f, 0.3f, 0.3f, 0.2f), .5f, new TestPixel(.2124324f, .2124324f, .2124324f, .37f) }, @@ -339,7 +358,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(HardLightFunctionData))] public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = PorterDuffFunctions.HardLightSrcOver(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -348,7 +367,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(HardLightFunctionData))] public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); @@ -357,7 +376,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders [Theory] [MemberData(nameof(HardLightFunctionData))] public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var dest = new Span(new TPixel[1]); new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 6a678abc7..2ff5157b7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -1,11 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. - -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. - -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using System; using System.Runtime.CompilerServices; @@ -53,7 +47,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) - where TSourcePixel : struct, IPixel where TDestinationPixel : struct, IPixel + where TSourcePixel : unmanaged, IPixel + where TDestinationPixel : unmanaged, IPixel { Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); @@ -75,12 +70,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats // packs/unpacks the pixel without and conversion so we employ custom methods do do this. if (typeof(TDestinationPixel) == typeof(L16)) { - ref L16 L16Ref = ref MemoryMarshal.GetReference( - MemoryMarshal.Cast(destinationPixels)); + ref L16 l16Ref = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(destinationPixels)); for (int i = 0; i < count; i++) { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref L16Ref, i); + ref L16 dp = ref Unsafe.Add(ref l16Ref, i); dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); } @@ -89,12 +83,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats if (typeof(TDestinationPixel) == typeof(L8)) { - ref L8 L8Ref = ref MemoryMarshal.GetReference( + ref L8 l8Ref = ref MemoryMarshal.GetReference( MemoryMarshal.Cast(destinationPixels)); for (int i = 0; i < count; i++) { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref L8Ref, i); + ref L8 dp = ref Unsafe.Add(ref l8Ref, i); dp.ConvertFromRgbaScaledVector4(sp.ToScaledVector4()); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index f1f56fcd4..19623c3d8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -1,4 +1,7 @@ -using SixLabors.ImageSharp.PixelFormats; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; using Xunit; diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs index e98e14fc6..817c29aa1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelConversionModifiersExtensionsTests.cs @@ -1,9 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -38,8 +34,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations } [Theory] - [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand, - PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)] + [InlineData(PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand, PixelConversionModifiers.Scale, PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand)] [InlineData(PixelConversionModifiers.None, PixelConversionModifiers.Premultiply, PixelConversionModifiers.None)] internal void Remove( PixelConversionModifiers baselineModifiers, @@ -62,4 +57,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(expected, result); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs index c881ae96b..9a0f4d8ac 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.Argb32OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +12,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public class Argb32OperationsTests : PixelOperationsTests { - public Argb32OperationsTests(ITestOutputHelper output) : base(output) { @@ -22,4 +21,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs index ddcdc30bf..87aed91e7 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.L16OperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -19,7 +19,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Fact] public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs index f03aa5587..a17594af6 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La16OperationsTests.cs @@ -19,7 +19,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Fact] public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs index 2112a2fea..dce934286 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.La32OperationsTests.cs @@ -19,7 +19,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Fact] public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); - } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 9b6814f9a..9d48675f1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -18,14 +18,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public partial class PixelOperationsTests { +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter [Theory] [WithBlankImages(1, 1, PixelTypes.All)] public void GetGlobalInstance(TestImageProvider _) - where T : struct, IPixel => Assert.NotNull(PixelOperations.Instance); + where T : unmanaged, IPixel => Assert.NotNull(PixelOperations.Instance); } +#pragma warning restore SA1313 // Parameter names should begin with lower-case letter public abstract class PixelOperationsTests : MeasureFixture - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public const string SkipProfilingBenchmarks = #if true @@ -113,8 +115,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan())); } [Theory] @@ -138,18 +139,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { SRgbCompanding.Compress(ref v); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -158,62 +159,60 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); } [Theory] [MemberData(nameof(ArraySizesData))] public void FromPremultipliedVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, expected, - (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) - ); + (s, d) => Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); } [Theory] [MemberData(nameof(ArraySizesData))] public void FromPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -222,36 +221,35 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] [MemberData(nameof(ArraySizesData))] public void FromCompandedPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); if (this.HasAlpha) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { if (this.HasAlpha) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } SRgbCompanding.Compress(ref v); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => sourceAction(ref v)); - TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => expectedAction(ref v)); + Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); + TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -260,8 +258,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] @@ -274,11 +271,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan())); } - public static readonly TheoryData Generic_To_Data = new TheoryData { new TestPixel(), @@ -293,7 +288,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Theory] [MemberData(nameof(Generic_To_Data))] public void Generic_To(TestPixel dummy) - where TDestPixel : struct, IPixel + where TDestPixel : unmanaged, IPixel { const int Count = 2134; TPixel[] source = CreatePixelTestData(Count); @@ -304,7 +299,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation(source, expected, (s, d) => Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); } - [Theory] [MemberData(nameof(ArraySizesData))] public void ToScaledVector4(int count) @@ -326,18 +320,18 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void ToCompandedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { SRgbCompanding.Compress(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); } - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -346,50 +340,48 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); } [Theory] [MemberData(nameof(ArraySizesData))] public void ToPremultipliedVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } - TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, expected, - (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply) - ); + (s, d) => Operations.ToVector4(this.Configuration, s, d.GetSpan(), PixelConversionModifiers.Premultiply)); } [Theory] [MemberData(nameof(ArraySizesData))] public void ToPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -405,20 +397,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void ToCompandedPremultipliedScaledVector4(int count) { - void sourceAction(ref Vector4 v) + void SourceAction(ref Vector4 v) { - Vector4Utils.UnPremultiply(ref v); + Vector4Utilities.UnPremultiply(ref v); SRgbCompanding.Compress(ref v); } - void expectedAction(ref Vector4 v) + void ExpectedAction(ref Vector4 v) { SRgbCompanding.Expand(ref v); - Vector4Utils.Premultiply(ref v); + Vector4Utilities.Premultiply(ref v); } - TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => sourceAction(ref v)); - Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => expectedAction(ref v)); + TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); + Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); TestOperation( source, @@ -427,8 +419,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale) - ); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale)); } [Theory] @@ -448,8 +439,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -457,7 +447,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToArgb32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 4]; + byte[] expected = new byte[count * 4]; var argb = default(Argb32); for (int i = 0; i < count; i++) @@ -474,8 +464,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToArgb32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -495,8 +484,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -504,7 +492,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToBgr24Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 3]; + byte[] expected = new byte[count * 3]; var bgr = default(Bgr24); for (int i = 0; i < count; i++) @@ -519,8 +507,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgr24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -540,8 +527,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -549,7 +535,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToBgra32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 4]; + byte[] expected = new byte[count * 4]; var bgra = default(Bgra32); for (int i = 0; i < count; i++) @@ -565,8 +551,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -588,8 +573,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromBgra5551Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -598,7 +582,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { int size = Unsafe.SizeOf(); TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * size]; + byte[] expected = new byte[count * size]; Bgra5551 bgra = default; for (int i = 0; i < count; i++) @@ -613,8 +597,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToBgra5551Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -625,7 +608,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); var expected = new TPixel[count]; - for (int i = 0; i < count; i++) { expected[i].FromL8(source[i]); @@ -634,8 +616,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromL8(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.FromL8(this.Configuration, s, d.GetSpan())); } [Theory] @@ -653,8 +634,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToL8(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.ToL8(this.Configuration, s, d.GetSpan())); } [Theory] @@ -678,8 +658,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromL16(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.FromL16(this.Configuration, s, d.GetSpan())); } [Theory] @@ -697,8 +676,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToL16(this.Configuration, s, d.GetSpan()) - ); + (s, d) => Operations.ToL16(this.Configuration, s, d.GetSpan())); } [Theory] @@ -720,8 +698,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromLa16Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromLa16Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -730,7 +707,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { int size = Unsafe.SizeOf(); TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * size]; + byte[] expected = new byte[count * size]; La16 la = default; for (int i = 0; i < count; i++) @@ -745,8 +722,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToLa16Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -768,17 +744,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromLa32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] [MemberData(nameof(ArraySizesData))] public void ToLa32Bytes(int count) { - var size = Unsafe.SizeOf(); + int size = Unsafe.SizeOf(); TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * size]; + byte[] expected = new byte[count * size]; La32 la = default; for (int i = 0; i < count; i++) @@ -795,8 +770,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToLa32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -816,8 +790,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -825,7 +798,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgb24Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 3]; + byte[] expected = new byte[count * 3]; var rgb = default(Rgb24); for (int i = 0; i < count; i++) @@ -840,8 +813,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgb24Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -861,8 +833,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -870,7 +841,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgba32Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 4]; + byte[] expected = new byte[count * 4]; var rgba = default(Rgba32); for (int i = 0; i < count; i++) @@ -886,8 +857,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgba32Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -907,8 +877,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -916,7 +885,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgb48Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 6]; + byte[] expected = new byte[count * 6]; Rgb48 rgb = default; for (int i = 0; i < count; i++) @@ -935,8 +904,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgb48Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -956,8 +924,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.FromRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); } [Theory] @@ -965,7 +932,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public void ToRgba64Bytes(int count) { TPixel[] source = CreatePixelTestData(count); - var expected = new byte[count * 8]; + byte[] expected = new byte[count * 8]; Rgba64 rgba = default; for (int i = 0; i < count; i++) @@ -986,8 +953,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count) - ); + (s, d) => Operations.ToRgba64Bytes(this.Configuration, s, d.GetSpan(), count)); } public delegate void RefAction(ref T1 arg1); @@ -1050,6 +1016,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations result[i] = v; } + return result; } @@ -1091,24 +1058,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations internal static byte[] CreateByteTestData(int length) { - var result = new byte[length]; + byte[] result = new byte[length]; var rnd = new Random(42); // Deterministic random values for (int i = 0; i < result.Length; i++) { result[i] = (byte)rnd.Next(255); } + return result; } internal static Vector4 GetVector(Random rnd) { - return new Vector4( - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble(), - (float)rnd.NextDouble() - ); + return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); } [StructLayout(LayoutKind.Sequential)] @@ -1132,7 +1095,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations where TDest : struct { public TSource[] SourceBuffer { get; } + public IMemoryOwner ActualDestBuffer { get; } + public TDest[] ExpectedDestBuffer { get; } public TestBuffers(TSource[] source, TDest[] expectedDest) @@ -1158,6 +1123,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { // ReSharper disable PossibleNullReferenceException Assert.Equal(expected[i], actual[i], comparer); + // ReSharper restore PossibleNullReferenceException } } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs index bccaaf816..ad45b0771 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rg32Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats float x = 0xb6dc; float y = 0xA59f; Assert.Equal(0xa59fb6dc, new Rg32(x / 0xffff, y / 0xffff).PackedValue); - Assert.Equal((uint)6554, new Rg32(0.1f, -0.3f).PackedValue); + Assert.Equal(6554U, new Rg32(0.1f, -0.3f).PackedValue); // Test the limits. - Assert.Equal((uint)0x0, new Rg32(Vector2.Zero).PackedValue); + Assert.Equal(0x0U, new Rg32(Vector2.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rg32(Vector2.One).PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs index 3bddc21ab..6ab7b9c95 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb48Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var short3 = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); var expected = new Rgb48(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); - // act + // act Vector4 scaled = short3.ToScaledVector4(); pixel.FromScaledVector4(scaled); @@ -75,4 +75,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, rgb.B); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs index 56cb526a4..7b3f71985 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba1010102Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -46,12 +46,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats float y = 0x36d; float z = 0x3b7; float w = 0x1; - Assert.Equal((uint)0x7B7DB6DB, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); + Assert.Equal(0x7B7DB6DBU, new Rgba1010102(x / 0x3ff, y / 0x3ff, z / 0x3ff, w / 3).PackedValue); - Assert.Equal((uint)536871014, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); + Assert.Equal(536871014U, new Rgba1010102(0.1f, -0.3f, 0.5f, -0.7f).PackedValue); // Test the limits. - Assert.Equal((uint)0x0, new Rgba1010102(Vector4.Zero).PackedValue); + Assert.Equal(0x0U, new Rgba1010102(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rgba1010102(Vector4.One).PackedValue); } diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index 1ab5f8e3d..6656ba19c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Rgba32(0, 0, 0); var color2 = new Rgba32(0, 0, 0, 1F); - var color3 = Rgba32.FromHex("#000"); - var color4 = Rgba32.FromHex("#000F"); - var color5 = Rgba32.FromHex("#000000"); - var color6 = Rgba32.FromHex("#000000FF"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000F"); + var color5 = Rgba32.ParseHex("#000000"); + var color6 = Rgba32.ParseHex("#000000FF"); Assert.Equal(color1, color2); Assert.Equal(color1, color3); @@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { var color1 = new Rgba32(255, 0, 0, 255); var color2 = new Rgba32(0, 0, 0, 255); - var color3 = Rgba32.FromHex("#000"); - var color4 = Rgba32.FromHex("#000000"); - var color5 = Rgba32.FromHex("#FF000000"); + var color3 = Rgba32.ParseHex("#000"); + var color4 = Rgba32.ParseHex("#000000"); + var color5 = Rgba32.ParseHex("#FF000000"); Assert.NotEqual(color1, color2); Assert.NotEqual(color1, color3); @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void FromAndToHex() { // 8 digit hex matches css4 spec. RRGGBBAA - var color = Rgba32.FromHex("#AABBCCDD"); // 170, 187, 204, 221 + var color = Rgba32.ParseHex("#AABBCCDD"); // 170, 187, 204, 221 Assert.Equal(170, color.R); Assert.Equal(187, color.G); Assert.Equal(204, color.B); @@ -126,8 +126,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Rgba32_PackedValues() { Assert.Equal(0x80001Au, new Rgba32(+0.1f, -0.3f, +0.5f, -0.7f).PackedValue); + // Test the limits. - Assert.Equal((uint)0x0, new Rgba32(Vector4.Zero).PackedValue); + Assert.Equal(0x0U, new Rgba32(Vector4.Zero).PackedValue); Assert.Equal(0xFFFFFFFF, new Rgba32(Vector4.One).PackedValue); } @@ -204,7 +205,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromRgba32(expected); actual.FromRgba32(rgba); @@ -220,7 +221,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra32); var expected = new Bgra32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromBgra32(expected); actual.FromRgba32(rgba); @@ -236,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Argb32); var expected = new Argb32(0x1a, 0, 0x80, 0); - // act + // act rgba.FromArgb32(expected); actual.FromRgba32(rgba); diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index 9cdf4125c..34ec0bdef 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -12,15 +12,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Rgba64_PackedValues() { - Assert.Equal((ulong)0x73334CCC2666147B, new Rgba64(5243, 9830, 19660, 29491).PackedValue); + Assert.Equal(0x73334CCC2666147BUL, new Rgba64(5243, 9830, 19660, 29491).PackedValue); // Test the limits. - Assert.Equal((ulong)0x0, new Rgba64(0, 0, 0, 0).PackedValue); - Assert.Equal(0xFFFFFFFFFFFFFFFF, new Rgba64( - ushort.MaxValue, - ushort.MaxValue, - ushort.MaxValue, - ushort.MaxValue).PackedValue); + Assert.Equal(0x0UL, new Rgba64(0, 0, 0, 0).PackedValue); + Assert.Equal( + 0xFFFFFFFFFFFFFFFF, + new Rgba64(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue).PackedValue); // Test data ordering Assert.Equal(0xC7AD8F5C570A1EB8, new Rgba64(0x1EB8, 0x570A, 0x8F5C, 0xC7AD).PackedValue); @@ -36,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Theory] [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue/2, 100, 2222, 33333)] + [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] public void Rgba64_ToScaledVector4(ushort r, ushort g, ushort b, ushort a) { // arrange @@ -61,17 +59,15 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Theory] [InlineData(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue)] [InlineData(0, 0, 0, 0)] - [InlineData(ushort.MaxValue/2, 100, 2222, 33333)] + [InlineData(ushort.MaxValue / 2, 100, 2222, 33333)] public void Rgba64_FromScaledVector4(ushort r, ushort g, ushort b, ushort a) { // arrange - var source = new Rgba64(r, g, b, a); // act Vector4 scaled = source.ToScaledVector4(); - Rgba64 actual = default; actual.FromScaledVector4(scaled); @@ -105,7 +101,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } - [Fact] public void Rgba64_FromBgra5551() { @@ -219,7 +214,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } - [Fact] public void ToRgba32_Retval() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index 4ae172ed4..45f65eb4b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -13,11 +13,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats public void Short2_PackedValues() { // Test ordering - Assert.Equal((uint)0x361d2db1, new Short2(0x2db1, 0x361d).PackedValue); + Assert.Equal(0x361d2db1U, new Short2(0x2db1, 0x361d).PackedValue); Assert.Equal(4294639744, new Short2(127.5f, -5.3f).PackedValue); + // Test the limits. - Assert.Equal((uint)0x0, new Short2(Vector2.Zero).PackedValue); - Assert.Equal((uint)0x7FFF7FFF, new Short2(Vector2.One * 0x7FFF).PackedValue); + Assert.Equal(0x0U, new Short2(Vector2.Zero).PackedValue); + Assert.Equal(0x7FFF7FFFU, new Short2(Vector2.One * 0x7FFF).PackedValue); Assert.Equal(0x80008000, new Short2(Vector2.One * -0x8000).PackedValue); } @@ -34,9 +35,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void Short2_ToVector4() { - Assert.Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), (new Short2(Vector2.One * 0x7FFF)).ToVector4()); - Assert.Equal(new Vector4(0, 0, 0, 1), (new Short2(Vector2.Zero)).ToVector4()); - Assert.Equal(new Vector4(-0x8000, -0x8000, 0, 1), (new Short2(Vector2.One * -0x8000)).ToVector4()); + Assert.Equal(new Vector4(0x7FFF, 0x7FFF, 0, 1), new Short2(Vector2.One * 0x7FFF).ToVector4()); + Assert.Equal(new Vector4(0, 0, 0, 1), new Short2(Vector2.Zero).ToVector4()); + Assert.Equal(new Vector4(-0x8000, -0x8000, 0, 1), new Short2(Vector2.One * -0x8000).ToVector4()); } [Fact] @@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var short2 = new Short2(Vector2.One * 0x7FFF); const ulong expected = 0x7FFF7FFF; - // act + // act Vector4 scaled = short2.ToScaledVector4(); pixel.FromScaledVector4(scaled); uint actual = pixel.PackedValue; @@ -102,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(20, 38, 0, 255); - // act + // act short2.FromRgba32(expected); short2.ToRgba32(ref actual); @@ -147,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats { // arrange var short2 = default(Short2); - + // act short2.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 45f8e25f4..54abf0db0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -15,10 +15,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var shortValue1 = new Short4(11547, 12653, 29623, 193); var shortValue2 = new Short4(0.1f, -0.3f, 0.5f, -0.7f); - Assert.Equal((ulong)0x00c173b7316d2d1b, shortValue1.PackedValue); + Assert.Equal(0x00c173b7316d2d1bUL, shortValue1.PackedValue); Assert.Equal(18446462598732840960, shortValue2.PackedValue); - Assert.Equal((ulong)0x0, new Short4(Vector4.Zero).PackedValue); - Assert.Equal((ulong)0x7FFF7FFF7FFF7FFF, new Short4(Vector4.One * 0x7FFF).PackedValue); + Assert.Equal(0x0UL, new Short4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, new Short4(Vector4.One * 0x7FFF).PackedValue); Assert.Equal(0x8000800080008000, new Short4(Vector4.One * -0x8000).PackedValue); } @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Rgba32); var expected = new Rgba32(20, 38, 0, 255); - // act + // act short4.FromRgba32(expected); short4.ToRgba32(ref actual); @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Bgra32); var expected = new Bgra32(20, 38, 0, 255); - // act + // act short4.FromBgra32(expected); Rgba32 temp = default; short4.ToRgba32(ref temp); @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats var actual = default(Argb32); var expected = new Argb32(20, 38, 0, 255); - // act + // act short4.FromArgb32(expected); Rgba32 temp = default; short4.ToRgba32(ref temp); diff --git a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs index bd8c64742..162775a25 100644 --- a/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/UnPackedPixelTests.cs @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.Colors [Fact] public void Color_Types_From_Hex_Produce_Equal_Scaled_Component_OutPut() { - var color = Rgba32.FromHex("183060C0"); + var color = Rgba32.ParseHex("183060C0"); var colorVector = RgbaVector.FromHex("183060C0"); Assert.Equal(color.R, (byte)(colorVector.R * 255)); diff --git a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs index 80fcb4370..6b9b14bbf 100644 --- a/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/ColorMatrixTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Globalization; using SixLabors.ImageSharp.Processing; using Xunit; @@ -7,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives { public class ColorMatrixTests { - private readonly ApproximateFloatComparer ApproximateFloatComparer = new ApproximateFloatComparer(1e-6f); + private readonly ApproximateFloatComparer approximateFloatComparer = new ApproximateFloatComparer(1e-6f); [Fact] public void ColorMatrixIdentityIsCorrect() @@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives ColorMatrix val = default; val.M11 = val.M22 = val.M33 = val.M44 = 1F; - Assert.Equal(val, ColorMatrix.Identity, this.ApproximateFloatComparer); + Assert.Equal(val, ColorMatrix.Identity, this.approximateFloatComparer); } [Fact] @@ -47,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Primitives ColorMatrix value1 = this.CreateAllTwos(); ColorMatrix value2 = this.CreateAllThrees(); - ColorMatrix m; + var m = default(ColorMatrix); // First row m.M11 = (value1.M11 * value2.M11) + (value1.M12 * value2.M21) + (value1.M13 * value2.M31) + (value1.M14 * value2.M41); @@ -79,14 +82,14 @@ namespace SixLabors.ImageSharp.Tests.Primitives m.M53 = (value1.M51 * value2.M13) + (value1.M52 * value2.M23) + (value1.M53 * value2.M33) + (value1.M54 * value2.M53) + value2.M53; m.M54 = (value1.M51 * value2.M14) + (value1.M52 * value2.M24) + (value1.M53 * value2.M34) + (value1.M54 * value2.M54) + value2.M54; - Assert.Equal(m, value1 * value2, this.ApproximateFloatComparer); + Assert.Equal(m, value1 * value2, this.approximateFloatComparer); } [Fact] public void ColorMatrixMultiplyScalar() { ColorMatrix m = this.CreateAllTwos(); - Assert.Equal(this.CreateAllFours(), m * 2, this.ApproximateFloatComparer); + Assert.Equal(this.CreateAllFours(), m * 2, this.approximateFloatComparer); } [Fact] @@ -148,12 +151,14 @@ namespace SixLabors.ImageSharp.Tests.Primitives CultureInfo ci = CultureInfo.CurrentCulture; +#pragma warning disable SA1117 // Parameters should be on same line or separate lines string expected = string.Format(ci, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} {{M51:{16} M52:{17} M53:{18} M54:{19}}} }}", - m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), - m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), - m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), - m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), - m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); + m.M11.ToString(ci), m.M12.ToString(ci), m.M13.ToString(ci), m.M14.ToString(ci), + m.M21.ToString(ci), m.M22.ToString(ci), m.M23.ToString(ci), m.M24.ToString(ci), + m.M31.ToString(ci), m.M32.ToString(ci), m.M33.ToString(ci), m.M34.ToString(ci), + m.M41.ToString(ci), m.M42.ToString(ci), m.M43.ToString(ci), m.M44.ToString(ci), + m.M51.ToString(ci), m.M52.ToString(ci), m.M53.ToString(ci), m.M54.ToString(ci)); +#pragma warning restore SA1117 // Parameters should be on same line or separate lines Assert.Equal(expected, m.ToString()); } diff --git a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs index 3e37cb30b..d515b21a9 100644 --- a/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs +++ b/tests/ImageSharp.Tests/Primitives/DenseMatrixTests.cs @@ -116,5 +116,25 @@ namespace SixLabors.ImageSharp.Tests.Primitives Assert.Equal(2, transposed[1, 0]); Assert.Equal(3, transposed[2, 0]); } + + [Fact] + public void DenseMatrixEquality() + { + var dense = new DenseMatrix(3, 1); + var dense2 = new DenseMatrix(3, 1); + var dense3 = new DenseMatrix(1, 3); + + Assert.True(dense == dense2); + Assert.False(dense != dense2); + Assert.Equal(dense, dense2); + Assert.Equal(dense, (object)dense2); + Assert.Equal(dense.GetHashCode(), dense2.GetHashCode()); + + Assert.False(dense == dense3); + Assert.True(dense != dense3); + Assert.NotEqual(dense, dense3); + Assert.NotEqual(dense, (object)dense3); + Assert.NotEqual(dense.GetHashCode(), dense3.GetHashCode()); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs deleted file mode 100644 index 531ad69b8..000000000 --- a/tests/ImageSharp.Tests/Processing/Binarization/BinaryDitherTest.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) Six Labors and contributors. -// Licensed under the Apache License, Version 2.0. - -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Binarization; -using SixLabors.ImageSharp.Processing.Processors.Dithering; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Processing.Binarization -{ - public class BinaryDitherTest : BaseImageOperationsExtensionTest - { - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - - public BinaryDitherTest() - { - this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; - } - - [Fact] - public void BinaryDither_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither); - BinaryOrderedDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_rect_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, this.rect); - BinaryOrderedDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - [Fact] - public void BinaryDither_index_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink); - BinaryOrderedDitherProcessor p = this.Verify(); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.Yellow, p.UpperColor); - Assert.Equal(Color.HotPink, p.LowerColor); - } - - [Fact] - public void BinaryDither_index_rect_CorrectProcessor() - { - this.operations.BinaryDither(this.orderedDither, Color.Yellow, Color.HotPink, this.rect); - BinaryOrderedDitherProcessor p = this.Verify(this.rect); - Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(Color.HotPink, p.LowerColor); - } - - - [Fact] - public void BinaryDither_ErrorDiffuser_CorrectProcessor() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .4F); - BinaryErrorDiffusionProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.4F, p.Threshold); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_rect_CorrectProcessor() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .3F, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.3F, p.Threshold); - Assert.Equal(Color.White, p.UpperColor); - Assert.Equal(Color.Black, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_CorrectProcessorWithColors() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.Yellow); - BinaryErrorDiffusionProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - - [Fact] - public void BinaryDither_ErrorDiffuser_rect_CorrectProcessorWithColors() - { - this.operations.BinaryDiffuse(this.errorDiffuser, .5F, Color.HotPink, Color.Yellow, this.rect); - BinaryErrorDiffusionProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(Color.HotPink, p.UpperColor); - Assert.Equal(Color.Yellow, p.LowerColor); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs index 680db5a15..5d550a595 100644 --- a/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Binarization/OrderedDitherFactoryTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Dithering; @@ -9,6 +9,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { public class OrderedDitherFactoryTests { +#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row + private static readonly DenseMatrix Expected2x2Matrix = new DenseMatrix( new uint[2, 2] { @@ -46,6 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization { 63, 31, 55, 23, 61, 29, 53, 21 } }); +#pragma warning restore SA1025 // Code should not contain multiple whitespace in a row [Fact] public void OrderedDitherFactoryCreatesCorrect2x2Matrix() @@ -99,4 +102,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs index 6a864d83a..e0b1e1bbb 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; @@ -13,7 +13,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution { public class DetectEdgesTest : BaseImageOperationsExtensionTest { - [Fact] public void DetectEdges_SobelProcessorDefaultsSet() { @@ -33,17 +32,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution SobelProcessor processor = this.Verify(this.rect); Assert.True(processor.Grayscale); } - public static IEnumerable EdgeDetectionTheoryData => new[] { - new object[]{ new TestType(), EdgeDetectionOperators.Kayyali }, - new object[]{ new TestType(), EdgeDetectionOperators.Kirsch }, - new object[]{ new TestType(), EdgeDetectionOperators.Laplacian3x3 }, - new object[]{ new TestType(), EdgeDetectionOperators.Laplacian5x5 }, - new object[]{ new TestType(), EdgeDetectionOperators.LaplacianOfGaussian }, - new object[]{ new TestType(), EdgeDetectionOperators.Prewitt }, - new object[]{ new TestType(), EdgeDetectionOperators.RobertsCross }, - new object[]{ new TestType(), EdgeDetectionOperators.Robinson }, - new object[]{ new TestType(), EdgeDetectionOperators.Scharr }, - new object[]{ new TestType(), EdgeDetectionOperators.Sobel }, + + public static IEnumerable EdgeDetectionTheoryData => new[] + { + new object[] { new TestType(), EdgeDetectionOperators.Kayyali }, + new object[] { new TestType(), EdgeDetectionOperators.Kirsch }, + new object[] { new TestType(), EdgeDetectionOperators.Laplacian3x3 }, + new object[] { new TestType(), EdgeDetectionOperators.Laplacian5x5 }, + new object[] { new TestType(), EdgeDetectionOperators.LaplacianOfGaussian }, + new object[] { new TestType(), EdgeDetectionOperators.Prewitt }, + new object[] { new TestType(), EdgeDetectionOperators.RobertsCross }, + new object[] { new TestType(), EdgeDetectionOperators.Robinson }, + new object[] { new TestType(), EdgeDetectionOperators.Scharr }, + new object[] { new TestType(), EdgeDetectionOperators.Sobel }, }; [Theory] @@ -71,4 +72,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Convolution Assert.Equal(grey, processor.Grayscale); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs index 7dc0ff567..ff20e3b9d 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/Processors/LaplacianKernelFactoryTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -22,11 +22,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution private static readonly DenseMatrix Expected5x5Matrix = new DenseMatrix( new float[,] { - { -1, -1, -1,-1, -1 }, - { -1, -1, -1,-1, -1 }, - { -1, -1, 24,-1, -1 }, - { -1, -1, -1,-1, -1 }, - { -1, -1, -1,-1, -1 } + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, 24, -1, -1 }, + { -1, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }); [Fact] @@ -64,4 +64,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs index 53a50468b..0cc8db651 100644 --- a/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs +++ b/tests/ImageSharp.Tests/Processing/Dithering/DitherTest.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; - using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; - using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Binarization @@ -19,10 +17,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization True(a.SequenceEqual(b)); } } - - private readonly IOrderedDither orderedDither; - private readonly IErrorDiffuser errorDiffuser; - private readonly Color[] TestPalette = + + private readonly IDither orderedDither; + private readonly IDither errorDiffuser; + private readonly Color[] testPalette = { Color.Red, Color.Green, @@ -31,15 +29,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public DitherTest() { - this.orderedDither = KnownDitherers.BayerDither4x4; - this.errorDiffuser = KnownDiffusers.FloydSteinberg; + this.orderedDither = KnownDitherings.Bayer4x4; + this.errorDiffuser = KnownDitherings.FloydSteinberg; } [Fact] public void Dither_CorrectProcessor() { this.operations.Dither(this.orderedDither); - OrderedDitherPaletteProcessor p = this.Verify(); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } @@ -48,67 +46,129 @@ namespace SixLabors.ImageSharp.Tests.Processing.Binarization public void Dither_rect_CorrectProcessor() { this.operations.Dither(this.orderedDither, this.rect); - OrderedDitherPaletteProcessor p = this.Verify(this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } + [Fact] public void Dither_index_CorrectProcessor() { - this.operations.Dither(this.orderedDither, this.TestPalette); - OrderedDitherPaletteProcessor p = this.Verify(); + this.operations.Dither(this.orderedDither, this.testPalette); + PaletteDitherProcessor p = this.Verify(); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.TestPalette, p.Palette); + Assert.Equal(this.testPalette, p.Palette); } [Fact] public void Dither_index_rect_CorrectProcessor() { - this.operations.Dither(this.orderedDither, this.TestPalette, this.rect); - OrderedDitherPaletteProcessor p = this.Verify(this.rect); + this.operations.Dither(this.orderedDither, this.testPalette, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); Assert.Equal(this.orderedDither, p.Dither); - Assert.Equal(this.TestPalette, p.Palette); + Assert.Equal(this.testPalette, p.Palette); } - [Fact] public void Dither_ErrorDiffuser_CorrectProcessor() { - this.operations.Diffuse(this.errorDiffuser, .4F); - ErrorDiffusionPaletteProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.4F, p.Threshold); + this.operations.Dither(this.errorDiffuser); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_rect_CorrectProcessor() { - this.operations.Diffuse(this.errorDiffuser, .3F, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.3F, p.Threshold); + this.operations.Dither(this.errorDiffuser, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); + Assert.Equal(this.errorDiffuser, p.Dither); Assert.Equal(Color.WebSafePalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_CorrectProcessorWithColors() { - this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette); - ErrorDiffusionPaletteProcessor p = this.Verify(); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(this.TestPalette, p.Palette); + this.operations.Dither(this.errorDiffuser, this.testPalette); + PaletteDitherProcessor p = this.Verify(); + Assert.Equal(this.errorDiffuser, p.Dither); + Assert.Equal(this.testPalette, p.Palette); } [Fact] public void Dither_ErrorDiffuser_rect_CorrectProcessorWithColors() { - this.operations.Diffuse(this.errorDiffuser, .5F, this.TestPalette, this.rect); - ErrorDiffusionPaletteProcessor p = this.Verify(this.rect); - Assert.Equal(this.errorDiffuser, p.Diffuser); - Assert.Equal(.5F, p.Threshold); - Assert.Equal(this.TestPalette, p.Palette); + this.operations.Dither(this.errorDiffuser, this.testPalette, this.rect); + PaletteDitherProcessor p = this.Verify(this.rect); + Assert.Equal(this.errorDiffuser, p.Dither); + Assert.Equal(this.testPalette, p.Palette); + } + + [Fact] + public void ErrorDitherEquality() + { + IDither dither = KnownDitherings.FloydSteinberg; + ErrorDither dither2 = ErrorDither.FloydSteinberg; + ErrorDither dither3 = ErrorDither.FloydSteinberg; + + Assert.True(dither == dither2); + Assert.True(dither2 == dither); + Assert.False(dither != dither2); + Assert.False(dither2 != dither); + Assert.Equal(dither, dither2); + Assert.Equal(dither, (object)dither2); + Assert.Equal(dither.GetHashCode(), dither2.GetHashCode()); + + dither = null; + Assert.False(dither == dither2); + Assert.False(dither2 == dither); + Assert.True(dither != dither2); + Assert.True(dither2 != dither); + Assert.NotEqual(dither, dither2); + Assert.NotEqual(dither, (object)dither2); + Assert.NotEqual(dither?.GetHashCode(), dither2.GetHashCode()); + + Assert.True(dither2 == dither3); + Assert.True(dither3 == dither2); + Assert.False(dither2 != dither3); + Assert.False(dither3 != dither2); + Assert.Equal(dither2, dither3); + Assert.Equal(dither2, (object)dither3); + Assert.Equal(dither2.GetHashCode(), dither3.GetHashCode()); + } + + [Fact] + public void OrderedDitherEquality() + { + IDither dither = KnownDitherings.Bayer2x2; + OrderedDither dither2 = OrderedDither.Bayer2x2; + OrderedDither dither3 = OrderedDither.Bayer2x2; + + Assert.True(dither == dither2); + Assert.True(dither2 == dither); + Assert.False(dither != dither2); + Assert.False(dither2 != dither); + Assert.Equal(dither, dither2); + Assert.Equal(dither, (object)dither2); + Assert.Equal(dither.GetHashCode(), dither2.GetHashCode()); + + dither = null; + Assert.False(dither == dither2); + Assert.False(dither2 == dither); + Assert.True(dither != dither2); + Assert.True(dither2 != dither); + Assert.NotEqual(dither, dither2); + Assert.NotEqual(dither, (object)dither2); + Assert.NotEqual(dither?.GetHashCode(), dither2.GetHashCode()); + + Assert.True(dither2 == dither3); + Assert.True(dither3 == dither2); + Assert.False(dither2 != dither3); + Assert.False(dither3 != dither2); + Assert.Equal(dither2, dither3); + Assert.Equal(dither2, (object)dither3); + Assert.Equal(dither2.GetHashCode(), dither3.GetHashCode()); } } } diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs index a137a9f43..37bd2e87a 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects { public class BackgroundColorTest : BaseImageOperationsExtensionTest { - private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); [Fact] public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet() @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects this.operations.BackgroundColor(Color.BlanchedAlmond); BackgroundColorProcessor processor = this.Verify(); - Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.BlanchedAlmond, processor.Color); } @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect); BackgroundColorProcessor processor = this.Verify(this.rect); - Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.BlanchedAlmond, processor.Color); } @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects this.operations.BackgroundColor(this.options, Color.BlanchedAlmond); BackgroundColorProcessor processor = this.Verify(); - Assert.Equal(this.options, processor.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(this.options, processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.BlanchedAlmond, processor.Color); } @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect); BackgroundColorProcessor processor = this.Verify(this.rect); - Assert.Equal(this.options, processor.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(this.options, processor.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.BlanchedAlmond, processor.Color); } } diff --git a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs index 18842e1f3..797423394 100644 --- a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -28,6 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(10, processor.Levels); Assert.Equal(15, processor.BrushSize); } + [Fact] public void OilPaint_Levels_Brush_OilPaintingProcessorDefaultsSet() { @@ -48,4 +49,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects Assert.Equal(43, processor.BrushSize); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs index bd7c0245b..3f11b4631 100644 --- a/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs +++ b/tests/ImageSharp.Tests/Processing/FakeImageOperationsProvider.cs @@ -11,36 +11,38 @@ namespace SixLabors.ImageSharp.Tests.Processing { internal class FakeImageOperationsProvider : IImageProcessingContextFactory { - private List ImageOperators = new List(); + private readonly List imageOperators = new List(); public bool HasCreated(Image source) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return this.Created(source).Any(); } - public IEnumerable> Created(Image source) where TPixel : struct, IPixel + + public IEnumerable> Created(Image source) + where TPixel : unmanaged, IPixel { - return this.ImageOperators.OfType>() + return this.imageOperators.OfType>() .Where(x => x.Source == source); } public IEnumerable.AppliedOperation> AppliedOperations(Image source) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return this.Created(source) .SelectMany(x => x.Applied); } public IInternalImageProcessingContext CreateImageProcessingContext(Configuration configuration, Image source, bool mutate) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var op = new FakeImageOperations(configuration, source, mutate); - this.ImageOperators.Add(op); + this.imageOperators.Add(op); return op; } public class FakeImageOperations : IInternalImageProcessingContext - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public FakeImageOperations(Configuration configuration, Image source, bool mutate) { @@ -86,6 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Processing public struct AppliedOperation { public Rectangle? Rectangle { get; set; } + public IImageProcessor GenericProcessor { get; set; } public IImageProcessor NonGenericProcessor { get; set; } diff --git a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs index e287fb7b5..70c78ec81 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/ColorBlindnessTest.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -14,15 +14,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters { public class ColorBlindnessTest : BaseImageOperationsExtensionTest { - public static IEnumerable TheoryData = new[] { - new object[]{ new TestType(), ColorBlindnessMode.Achromatomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Achromatopsia }, - new object[]{ new TestType(), ColorBlindnessMode.Deuteranomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Deuteranopia }, - new object[]{ new TestType(), ColorBlindnessMode.Protanomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Protanopia }, - new object[]{ new TestType(), ColorBlindnessMode.Tritanomaly }, - new object[]{ new TestType(), ColorBlindnessMode.Tritanopia } + public static IEnumerable TheoryData = new[] + { + new object[] { new TestType(), ColorBlindnessMode.Achromatomaly }, + new object[] { new TestType(), ColorBlindnessMode.Achromatopsia }, + new object[] { new TestType(), ColorBlindnessMode.Deuteranomaly }, + new object[] { new TestType(), ColorBlindnessMode.Deuteranopia }, + new object[] { new TestType(), ColorBlindnessMode.Protanomaly }, + new object[] { new TestType(), ColorBlindnessMode.Protanopia }, + new object[] { new TestType(), ColorBlindnessMode.Tritanomaly }, + new object[] { new TestType(), ColorBlindnessMode.Tritanopia } }; [Theory] @@ -33,6 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters this.operations.ColorBlindness(colorBlindness); this.Verify(); } + [Theory] [MemberData(nameof(TheoryData))] public void ColorBlindness_rect_CorrectProcessor(TestType testType, ColorBlindnessMode colorBlindness) diff --git a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs index 08de55d6b..9afaf16fd 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/GrayscaleTest.cs @@ -4,8 +4,8 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Processing.Processors.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; @@ -14,8 +14,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Filters { public class GrayscaleTest : BaseImageOperationsExtensionTest { - public static IEnumerable ModeTheoryData = new[] { - new object[]{ new TestType(), GrayscaleMode.Bt709 } + public static IEnumerable ModeTheoryData = new[] + { + new object[] { new TestType(), GrayscaleMode.Bt709 } }; [Theory] diff --git a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs index 86a0d1485..063844178 100644 --- a/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs +++ b/tests/ImageSharp.Tests/Processing/ImageOperationTests.cs @@ -32,13 +32,15 @@ namespace SixLabors.ImageSharp.Tests.Processing var processorMock = new Mock(); this.processorDefinition = processorMock.Object; - this.image = new Image(new Configuration - { - ImageOperationsProvider = this.provider - }, 1, 1); + this.image = new Image( + new Configuration + { + ImageOperationsProvider = this.provider + }, + 1, + 1); } - [Fact] public void MutateCallsImageOperationsProvider_Func_OriginalImage() { diff --git a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs index 32da38621..5e2b7062e 100644 --- a/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs +++ b/tests/ImageSharp.Tests/Processing/Normalization/HistogramEqualizationTests.cs @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization [Theory] [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] public void Adaptive_SlidingWindow_15Tiles_WithClipping(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization [Theory] [WithFile(TestImages.Jpeg.Baseline.LowContrast, PixelTypes.Rgba32)] public void Adaptive_TileInterpolation_10Tiles_WithClipping(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -120,11 +120,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Normalization /// where it could happen that one too much start position was calculated in some cases. /// See: https://github.com/SixLabors/ImageSharp/pull/984 /// + /// The pixel type of the image. [Theory] [WithTestPatternImages(110, 110, PixelTypes.Rgb24)] [WithTestPatternImages(170, 170, PixelTypes.Rgb24)] public void Issue984(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs index f7d6eba97..ea000ae2a 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { public class GlowTest : BaseImageOperationsExtensionTest { - private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); [Fact] public void Glow_GlowProcessorWithDefaultValues() @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Glow(); GlowProcessor p = this.Verify(); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -27,10 +27,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays [Fact] public void Glow_Color_GlowProcessorWithDefaultValues() { - this.operations.Glow(Rgba32.Aquamarine); + this.operations.Glow(Color.Aquamarine); GlowProcessor p = this.Verify(); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Aquamarine, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Glow(3.5f); GlowProcessor p = this.Verify(); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.Absolute(3.5f), p.Radius); } @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Glow(rect); GlowProcessor p = this.Verify(rect); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Black, p.GlowColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius); } diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs index da11edf73..8e5eb7207 100644 --- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays { public class VignetteTest : BaseImageOperationsExtensionTest { - private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer(); + private static readonly GraphicsOptionsComparer GraphicsOptionsComparer = new GraphicsOptionsComparer(); [Fact] public void Vignette_VignetteProcessorWithDefaultValues() @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Vignette(); VignetteProcessor p = this.Verify(); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Vignette(Color.Aquamarine); VignetteProcessor p = this.Verify(); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Aquamarine, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Vignette(3.5f, 12123f); VignetteProcessor p = this.Verify(); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX); Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY); @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays this.operations.Vignette(rect); VignetteProcessor p = this.Verify(rect); - Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer); + Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, GraphicsOptionsComparer); Assert.Equal(Color.Black, p.VignetteColor); Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX); Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs index 13be1dd2d..e718df4a2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryDitherTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,8 +7,8 @@ using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { public class BinaryDitherTests @@ -18,38 +18,38 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public static readonly TheoryData OrderedDitherers = new TheoryData + public static readonly TheoryData OrderedDitherers = new TheoryData { - { "Bayer8x8", KnownDitherers.BayerDither8x8 }, - { "Bayer4x4", KnownDitherers.BayerDither4x4 }, - { "Ordered3x3", KnownDitherers.OrderedDither3x3 }, - { "Bayer2x2", KnownDitherers.BayerDither2x2 } + { "Bayer8x8", KnownDitherings.Bayer8x8 }, + { "Bayer4x4", KnownDitherings.Bayer4x4 }, + { "Ordered3x3", KnownDitherings.Ordered3x3 }, + { "Bayer2x2", KnownDitherings.Bayer2x2 } }; - public static readonly TheoryData ErrorDiffusers = new TheoryData + public static readonly TheoryData ErrorDiffusers = new TheoryData { - { "Atkinson", KnownDiffusers.Atkinson }, - { "Burks", KnownDiffusers.Burks }, - { "FloydSteinberg", KnownDiffusers.FloydSteinberg }, - { "JarvisJudiceNinke", KnownDiffusers.JarvisJudiceNinke }, - { "Sierra2", KnownDiffusers.Sierra2 }, - { "Sierra3", KnownDiffusers.Sierra3 }, - { "SierraLite", KnownDiffusers.SierraLite }, - { "StevensonArce", KnownDiffusers.StevensonArce }, - { "Stucki", KnownDiffusers.Stucki }, + { "Atkinson", KnownDitherings.Atkinson }, + { "Burks", KnownDitherings.Burks }, + { "FloydSteinberg", KnownDitherings.FloydSteinberg }, + { "JarvisJudiceNinke", KnownDitherings.JarvisJudiceNinke }, + { "Sierra2", KnownDitherings.Sierra2 }, + { "Sierra3", KnownDitherings.Sierra3 }, + { "SierraLite", KnownDitherings.SierraLite }, + { "StevensonArce", KnownDitherings.StevensonArce }, + { "Stucki", KnownDitherings.Stucki }, }; public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; - private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; - private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(OrderedDitherers), 100, 100, PixelTypes.Rgba32)] - public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IOrderedDither ditherer) - where TPixel : struct, IPixel + public void BinaryDitherFilter_WorksWithAllDitherers(TestImageProvider provider, string name, IDither ditherer) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -61,12 +61,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(ErrorDiffusers), 100, 100, PixelTypes.Rgba32)] - public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IErrorDiffuser diffuser) - where TPixel : struct, IPixel + public void DiffusionFilter_WorksWithAllErrorDiffusers(TestImageProvider provider, string name, IDither diffuser) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(diffuser, .5F)); + image.Mutate(x => x.BinaryDither(diffuser)); image.DebugSave(provider, name); } } @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void BinaryDitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -86,11 +86,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Bike, TestPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, 0.5f)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser)); image.DebugSave(provider); } } @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (Image image = source.Clone()) @@ -115,18 +115,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image source = provider.GetImage()) using (Image image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); - image.Mutate(x => x.BinaryDiffuse(DefaultErrorDiffuser, .5F, bounds)); + image.Mutate(x => x.BinaryDither(DefaultErrorDiffuser, bounds)); image.DebugSave(provider); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs index 4fe501237..3801a4888 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Binarization/BinaryThresholdTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -17,18 +17,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization .25F, .75F }; - + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - + public const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24; [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -40,19 +40,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(BinaryThresholdValues), PixelTypes.Rgba32)] public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (Image source = provider.GetImage()) using (var image = source.Clone()) { var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); image.Mutate(x => x.BinaryThreshold(value, bounds)); - image.DebugSave(provider, value); + image.DebugSave(provider, value); ImageComparer.Tolerant().VerifySimilarityIgnoreRegion(source, image, bounds); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs index 05595eece..bd574c916 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -1,5 +1,5 @@ -// // Copyright (c) Six Labors and contributors. -// // Licensed under the Apache License, Version 2.0. +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public abstract class Basic1ParameterConvolutionTests { private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); - + public static readonly TheoryData Values = new TheoryData { 3, 5 }; - + public static readonly string[] InputImages = { TestImages.Bmp.Car, @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] public void OnFullImage(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunValidatingProcessorTest( @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunRectangleConstrainedValidatingProcessorTest( @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } protected abstract void Apply(IImageProcessingContext ctx, int value); - + protected abstract void Apply(IImageProcessingContext ctx, int value, Rectangle bounds); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index 34b016513..9dc135016 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -19,11 +19,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { public class BokehBlurTest { - static BokehBlurTest() - { - TestEnvironment.PrepareRemoteExecutor(); - } - private static readonly string Components10x2 = @" [[ 0.00451261+0.0165137j 0.02161237-0.00299122j 0.00387479-0.02682816j -0.02752798-0.01788438j -0.03553877+0.0154543j -0.01428268+0.04224722j @@ -128,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [WithTestPatternImages(nameof(BokehBlurValues), 23, 31, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BokehBlurValues), 30, 20, PixelTypes.Rgba32)] public void BokehBlurFilterProcessor(TestImageProvider provider, BokehBlurInfo value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { static void RunTest(string providerDump, string infoDump) { @@ -140,7 +135,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution x => x.BokehBlur(value.Radius, value.Components, value.Gamma), testOutputDetails: value.ToString(), appendPixelTypeToFileName: false); - } RemoteExecutor @@ -149,11 +143,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution } [Theory] - // TODO: Re-enable L8 when we update the reference images. - // [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.L8)] + /* + TODO: Re-enable L8 when we update the reference images. + [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32 | PixelTypes.L8)] + */ [WithTestPatternImages(200, 200, PixelTypes.Bgr24 | PixelTypes.Bgra32)] public void BokehBlurFilterProcessor_WorksWithAllPixelTypes(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { static void RunTest(string providerDump) { @@ -169,11 +165,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution .Dispose(); } - [Theory] [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { static void RunTest(string providerDump, string infoDump) { @@ -196,5 +191,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution .Invoke(RunTest, BasicSerializer.Serialize(provider), BasicSerializer.Serialize(value)) .Dispose(); } + + [Theory] + [WithTestPatternImages(100, 300, PixelTypes.Bgr24)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(41, c => c.BokehBlur()); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs index 0e8013a64..3d1e378b1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs @@ -1,23 +1,23 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution { [GroupOutput("Convolution")] public class DetectEdgesTest { - // I think our comparison is not accurate enough (nor can be) for RgbaVector. - // The image pixels are identical according to BeyondCompare. - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F); + private static readonly ImageComparer OpaqueComparer = ImageComparer.TolerantPercentage(0.01F); + + private static readonly ImageComparer TransparentComparer = ImageComparer.TolerantPercentage(0.5F); public static readonly string[] TestImages = { Tests.TestImages.Png.Bike }; - + public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public static readonly TheoryData DetectEdgesFilters = new TheoryData @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_WorksOnWrappedMemoryImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTestOnWrappedMemoryImage( ctx => @@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution var bounds = new Rectangle(10, 10, size.Width / 2, size.Height / 2); ctx.DetectEdges(bounds); }, - comparer: ValidatorComparer, + comparer: OpaqueComparer, useReferenceOutputFrom: nameof(this.DetectEdges_InBox)); } @@ -54,33 +54,42 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [WithTestPatternImages(nameof(DetectEdgesFilters), 100, 100, PixelTypes.Rgba32)] [WithFileCollection(nameof(TestImages), nameof(DetectEdgesFilters), PixelTypes.Rgba32)] public void DetectEdges_WorksWithAllFilters(TestImageProvider provider, EdgeDetectionOperators detector) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + bool hasAlpha = provider.SourceFileOrDescription.Contains("TestPattern"); + ImageComparer comparer = hasAlpha ? TransparentComparer : OpaqueComparer; using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges(detector)); image.DebugSave(provider, detector.ToString()); - image.CompareToReferenceOutput(ValidatorComparer, provider, detector.ToString()); + image.CompareToReferenceOutput(comparer, provider, detector.ToString()); } } [Theory] [WithFileCollection(nameof(TestImages), CommonNonDefaultPixelTypes)] public void DetectEdges_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { + // James: + // I think our comparison is not accurate enough (nor can be) for RgbaVector. + // The image pixels are identical according to BeyondCompare. + ImageComparer comparer = typeof(TPixel) == typeof(RgbaVector) ? + ImageComparer.TolerantPercentage(1f) : + OpaqueComparer; + using (Image image = provider.GetImage()) { image.Mutate(x => x.DetectEdges()); image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); + image.CompareToReferenceOutput(comparer, provider); } } [Theory] [WithFile(Tests.TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void DetectEdges_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -92,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution [Theory] [WithFileCollection(nameof(TestImages), PixelTypes.Rgba32)] public void DetectEdges_InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -100,8 +109,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution image.Mutate(x => x.DetectEdges(bounds)); image.DebugSave(provider); - image.CompareToReferenceOutput(ValidatorComparer, provider); + image.CompareToReferenceOutput(OpaqueComparer, provider); } } + + [Theory] + [WithFile(Tests.TestImages.Png.Bike, nameof(DetectEdgesFilters), PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers(TestImageProvider provider, EdgeDetectionOperators detector) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.DetectEdges(detector), + detector); + } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 5d65a9e61..2ae926392 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -14,34 +14,37 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24 | PixelTypes.RgbaVector; - + public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial, TestImages.Png.Bike }; - public static readonly TheoryData ErrorDiffusers = new TheoryData - { - KnownDiffusers.Atkinson, - KnownDiffusers.Burks, - KnownDiffusers.FloydSteinberg, - KnownDiffusers.JarvisJudiceNinke, - KnownDiffusers.Sierra2, - KnownDiffusers.Sierra3, - KnownDiffusers.SierraLite, - KnownDiffusers.StevensonArce, - KnownDiffusers.Stucki, - }; - - public static readonly TheoryData OrderedDitherers = new TheoryData - { - KnownDitherers.BayerDither8x8, - KnownDitherers.BayerDither4x4, - KnownDitherers.OrderedDither3x3, - KnownDitherers.BayerDither2x2 - }; + public static readonly TheoryData ErrorDiffusers + = new TheoryData + { + { KnownDitherings.Atkinson, nameof(KnownDitherings.Atkinson) }, + { KnownDitherings.Burks, nameof(KnownDitherings.Burks) }, + { KnownDitherings.FloydSteinberg, nameof(KnownDitherings.FloydSteinberg) }, + { KnownDitherings.JarvisJudiceNinke, nameof(KnownDitherings.JarvisJudiceNinke) }, + { KnownDitherings.Sierra2, nameof(KnownDitherings.Sierra2) }, + { KnownDitherings.Sierra3, nameof(KnownDitherings.Sierra3) }, + { KnownDitherings.SierraLite, nameof(KnownDitherings.SierraLite) }, + { KnownDitherings.StevensonArce, nameof(KnownDitherings.StevensonArce) }, + { KnownDitherings.Stucki, nameof(KnownDitherings.Stucki) }, + }; + + public static readonly TheoryData OrderedDitherers + = new TheoryData + { + { KnownDitherings.Bayer2x2, nameof(KnownDitherings.Bayer2x2) }, + { KnownDitherings.Bayer4x4, nameof(KnownDitherings.Bayer4x4) }, + { KnownDitherings.Bayer8x8, nameof(KnownDitherings.Bayer8x8) }, + { KnownDitherings.Ordered3x3, nameof(KnownDitherings.Ordered3x3) } + }; + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); - - private static IOrderedDither DefaultDitherer => KnownDitherers.BayerDither4x4; - private static IErrorDiffuser DefaultErrorDiffuser => KnownDiffusers.Atkinson; + private static IDither DefaultDitherer => KnownDitherings.Bayer4x4; + + private static IDither DefaultErrorDiffuser => KnownDitherings.Atkinson; /// /// The output is visually correct old 32bit runtime, @@ -53,28 +56,28 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDiffusionFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunRectangleConstrainedValidatingProcessorTest( - (x, rect) => x.Diffuse(DefaultErrorDiffuser, .5F, rect), + (x, rect) => x.Dither(DefaultErrorDiffuser, rect), comparer: ValidatorComparer); } [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void ApplyDitherFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.Dither(DefaultDitherer, rect), comparer: ValidatorComparer); @@ -83,33 +86,34 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] public void DiffusionFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + // Increased tolerance because of compatibility issues on .NET 4.6.2: var comparer = ImageComparer.TolerantPercentage(1f); - provider.RunValidatingProcessorTest(x => x.Diffuse(DefaultErrorDiffuser, 0.5f), comparer: comparer); + provider.RunValidatingProcessorTest(x => x.Dither(DefaultErrorDiffuser), comparer: comparer); } [Theory] [WithFileCollection(nameof(CommonTestImages), nameof(ErrorDiffusers), PixelTypes.Rgba32)] public void DiffusionFilter_WorksWithAllErrorDiffusers( TestImageProvider provider, - IErrorDiffuser diffuser) - where TPixel : struct, IPixel + IDither diffuser, + string name) + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunValidatingProcessorTest( - x => x.Diffuse(diffuser, 0.5f), - testOutputDetails: diffuser.GetType().Name, + x => x.Dither(diffuser), + testOutputDetails: name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); } @@ -117,13 +121,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [Theory] [WithFile(TestImages.Png.Filter0, CommonNonDefaultPixelTypes)] public void DitherFilter_ShouldNotDependOnSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunValidatingProcessorTest( x => x.Dither(DefaultDitherer), comparer: ValidatorComparer); @@ -133,19 +137,41 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Binarization [WithFileCollection(nameof(CommonTestImages), nameof(OrderedDitherers), PixelTypes.Rgba32)] public void DitherFilter_WorksWithAllDitherers( TestImageProvider provider, - IOrderedDither ditherer) - where TPixel : struct, IPixel + IDither ditherer, + string name) + where TPixel : unmanaged, IPixel { if (SkipAllDitherTests) { return; } - + provider.RunValidatingProcessorTest( x => x.Dither(ditherer), - testOutputDetails: ditherer.GetType().Name, + testOutputDetails: name, comparer: ValidatorComparer, appendPixelTypeToFileName: false); } + + [Theory] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(OrderedDither.Ordered3x3))] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32, nameof(ErrorDither.FloydSteinberg))] + public void CommonDitherers_WorkWithDiscoBuffers( + TestImageProvider provider, + string name) + where TPixel : unmanaged, IPixel + { + IDither dither = TestUtils.GetDither(name); + if (SkipAllDitherTests) + { + return; + } + + provider.RunBufferCapacityLimitProcessorTest( + 41, + c => c.Dither(dither), + name, + ImageComparer.TolerantPercentage(0.001f)); + } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs index 56ffceb47..88ebec4e2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/BackgroundColorTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,22 +16,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects TestImages.Png.Splash, TestImages.Png.Ducky }; - + [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); + provider.RunValidatingProcessorTest(x => x.BackgroundColor(Color.HotPink)); } [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.BackgroundColor(Color.HotPink, rect)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs index aad48e357..4eeebc3a0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/OilPaintTest.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; - +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects @@ -13,9 +13,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects { public static readonly TheoryData OilPaintValues = new TheoryData { - { 15, 10 }, + { 15, 10 }, { 6, 5 } }; + public static readonly string[] InputImages = { TestImages.Png.CalliphoraPartial, @@ -25,11 +26,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider, int levels, int brushSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( - x => x.OilPaint(levels, brushSize), - $"{levels}-{brushSize}", + x => + { + x.OilPaint(levels, brushSize); + return $"{levels}-{brushSize}"; + }, + ImageComparer.TolerantPercentage(0.01F), appendPixelTypeToFileName: false); } @@ -37,11 +42,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [WithFileCollection(nameof(InputImages), nameof(OilPaintValues), PixelTypes.Rgba32)] [WithTestPatternImages(nameof(OilPaintValues), 100, 100, PixelTypes.Rgba32)] public void InBox(TestImageProvider provider, int levels, int brushSize) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.OilPaint(levels, brushSize, rect), - $"{levels}-{brushSize}"); + $"{levels}-{brushSize}", + ImageComparer.TolerantPercentage(0.01F)); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs index 00a45a94e..dd4abfc76 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( x => x.ProcessPixelRowsAsVector4( @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest( (x, rect) => x.ProcessPixelRowsAsVector4( @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void PositionAwareFullImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( c => c.ProcessPixelRowsAsVector4( @@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void PositionAwareInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest( (c, rect) => c.ProcessPixelRowsAsVector4( diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs index e95452ffb..d7cee311d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelateTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithFile(TestImages.Png.Ducky, nameof(PixelateValues), PixelTypes.Rgba32)] public void FullImage(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Pixelate(value), value, appendPixelTypeToFileName: false); } @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] [WithFile(TestImages.Png.CalliphoraPartial, nameof(PixelateValues), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunRectangleConstrainedValidatingProcessorTest((x, rect) => x.Pixelate(value, rect), value); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs index 64aeae053..86518b015 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BlackWhiteTest.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyBlackWhiteFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.BlackWhite(), comparer: ImageComparer.TolerantPercentage(0.002f)); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs index 54a8dd4b7..bb52731bb 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/BrightnessTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.5F + 1.5F }; [Theory] [WithTestPatternImages(nameof(BrightnessValues), 48, 48, PixelTypes.Rgba32)] public void ApplyBrightnessFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Brightness(value), value, this.imageComparer); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs index 8ac56655e..5c6a29822 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ColorBlindnessTest.cs @@ -31,6 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(nameof(ColorBlindnessFilters), 48, 48, PixelTypes.Rgba32)] public void ApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindnessMode colorBlindness) - where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(x => x.ColorBlindness(colorBlindness), colorBlindness.ToString(), this.imageComparer); } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs index e5e4fa4a9..1758db8dd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/ContrastTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.5F + 1.5F }; [Theory] [WithTestPatternImages(nameof(ContrastValues), 48, 48, PixelTypes.Rgba32)] public void ApplyContrastFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Contrast(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs index eca3da58a..ae9abed7f 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void ApplyFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ColorMatrix m = CreateCombinedTestFilterMatrix(); @@ -30,13 +30,23 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyFilterInBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { ColorMatrix m = CreateCombinedTestFilterMatrix(); provider.RunRectangleConstrainedValidatingProcessorTest((x, b) => x.Filter(m, b), comparer: ValidatorComparer); } + [Theory] + [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] + public void FilterProcessor_WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + ColorMatrix m = CreateCombinedTestFilterMatrix(); + + provider.RunBufferCapacityLimitProcessorTest(37, c => c.Filter(m)); + } + private static ColorMatrix CreateCombinedTestFilterMatrix() { ColorMatrix brightness = KnownFilterMatrices.CreateBrightnessFilter(0.9F); @@ -44,6 +54,5 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters ColorMatrix saturation = KnownFilterMatrices.CreateSaturateFilter(1.5F); return brightness * hue * saturation; } - } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs index c2728e043..2352a4dce 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/GrayscaleTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -22,12 +22,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters /// /// Use test patterns over loaded images to save decode time. /// + /// The pixel type of the image. [Theory] [WithTestPatternImages(nameof(GrayscaleModeTypes), 48, 48, PixelTypes.Rgba32)] public void ApplyGrayscaleFilter(TestImageProvider provider, GrayscaleMode value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Grayscale(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs index 4ce700bad..9b96653b8 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/HueTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters = new TheoryData { 180, - -180 + -180 }; [Theory] [WithTestPatternImages(nameof(HueValues), 48, 48, PixelTypes.Rgba32)] public void ApplyHueFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Hue(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs index 1b4c70646..cb7a403f9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/InvertTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyInvertFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Invert()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs index b7b635c2d..04e86c955 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/KodachromeTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyKodachromeFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Kodachrome()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs index c330ed6d9..8b77f902d 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LightnessTest.cs @@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects = new TheoryData { .5F, - 1.5F + 1.5F }; [Theory] [WithTestPatternImages(nameof(LightnessValues), 48, 48, PixelTypes.Rgba32)] public void ApplyLightnessFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Lightness(value), value, this.imageComparer); + where TPixel : unmanaged, IPixel => provider.RunValidatingProcessorTest(ctx => ctx.Lightness(value), value, this.imageComparer); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs index 013ec3874..65e616dca 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/LomographTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyLomographFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Lomograph()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs index 35e405f4c..bd7336119 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/OpacityTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -15,16 +15,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects public static readonly TheoryData AlphaValues = new TheoryData { - 20/100F, - 80/100F + 20 / 100F, + 80 / 100F }; [Theory] [WithTestPatternImages(nameof(AlphaValues), 48, 48, PixelTypes.Rgba32)] public void ApplyAlphaFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Opacity(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs index 3b39542a5..26dac7532 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/PolaroidTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplyPolaroidFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Polaroid()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs index 31fab8b65..4be11a72c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SaturateTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -16,15 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters = new TheoryData { .5F, - 1.5F, + 1.5F, }; [Theory] [WithTestPatternImages(nameof(SaturationValues), 48, 48, PixelTypes.Rgba32)] public void ApplySaturationFilter(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Saturate(value), value); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs index b7d381f5f..fa43cb15a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Filters/SepiaTest.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters [Theory] [WithTestPatternImages(48, 48, PixelTypes.Rgba32)] public void ApplySepiaFilter(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Sepia()); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs index 0c09b6872..88cd6688a 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Overlays/OverlayTestBase.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays [Theory] [WithFileCollection(nameof(InputImages), nameof(ColorNames), PixelTypes.Rgba32)] public void FullImage_ApplyColor(TestImageProvider provider, string colorName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; Color color = TestUtils.GetColorByName(colorName); @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void FullImage_ApplyRadius(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunValidatingProcessorTest( @@ -48,12 +48,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Overlays [Theory] [WithFileCollection(nameof(InputImages), PixelTypes.Rgba32)] public void InBox(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Utility.TestGroupName = this.GetType().Name; provider.RunRectangleConstrainedValidatingProcessorTest(this.Apply); } + [Theory] + [WithTestPatternImages(70, 120, PixelTypes.Rgba32)] + public void WorksWithDiscoBuffers(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + provider.RunBufferCapacityLimitProcessorTest(37, c => this.Apply(c, Color.DarkRed)); + } + protected abstract void Apply(IImageProcessingContext ctx, Color color); protected abstract void Apply(IImageProcessingContext ctx, float radiusX, float radiusY); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs index b3900325d..bb7921d68 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void OctreeQuantizerConstructor() { - var quantizer = new OctreeQuantizer(128); - - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); - - quantizer = new OctreeQuantizer(false); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Diffuser); - - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); - - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson, 128); - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new OctreeQuantizer(expected); + + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = null }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new OctreeQuantizer(expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] @@ -38,21 +42,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(false); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(KnownDiffusers.Atkinson); + quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index f2e1136ca..3c1fa11ab 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -10,61 +10,70 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization { public class PaletteQuantizerTests { - private static readonly Color[] Rgb = new Color[] { Rgba32.Red, Rgba32.Green, Rgba32.Blue }; + private static readonly Color[] Palette = new Color[] { Color.Red, Color.Green, Color.Blue }; [Fact] public void PaletteQuantizerConstructor() { - var quantizer = new PaletteQuantizer(Rgb); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new PaletteQuantizer(Palette, expected); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, false); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Null(quantizer.Diffuser); + expected = new QuantizerOptions { Dither = null }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); - Assert.Equal(Rgb, quantizer.Palette); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new PaletteQuantizer(Palette, expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] public void PaletteQuantizerCanCreateFrameQuantizer() { - var quantizer = new PaletteQuantizer(Rgb); + var quantizer = new PaletteQuantizer(Palette); IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, false); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new PaletteQuantizer(Rgb, KnownDiffusers.Atkinson); + quantizer = new PaletteQuantizer(Palette, new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } [Fact] public void KnownQuantizersWebSafeTests() { IQuantizer quantizer = KnownQuantizers.WebSafe; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } [Fact] public void KnownQuantizersWernerTests() { IQuantizer quantizer = KnownQuantizers.Werner; - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs new file mode 100644 index 000000000..bcbb60e79 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -0,0 +1,220 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization +{ + public class QuantizerTests + { + /// + /// Something is causing tests to fail on NETFX in CI. + /// Could be a JIT error as everything runs well and is identical to .NET Core output. + /// Not worth investigating for now. + /// + /// + private static readonly bool SkipAllQuantizerTests = TestEnvironment.IsFramework; + + public static readonly string[] CommonTestImages = + { + TestImages.Png.CalliphoraPartial, + TestImages.Png.Bike + }; + + private static readonly QuantizerOptions NoDitherOptions = new QuantizerOptions { Dither = null }; + private static readonly QuantizerOptions DiffuserDitherOptions = new QuantizerOptions { Dither = KnownDitherings.FloydSteinberg }; + private static readonly QuantizerOptions OrderedDitherOptions = new QuantizerOptions { Dither = KnownDitherings.Bayer8x8 }; + + private static readonly QuantizerOptions Diffuser0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Diffuser0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Diffuser0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Diffuser0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.FloydSteinberg, + DitherScale = .75F + }; + + private static readonly QuantizerOptions Ordered0_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = 0F + }; + + private static readonly QuantizerOptions Ordered0_25_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .25F + }; + + private static readonly QuantizerOptions Ordered0_5_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .5F + }; + + private static readonly QuantizerOptions Ordered0_75_ScaleDitherOptions = new QuantizerOptions + { + Dither = KnownDitherings.Bayer8x8, + DitherScale = .75F + }; + + public static readonly TheoryData Quantizers + = new TheoryData + { + // Known uses error diffusion by default. + KnownQuantizers.Octree, + KnownQuantizers.WebSafe, + KnownQuantizers.Werner, + KnownQuantizers.Wu, + new OctreeQuantizer(NoDitherOptions), + new WebSafePaletteQuantizer(NoDitherOptions), + new WernerPaletteQuantizer(NoDitherOptions), + new WuQuantizer(NoDitherOptions), + new OctreeQuantizer(OrderedDitherOptions), + new WebSafePaletteQuantizer(OrderedDitherOptions), + new WernerPaletteQuantizer(OrderedDitherOptions), + new WuQuantizer(OrderedDitherOptions) + }; + + public static readonly TheoryData DitherScaleQuantizers + = new TheoryData + { + new OctreeQuantizer(Diffuser0_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), + new WuQuantizer(Diffuser0_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + new WuQuantizer(Diffuser0_25_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + new WuQuantizer(Diffuser0_5_ScaleDitherOptions), + + new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions), + new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + new WuQuantizer(Diffuser0_75_ScaleDitherOptions), + + new OctreeQuantizer(DiffuserDitherOptions), + new WebSafePaletteQuantizer(DiffuserDitherOptions), + new WernerPaletteQuantizer(DiffuserDitherOptions), + new WuQuantizer(DiffuserDitherOptions), + + new OctreeQuantizer(Ordered0_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), + new WuQuantizer(Ordered0_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_25_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), + new WuQuantizer(Ordered0_25_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_5_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), + new WuQuantizer(Ordered0_5_ScaleDitherOptions), + + new OctreeQuantizer(Ordered0_75_ScaleDitherOptions), + new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), + new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), + new WuQuantizer(Ordered0_75_ScaleDitherOptions), + + new OctreeQuantizer(OrderedDitherOptions), + new WebSafePaletteQuantizer(OrderedDitherOptions), + new WernerPaletteQuantizer(OrderedDitherOptions), + new WuQuantizer(OrderedDitherOptions), + }; + + private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantizationInBox(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; + + provider.RunRectangleConstrainedValidatingProcessorTest( + (x, rect) => x.Quantize(quantizer, rect), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFileCollection(nameof(CommonTestImages), nameof(Quantizers), PixelTypes.Rgba32)] + public void ApplyQuantization(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither?.GetType()?.Name ?? "NoDither"; + string testOutputDetails = $"{quantizerName}_{ditherName}"; + + provider.RunValidatingProcessorTest( + x => x.Quantize(quantizer), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + + [Theory] + [WithFile(TestImages.Png.David, nameof(DitherScaleQuantizers), PixelTypes.Rgba32)] + public void ApplyQuantizationWithDitheringScale(TestImageProvider provider, IQuantizer quantizer) + where TPixel : unmanaged, IPixel + { + if (SkipAllQuantizerTests) + { + return; + } + + string quantizerName = quantizer.GetType().Name; + string ditherName = quantizer.Options.Dither.GetType().Name; + float ditherScale = quantizer.Options.DitherScale; + string testOutputDetails = FormattableString.Invariant($"{quantizerName}_{ditherName}_{ditherScale}"); + + provider.RunValidatingProcessorTest( + x => x.Quantize(quantizer), + comparer: ValidatorComparer, + testOutputDetails: testOutputDetails, + appendPixelTypeToFileName: false); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs index 625043c7f..eb9d738e9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/WuQuantizerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -13,22 +13,26 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization [Fact] public void WuQuantizerConstructor() { - var quantizer = new WuQuantizer(128); - - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.FloydSteinberg, quantizer.Diffuser); - - quantizer = new WuQuantizer(false); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Null(quantizer.Diffuser); - - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); - Assert.Equal(QuantizerConstants.MaxColors, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); - - quantizer = new WuQuantizer(KnownDiffusers.Atkinson, 128); - Assert.Equal(128, quantizer.MaxColors); - Assert.Equal(KnownDiffusers.Atkinson, quantizer.Diffuser); + var expected = new QuantizerOptions { MaxColors = 128 }; + var quantizer = new WuQuantizer(expected); + + Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = null }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Null(quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); + + expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; + quantizer = new WuQuantizer(expected); + Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); + Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] @@ -38,21 +42,22 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.FloydSteinberg, frameQuantizer.Diffuser); + Assert.NotNull(frameQuantizer.Options); + Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(false); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.False(frameQuantizer.Dither); - Assert.Null(frameQuantizer.Diffuser); + Assert.Null(frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); - quantizer = new WuQuantizer(KnownDiffusers.Atkinson); + quantizer = new WuQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreateFrameQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); - Assert.True(frameQuantizer.Dither); - Assert.Equal(KnownDiffusers.Atkinson, frameQuantizer.Diffuser); + Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); + frameQuantizer.Dispose(); } } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs similarity index 86% rename from tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs rename to tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index 1c63d923a..cdc77d321 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Numerics; using System.Reflection; using SixLabors.ImageSharp.Advanced; @@ -14,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class AffineTransformTests { - private readonly ITestOutputHelper Output; + private readonly ITestOutputHelper output; // 1 byte difference on one color component. private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0134F, 3); @@ -65,15 +68,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms nameof(KnownResamplers.Lanczos8), }; - public AffineTransformTests(ITestOutputHelper output) => this.Output = output; + public AffineTransformTests(ITestOutputHelper output) => this.output = output; /// /// The output of an "all white" image should be "all white" or transparent, regardless of the transformation and the resampler. /// + /// The pixel type of the image. [Theory] [WithSolidFilledImages(nameof(Transform_DoesNotCreateEdgeArtifacts_ResamplerNames), 5, 5, 255, 255, 255, 255, PixelTypes.Rgba32)] public void Transform_DoesNotCreateEdgeArtifacts(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler resampler = GetResampler(resamplerName); using (Image image = provider.GetImage()) @@ -93,9 +97,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public void Transform_RotateScaleTranslate( TestImageProvider provider, float angleDeg, - float sx, float sy, - float tx, float ty) - where TPixel : struct, IPixel + float sx, + float sy, + float tx, + float ty) + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -118,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithTestPatternImages(96, 96, PixelTypes.Rgba32, 50, 0.8f)] public void Transform_RotateScale_ManuallyCentered(TestImageProvider provider, float angleDeg, float s) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -141,17 +147,18 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { 0, 0, 5, 10 }, { 0, 0, 10, 5 }, { 5, 0, 5, 10 }, - {-5,-5, 20, 20 } + { -5, -5, 20, 20 } }; /// /// Testing transforms using custom source rectangles: /// https://github.com/SixLabors/ImageSharp/pull/386#issuecomment-357104963 /// + /// The pixel type of the image. [Theory] [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] public void Transform_FromSourceRectangle1(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var rectangle = new Rectangle(48, 0, 48, 24); @@ -171,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithTestPatternImages(96, 48, PixelTypes.Rgba32)] public void Transform_FromSourceRectangle2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var rectangle = new Rectangle(0, 24, 48, 24); @@ -190,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = GetResampler(resamplerName); using (Image image = provider.GetImage()) @@ -206,6 +213,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 21)] + public void WorksWithDiscoBuffers(TestImageProvider provider, int bufferCapacityInPixelRows) + where TPixel : unmanaged, IPixel + { + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(50) + .AppendScale(new SizeF(.6F, .6F)); + provider.RunBufferCapacityLimitProcessorTest( + bufferCapacityInPixelRows, + c => c.Transform(builder)); + } + private static IResampler GetResampler(string name) { PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name); @@ -219,7 +239,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span data = image.Frames.RootFrame.GetPixelSpan(); var white = new Rgb24(255, 255, 255); @@ -239,7 +259,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms private void PrintMatrix(Matrix3x2 a) { string s = $"{a.M11:F10},{a.M12:F10},{a.M21:F10},{a.M22:F10},{a.M31:F10},{a.M32:F10}"; - this.Output.WriteLine(s); + this.output.WriteLine(s); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs index ceb6f8363..a4fec9fd9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AutoOrientTests.cs @@ -21,8 +21,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { { ExifDataType.Byte, new byte[] { 1 } }, { ExifDataType.SignedByte, new byte[] { 2 } }, - { ExifDataType.SignedShort, BitConverter.GetBytes((short) 3) }, - { ExifDataType.Long, BitConverter.GetBytes((uint) 4) }, + { ExifDataType.SignedShort, BitConverter.GetBytes((short)3) }, + { ExifDataType.Long, BitConverter.GetBytes(4U) }, { ExifDataType.SignedLong, BitConverter.GetBytes(5) } }; @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(FlipTestFile, nameof(ExifOrientationValues), PixelTypes.Rgba32)] public void AutoOrient_WorksForAllExifOrientations(TestImageProvider provider, ushort orientation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -58,17 +58,20 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(FlipTestFile, nameof(InvalidOrientationValues), PixelTypes.Rgba32)] public void AutoOrient_WorksWithCorruptExifData(TestImageProvider provider, ExifDataType dataType, byte[] orientation) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var profile = new ExifProfile(); profile.SetValue(ExifTag.JPEGTables, orientation); byte[] bytes = profile.ToByteArray(); + // Change the tag into ExifTag.Orientation bytes[16] = 18; bytes[17] = 1; + // Change the data type bytes[18] = (byte)dataType; + // Change the number of components bytes[20] = 1; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index b49ac3ea9..f0eef3afd 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] [WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] public void Crop(TestImageProvider provider, int x, int y, int w, int h) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var rect = new Rectangle(x, y, w, h); FormattableString info = $"X{x}Y{y}.W{w}H{h}"; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs index d20e6fa35..a04aef6bc 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/EntropyCropTest.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; @@ -22,9 +23,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(InputImages), nameof(EntropyCropValues), PixelTypes.Rgba32)] public void EntropyCrop(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.EntropyCrop(value), value, appendPixelTypeToFileName: false); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index 3c932bfaa..ae53afd67 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { [GroupOutput("Transforms")] @@ -26,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] public void Flip(TestImageProvider provider, FlipMode flipMode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest( ctx => ctx.Flip(flipMode), @@ -38,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)] public void Flip_WorksOnWrappedMemoryImage(TestImageProvider provider, FlipMode flipMode) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTestOnWrappedMemoryImage( ctx => ctx.Flip(flipMode), @@ -47,4 +46,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms appendPixelTypeToFileName: false); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs index dbaff43f0..28833248c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/PadTest.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void ImageShouldPad(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void ImageShouldPadWithBackgroundColor(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var color = Color.Red; TPixel expected = color.ToPixel(); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs index b7b4597c7..d3025d911 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResamplerTests.cs @@ -9,7 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { public class ResamplerTests - { + { [Theory] [InlineData(-2, 0)] [InlineData(-1, 0)] @@ -66,4 +66,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms Assert.Equal(result, expected); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 1681c3046..3d08cf1a4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -26,7 +26,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public ReferenceKernel GetKernel(int destinationIndex) => this.kernels[destinationIndex]; - public static ReferenceKernelMap Calculate(IResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + public static ReferenceKernelMap Calculate(in TResampler sampler, int destinationSize, int sourceSize, bool normalize = true) + where TResampler : struct, IResampler { double ratio = (double)sourceSize / destinationSize; double scale = ratio; @@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms TolerantMath tolerantMath = TolerantMath.Default; double radius = tolerantMath.Ceiling(scale * sampler.Radius); - + var result = new List(); for (int i = 0; i < destinationSize; i++) @@ -61,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms double sum = 0; - var values = new double[right - left + 1]; + double[] values = new double[right - left + 1]; for (int j = left; j <= right; j++) { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 91b011ed6..8dbc05655 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -25,91 +25,81 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms /// /// resamplerName, srcSize, destSize /// - public static readonly TheoryData KernelMapData = new TheoryData + public static readonly TheoryData KernelMapData + = new TheoryData { - { nameof(KnownResamplers.Bicubic), 15, 10 }, - { nameof(KnownResamplers.Bicubic), 10, 15 }, - { nameof(KnownResamplers.Bicubic), 20, 20 }, - { nameof(KnownResamplers.Bicubic), 50, 40 }, - { nameof(KnownResamplers.Bicubic), 40, 50 }, - { nameof(KnownResamplers.Bicubic), 500, 200 }, - { nameof(KnownResamplers.Bicubic), 200, 500 }, - { nameof(KnownResamplers.Bicubic), 3032, 400 }, - - { nameof(KnownResamplers.Bicubic), 10, 25 }, - - { nameof(KnownResamplers.Lanczos3), 16, 12 }, - { nameof(KnownResamplers.Lanczos3), 12, 16 }, - { nameof(KnownResamplers.Lanczos3), 12, 9 }, - { nameof(KnownResamplers.Lanczos3), 9, 12 }, - { nameof(KnownResamplers.Lanczos3), 6, 8 }, - { nameof(KnownResamplers.Lanczos3), 8, 6 }, - { nameof(KnownResamplers.Lanczos3), 20, 12 }, - - { nameof(KnownResamplers.Lanczos3), 5, 25 }, - { nameof(KnownResamplers.Lanczos3), 5, 50 }, - - { nameof(KnownResamplers.Lanczos3), 25, 5 }, - { nameof(KnownResamplers.Lanczos3), 50, 5 }, - { nameof(KnownResamplers.Lanczos3), 49, 5 }, - { nameof(KnownResamplers.Lanczos3), 31, 5 }, - - { nameof(KnownResamplers.Lanczos8), 500, 200 }, - { nameof(KnownResamplers.Lanczos8), 100, 10 }, - { nameof(KnownResamplers.Lanczos8), 100, 80 }, - { nameof(KnownResamplers.Lanczos8), 10, 100 }, + { KnownResamplers.Bicubic, 15, 10 }, + { KnownResamplers.Bicubic, 10, 15 }, + { KnownResamplers.Bicubic, 20, 20 }, + { KnownResamplers.Bicubic, 50, 40 }, + { KnownResamplers.Bicubic, 40, 50 }, + { KnownResamplers.Bicubic, 500, 200 }, + { KnownResamplers.Bicubic, 200, 500 }, + { KnownResamplers.Bicubic, 3032, 400 }, + { KnownResamplers.Bicubic, 10, 25 }, + { KnownResamplers.Lanczos3, 16, 12 }, + { KnownResamplers.Lanczos3, 12, 16 }, + { KnownResamplers.Lanczos3, 12, 9 }, + { KnownResamplers.Lanczos3, 9, 12 }, + { KnownResamplers.Lanczos3, 6, 8 }, + { KnownResamplers.Lanczos3, 8, 6 }, + { KnownResamplers.Lanczos3, 20, 12 }, + { KnownResamplers.Lanczos3, 5, 25 }, + { KnownResamplers.Lanczos3, 5, 50 }, + { KnownResamplers.Lanczos3, 25, 5 }, + { KnownResamplers.Lanczos3, 50, 5 }, + { KnownResamplers.Lanczos3, 49, 5 }, + { KnownResamplers.Lanczos3, 31, 5 }, + { KnownResamplers.Lanczos8, 500, 200 }, + { KnownResamplers.Lanczos8, 100, 10 }, + { KnownResamplers.Lanczos8, 100, 80 }, + { KnownResamplers.Lanczos8, 10, 100 }, // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: - { nameof(KnownResamplers.Box), 378, 149 }, - { nameof(KnownResamplers.Box), 349, 174 }, + { KnownResamplers.Box, 378, 149 }, + { KnownResamplers.Box, 349, 174 }, // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData - { nameof(KnownResamplers.Box), 201, 100 }, - { nameof(KnownResamplers.Box), 199, 99 }, - { nameof(KnownResamplers.Box), 10, 299 }, - { nameof(KnownResamplers.Box), 299, 10 }, - { nameof(KnownResamplers.Box), 301, 300 }, - { nameof(KnownResamplers.Box), 1180, 480 }, - - { nameof(KnownResamplers.Lanczos2), 3264, 3032 }, - - { nameof(KnownResamplers.Bicubic), 1280, 2240 }, - { nameof(KnownResamplers.Bicubic), 1920, 1680 }, - { nameof(KnownResamplers.Bicubic), 3072, 2240 }, - - { nameof(KnownResamplers.Welch), 300, 2008 }, + { KnownResamplers.Box, 201, 100 }, + { KnownResamplers.Box, 199, 99 }, + { KnownResamplers.Box, 10, 299 }, + { KnownResamplers.Box, 299, 10 }, + { KnownResamplers.Box, 301, 300 }, + { KnownResamplers.Box, 1180, 480 }, + { KnownResamplers.Lanczos2, 3264, 3032 }, + { KnownResamplers.Bicubic, 1280, 2240 }, + { KnownResamplers.Bicubic, 1920, 1680 }, + { KnownResamplers.Bicubic, 3072, 2240 }, + { KnownResamplers.Welch, 300, 2008 }, // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData - { nameof(KnownResamplers.Bicubic), 10, 50 }, - { nameof(KnownResamplers.Bicubic), 49, 301 }, - { nameof(KnownResamplers.Bicubic), 301, 49 }, - { nameof(KnownResamplers.Bicubic), 1680, 1200 }, - { nameof(KnownResamplers.Box), 13, 299 }, - { nameof(KnownResamplers.Lanczos5), 3032, 600 }, + { KnownResamplers.Bicubic, 10, 50 }, + { KnownResamplers.Bicubic, 49, 301 }, + { KnownResamplers.Bicubic, 301, 49 }, + { KnownResamplers.Bicubic, 1680, 1200 }, + { KnownResamplers.Box, 13, 299 }, + { KnownResamplers.Lanczos5, 3032, 600 }, }; public static TheoryData GeneratedImageResizeData = GenerateImageResizeData(); - - [Theory( - Skip = "Only for debugging and development" - )] + [Theory(Skip = "Only for debugging and development")] [MemberData(nameof(KernelMapData))] - public void PrintNonNormalizedKernelMap(string resamplerName, int srcSize, int destSize) + public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var kernelMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize, false); + var kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); } [Theory] [MemberData(nameof(KernelMapData))] - public void KernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - this.VerifyKernelMapContentIsCorrect(resamplerName, srcSize, destSize); + this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); } // Comprehensive but expensive tests, for ResizeKernelMap. @@ -124,14 +114,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } #endif - private void VerifyKernelMapContentIsCorrect(string resamplerName, int srcSize, int destSize) + private void VerifyKernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) + where TResampler : struct, IResampler { - IResampler resampler = TestUtils.GetResampler(resamplerName); - - var referenceMap = ReferenceKernelMap.Calculate(resampler, destSize, srcSize); - var kernelMap = ResizeKernelMap.Calculate(resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); - - + var referenceMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize); + var kernelMap = ResizeKernelMap.Calculate(in resampler, destSize, srcSize, Configuration.Default.MemoryAllocator); #if DEBUG this.Output.WriteLine(kernelMap.Info); @@ -157,8 +144,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms Assert.Equal(expectedValues.Length, actualValues.Length); - - for (int x = 0; x < expectedValues.Length; x++) { Assert.True( @@ -168,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } - private static string PrintKernelMap(ResizeKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ResizeKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationLength, (km, i) => km.GetKernel(i)); - private static string PrintKernelMap(ReferenceKernelMap kernelMap) => - PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); + private static string PrintKernelMap(ReferenceKernelMap kernelMap) + => PrintKernelMap(kernelMap, km => km.DestinationSize, (km, i) => km.GetKernel(i)); private static string PrintKernelMap( TKernelMap kernelMap, @@ -207,7 +192,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms return bld.ToString(); } - private static TheoryData GenerateImageResizeData() { var result = new TheoryData(); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 763db47f9..655bcfea4 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -5,18 +5,15 @@ using System; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; - using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - using Xunit; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { public class ResizeTests @@ -38,15 +35,14 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms nameof(KnownResamplers.Lanczos5), }; - private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.07F); [Fact] public void Resize_PixelAgnostic() { - var filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); + string filePath = TestFile.GetInputFileFullPath(TestImages.Jpeg.Baseline.Calliphora); - using (Image image = Image.Load(filePath)) + using (var image = Image.Load(filePath)) { image.Mutate(x => x.Resize(image.Size() / 2)); string path = System.IO.Path.Combine( @@ -62,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 1024)] [WithTestPatternImages(3032, 3032, PixelTypes.Rgba32, 400, 128)] public void LargeImage(TestImageProvider provider, int destSize, int workingBufferSizeHintInKilobytes) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -71,7 +67,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms provider.Configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInKilobytes * 1024; - using (var image = provider.GetImage()) + using (Image image = provider.GetImage()) { image.Mutate(x => x.Resize(destSize, destSize)); image.DebugSave(provider, appendPixelTypeToFileName: false); @@ -83,14 +79,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithBasicTestPatternImages(2, 256, PixelTypes.Rgba32, 1, 1, 1, 8)] [WithBasicTestPatternImages(2, 32, PixelTypes.Rgba32, 1, 1, 1, 2)] public void Resize_BasicSmall(TestImageProvider provider, int wN, int wD, int hN, int hD) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Basic test case, very helpful for debugging // [WithBasicTestPatternImages(15, 12, PixelTypes.Rgba32, 2, 3, 1, 2)] means: // resizing: (15, 12) -> (10, 6) // kernel dimensions: (3, 4) - - using (Image image = provider.GetImage()) { var destSize = new Size(image.Width * wN / wD, image.Height * hN / hD); @@ -114,21 +108,21 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void WorkingBufferSizeHintInBytes_IsAppliedCorrectly( TestImageProvider provider, int workingBufferLimitInRows) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image0 = provider.GetImage()) { Size destSize = image0.Size() / 4; - Configuration configuration = Configuration.CreateDefaultInstance(); + var configuration = Configuration.CreateDefaultInstance(); int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; - TestMemoryAllocator allocator = new TestMemoryAllocator(); + var allocator = new TestMemoryAllocator(); configuration.MemoryAllocator = allocator; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; - var verticalKernelMap = ResizeKernelMap.Calculate( - KnownResamplers.Bicubic, + var verticalKernelMap = ResizeKernelMap.Calculate( + default, destSize.Height, image0.Height, Configuration.Default.MemoryAllocator); @@ -159,10 +153,43 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms } } + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 100)] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 31, 73)] + [WithTestPatternImages(200, 200, PixelTypes.Rgba32, 73, 31)] + [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 13, 17)] + [WithTestPatternImages(200, 193, PixelTypes.Rgba32, 79, 23)] + [WithTestPatternImages(200, 503, PixelTypes.Rgba32, 61, 33)] + public void WorksWithDiscoBuffers( + TestImageProvider provider, + int workingBufferLimitInRows, + int bufferCapacityInRows) + where TPixel : unmanaged, IPixel + { + using Image expected = provider.GetImage(); + int width = expected.Width; + Size destSize = expected.Size() / 4; + expected.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + + // Replace configuration: + provider.Configuration = Configuration.CreateDefaultInstance(); + + // Note: when AllocatorCapacityInBytes < WorkingBufferSizeHintInBytes, + // ResizeProcessor is expected to use the minimum of the two values, when establishing the working buffer. + provider.LimitAllocatorBufferCapacity().InBytes(width * bufferCapacityInRows * SizeOfVector4); + provider.Configuration.WorkingBufferSizeHintInBytes = width * workingBufferLimitInRows * SizeOfVector4; + + using Image actual = provider.GetImage(); + actual.Mutate(c => c.Resize(destSize, KnownResamplers.Bicubic, false)); + actual.DebugSave(provider, $"{workingBufferLimitInRows}-{bufferCapacityInRows}"); + + ImageComparer.Exact.VerifySimilarity(expected, actual); + } + [Theory] [WithTestPatternImages(100, 100, DefaultPixelType)] public void Resize_Compand(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -177,9 +204,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] public void Resize_DoesNotBleedAlphaPixels(TestImageProvider provider, bool compand) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - string details = compand ? "Compand" : ""; + string details = compand ? "Compand" : string.Empty; provider.RunValidatingProcessorTest( x => x.Resize(x.GetCurrentSize() / 2, compand), @@ -191,7 +218,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -205,7 +232,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)] public void Resize_IsNotBoundToSinglePixelType(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer); } @@ -213,7 +240,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void Resize_ThrowsForWrappedMemoryImage(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image0 = provider.GetImage()) { @@ -235,7 +262,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms public void Resize_WorksWithAllParallelismLevels( TestImageProvider provider, int maxDegreeOfParallelism) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism > 0 ? maxDegreeOfParallelism : Environment.ProcessorCount; @@ -277,7 +304,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms float? ratio, int? specificDestWidth, int? specificDestHeight) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = TestUtils.GetResampler(samplerName); @@ -297,31 +324,31 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms provider.RunValidatingProcessorTest( ctx => + { + SizeF newSize; + string destSizeInfo; + if (ratio.HasValue) { - SizeF newSize; - string destSizeInfo; - if (ratio.HasValue) - { - newSize = ctx.GetCurrentSize() * ratio.Value; - destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); - } - else + newSize = ctx.GetCurrentSize() * ratio.Value; + destSizeInfo = ratio.Value.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) { - if (!specificDestWidth.HasValue || !specificDestHeight.HasValue) - { - throw new InvalidOperationException( - "invalid dimensional input for Resize_WorksWithAllResamplers!"); - } - - newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); - destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + throw new InvalidOperationException( + "invalid dimensional input for Resize_WorksWithAllResamplers!"); } - FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; + newSize = new SizeF(specificDestWidth.Value, specificDestHeight.Value); + destSizeInfo = $"{newSize.Width}x{newSize.Height}"; + } + + FormattableString testOutputDetails = $"{samplerName}-{destSizeInfo}"; - ctx.Resize((Size)newSize, sampler, false); - return testOutputDetails; - }, + ctx.Resize((Size)newSize, sampler, false); + return testOutputDetails; + }, comparer, appendPixelTypeToFileName: false); } @@ -329,7 +356,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeFromSourceRectangle(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -357,7 +384,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeHeightAndKeepAspect(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -371,7 +398,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(10, 100, DefaultPixelType)] public void ResizeHeightCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -384,7 +411,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWidthAndKeepAspect(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -398,7 +425,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(100, 10, DefaultPixelType)] public void ResizeWidthCannotKeepAspectKeepsOnePixel(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -411,7 +438,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithBoxPadMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -431,7 +458,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropHeightMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -447,7 +474,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithCropWidthMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -463,7 +490,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(TestImages.Jpeg.Issues.IncorrectResize1006, DefaultPixelType)] public void CanResizeLargeImageWithCropMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -483,7 +510,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithMaxMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -499,7 +526,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithMinMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -521,7 +548,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithPadMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -541,7 +568,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFileCollection(nameof(CommonTestImages), DefaultPixelType)] public void ResizeWithStretchMode(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -563,7 +590,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithFile(TestImages.Jpeg.Issues.ExifGetString750Transform, DefaultPixelType)] [WithFile(TestImages.Jpeg.Issues.ExifResize1049, DefaultPixelType)] public void CanResizeExifIssueImages(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Test images are large so skip on 32bit for now. if (!TestEnvironment.Is64BitProcess) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs index 1e08836c1..04647c019 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateFlipTests.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(RotateFlipValues), 100, 50, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(RotateFlipValues), 50, 100, PixelTypes.Rgba32)] public void RotateFlip(TestImageProvider provider, RotateMode rotateType, FlipMode flipType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index 7801c7143..cf7c0c54b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithAngle(TestImageProvider provider, float value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)] public void Rotate_WithRotateTypeEnum(TestImageProvider provider, RotateMode value) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs index ad77027f0..0720bcfa2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SkewTests.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithTestPatternImages(nameof(SkewValues), 100, 50, CommonPixelTypes)] public void Skew_IsNotBoundToSinglePixelType(TestImageProvider provider, float x, float y) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.RunValidatingProcessorTest(ctx => ctx.Skew(x, y), $"{x}_{y}", ValidatorComparer); } @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms [Theory] [WithFile(TestImages.Png.Ducky, nameof(ResamplerNames), PixelTypes.Rgba32)] public void Skew_WorksWithAllResamplers(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = TestUtils.GetResampler(resamplerName); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs index 7bb155f3a..59d226ef5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using Xunit; @@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms public class FlipTests : BaseImageOperationsExtensionTest { - [Theory] [InlineData(FlipMode.None)] [InlineData(FlipMode.Horizontal)] diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs index 33da33c71..db1e76ae5 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing; @@ -20,9 +20,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Pad(width, height); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 3679180f4..c702aebe4 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Transforms { public class ProjectiveTransformTests @@ -45,19 +45,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms { TaperSide.Bottom, TaperCorner.Both }, { TaperSide.Bottom, TaperCorner.LeftOrTop }, { TaperSide.Bottom, TaperCorner.RightOrBottom }, - { TaperSide.Top, TaperCorner.Both }, { TaperSide.Top, TaperCorner.LeftOrTop }, { TaperSide.Top, TaperCorner.RightOrBottom }, - { TaperSide.Left, TaperCorner.Both }, { TaperSide.Left, TaperCorner.LeftOrTop }, { TaperSide.Left, TaperCorner.RightOrBottom }, - { TaperSide.Right, TaperCorner.Both }, { TaperSide.Right, TaperCorner.LeftOrTop }, { TaperSide.Right, TaperCorner.RightOrBottom }, - }; public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output; @@ -65,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithTestPatternImages(nameof(ResamplerNames), 150, 150, PixelTypes.Rgba32)] public void Transform_WithSampler(TestImageProvider provider, string resamplerName) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { IResampler sampler = GetResampler(resamplerName); using (Image image = provider.GetImage()) @@ -81,9 +77,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms } [Theory] - [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Rgba32.Red), PixelTypes.Rgba32)] + [WithSolidFilledImages(nameof(TaperMatrixData), 30, 30, nameof(Color.Red), PixelTypes.Rgba32)] public void Transform_WithTaperMatrix(TestImageProvider provider, TaperSide taperSide, TaperCorner taperCorner) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -101,7 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] public void RawTransformMatchesDocumentedExample(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // Printing some extra output to help investigating rounding errors: this.Output.WriteLine($"Vector.IsHardwareAccelerated: {Vector.IsHardwareAccelerated}"); @@ -126,17 +122,19 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [WithSolidFilledImages(290, 154, 0, 0, 255, PixelTypes.Rgba32)] public void PerspectiveTransformMatchesCSS(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // https://jsfiddle.net/dFrHS/545/ // https://github.com/SixLabors/ImageSharp/issues/787 using (Image image = provider.GetImage()) { +#pragma warning disable SA1117 // Parameters should be on same line or separate lines var matrix = new Matrix4x4( 0.260987f, -0.434909f, 0, -0.0022184f, 0.373196f, 0.949882f, 0, -0.000312129f, 0, 0, 1, 0, 52, 165, 0, 1); +#pragma warning restore SA1117 // Parameters should be on same line or separate lines ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() .AppendMatrix(matrix); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index f87e17e06..e7b92b7b3 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -17,8 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); } [Fact] @@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height, sampler); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); } @@ -47,8 +47,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(width, height, sampler, compand); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); } @@ -73,8 +73,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.operations.Resize(resizeOptions); ResizeProcessor resizeProcessor = this.Verify(); - Assert.Equal(width, resizeProcessor.TargetWidth); - Assert.Equal(height, resizeProcessor.TargetHeight); + Assert.Equal(width, resizeProcessor.DestinationWidth); + Assert.Equal(height, resizeProcessor.DestinationHeight); Assert.Equal(sampler, resizeProcessor.Sampler); Assert.Equal(compand, resizeProcessor.Compand); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index c13d4affd..21359799e 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,7 +25,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [MemberData(nameof(ScaleTranslate_Data))] +#pragma warning disable SA1300 // Element should begin with upper-case letter public void _1Scale_2Translate(Vector2 scale, Vector2 translate, Vector2 source, Vector2 expectedDest) +#pragma warning restore SA1300 // Element should begin with upper-case letter { // These operations should be size-agnostic: var size = new Size(123, 321); @@ -49,7 +51,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms [Theory] [MemberData(nameof(TranslateScale_Data))] +#pragma warning disable SA1300 // Element should begin with upper-case letter public void _1Translate_2Scale(Vector2 translate, Vector2 scale, Vector2 source, Vector2 expectedDest) +#pragma warning restore SA1300 // Element should begin with upper-case letter { // Translate ans scale are size-agnostic: var size = new Size(456, 432); @@ -95,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.AppendRotationDegrees(builder, degrees); // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationMatrixDegrees(degrees, size); + Matrix3x2 matrix = TransformUtilities.CreateRotationMatrixDegrees(degrees, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -149,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms this.AppendSkewDegrees(builder, degreesX, degreesY); - Matrix3x2 matrix = TransformUtils.CreateSkewMatrixDegrees(degreesX, degreesY, size); + Matrix3x2 matrix = TransformUtilities.CreateSkewMatrixDegrees(degreesX, degreesY, size); var position = new Vector2(x, y); var expected = Vector2.Transform(position, matrix); @@ -271,4 +275,4 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs index 95a47fd7c..0b63d6377 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs @@ -13,6 +13,9 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Abstractions; +// in this file, comments are used for disabling stuff for local execution +#pragma warning disable SA1515 + namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { public class JpegProfilingBenchmarks : MeasureFixture @@ -22,24 +25,28 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { } - public static readonly TheoryData DecodeJpegData = new TheoryData + public static readonly TheoryData DecodeJpegData = new TheoryData { - TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, - TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, - TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, - TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, - TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, - TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, + { TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, 20 }, + { TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, 20 }, + { TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, 40 }, + // { TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, 10 }, + // { TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, 5 }, + { TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, 5 } }; [Theory(Skip = ProfilingSetup.SkipProfilingTests)] [MemberData(nameof(DecodeJpegData))] - public void DecodeJpeg(string fileName) + public void DecodeJpeg(string fileName, int executionCount) { - this.DecodeJpegBenchmarkImpl(fileName, new JpegDecoder()); + var decoder = new JpegDecoder() + { + IgnoreMetadata = true + }; + this.DecodeJpegBenchmarkImpl(fileName, decoder, executionCount); } - private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder) + private void DecodeJpegBenchmarkImpl(string fileName, IImageDecoder decoder, int executionCount) { // do not run this on CI even by accident if (TestEnvironment.RunsOnCI) @@ -47,8 +54,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks return; } - const int ExecutionCount = 20; - if (!Vector.IsHardwareAccelerated) { throw new Exception("Vector.IsHardwareAccelerated == false! ('prefer32 bit' enabled?)"); @@ -58,14 +63,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks byte[] bytes = File.ReadAllBytes(path); this.Measure( - ExecutionCount, + executionCount, () => { var img = Image.Load(bytes, decoder); img.Dispose(); }, - // ReSharper disable once ExplicitCallerInfoArgument +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // ReSharper disable once ExplicitCallerInfoArgument $"Decode {fileName}"); +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } // Benchmark, enable manually! @@ -101,8 +108,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks ms.Seek(0, SeekOrigin.Begin); } }, - // ReSharper disable once ExplicitCallerInfoArgument +#pragma warning disable SA1515 // Single-line comment should be preceded by blank line + // ReSharper disable once ExplicitCallerInfoArgument $@"Encode {testFiles.Length} images"); +#pragma warning restore SA1515 // Single-line comment should be preceded by blank line } foreach (Image image in testImages) @@ -111,4 +120,4 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 95fe4e48f..858607a02 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; @@ -28,7 +28,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks using (var ms = new MemoryStream()) { - this.Measure(30, + this.Measure( + 30, () => { using (var image = Image.Load(configuration, imageBytes)) @@ -36,9 +37,10 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks image.Mutate(x => x.Resize(image.Size() / 4)); image.SaveAsJpeg(ms); } + ms.Seek(0, SeekOrigin.Begin); }); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs index f9a68d4e7..34a1eaa30 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ProfilingSetup.cs @@ -1,9 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. // Uncomment to enable local profiling benchmarks. DO NOT PUSH TO MAIN! // #define PROFILING - namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks { public static class ProfilingSetup @@ -15,4 +14,4 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks "Profiling benchmark, enable manually!"; #endif } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs index 8b9355938..ba5eb532b 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs @@ -20,13 +20,14 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } public int ExecutionCount { get; set; } = 50; - + [Theory(Skip = ProfilingSetup.SkipProfilingTests)] [InlineData(100, 100)] [InlineData(2000, 2000)] public void ResizeBicubic(int width, int height) { - this.Measure(this.ExecutionCount, + this.Measure( + this.ExecutionCount, () => { using (var image = new Image(this.configuration, width, height)) @@ -35,6 +36,5 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks } }); } - } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 775001709..7e4eced8f 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -22,15 +22,30 @@ namespace SixLabors.ImageSharp.Tests var octree = new OctreeQuantizer(); var wu = new WuQuantizer(); - Assert.NotNull(werner.Diffuser); - Assert.NotNull(webSafe.Diffuser); - Assert.NotNull(octree.Diffuser); - Assert.NotNull(wu.Diffuser); - - Assert.True(werner.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(webSafe.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(octree.CreateFrameQuantizer(this.Configuration).Dither); - Assert.True(wu.CreateFrameQuantizer(this.Configuration).Dither); + Assert.NotNull(werner.Options.Dither); + Assert.NotNull(webSafe.Options.Dither); + Assert.NotNull(octree.Options.Dither); + Assert.NotNull(wu.Options.Dither); + + using (IFrameQuantizer quantizer = werner.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IFrameQuantizer quantizer = webSafe.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IFrameQuantizer quantizer = octree.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } + + using (IFrameQuantizer quantizer = wu.CreateFrameQuantizer(this.Configuration)) + { + Assert.NotNull(quantizer.Options.Dither); + } } [Theory] @@ -39,21 +54,28 @@ namespace SixLabors.ImageSharp.Tests public void OctreeQuantizerYieldsCorrectTransparentPixel( TestImageProvider provider, bool dither) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); + + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } - var quantizer = new OctreeQuantizer(dither); + var quantizer = new OctreeQuantizer(options); foreach (ImageFrame frame in image.Frames) { - IQuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelBufferSpan()[0]); + } } } } @@ -62,37 +84,44 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] public void WuQuantizerYieldsCorrectTransparentPixel(TestImageProvider provider, bool dither) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { - Assert.True(image[0, 0].Equals(default(TPixel))); + Assert.True(image[0, 0].Equals(default)); - var quantizer = new WuQuantizer(dither); + var options = new QuantizerOptions(); + if (!dither) + { + options.Dither = null; + } + + var quantizer = new WuQuantizer(options); foreach (ImageFrame frame in image.Frames) { - IQuantizedFrame quantized = - quantizer.CreateFrameQuantizer(this.Configuration).QuantizeFrame(frame); - - int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelSpan()[0]); + using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(this.Configuration)) + using (IndexedImageFrame quantized = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) + { + int index = this.GetTransparentIndex(quantized); + Assert.Equal(index, quantized.GetPixelBufferSpan()[0]); + } } } } - private int GetTransparentIndex(IQuantizedFrame quantized) - where TPixel : struct, IPixel + private int GetTransparentIndex(IndexedImageFrame quantized) + where TPixel : unmanaged, IPixel { // Transparent pixels are much more likely to be found at the end of a palette int index = -1; - Rgba32 trans = default; ReadOnlySpan paletteSpan = quantized.Palette.Span; - for (int i = paletteSpan.Length - 1; i >= 0; i--) - { - paletteSpan[i].ToRgba32(ref trans); + Span colorSpan = stackalloc Rgba32[QuantizerConstants.MaxColors].Slice(0, paletteSpan.Length); - if (trans.Equals(default)) + PixelOperations.Instance.ToRgba32(quantized.Configuration, paletteSpan, colorSpan); + for (int i = colorSpan.Length - 1; i >= 0; i--) + { + if (colorSpan[i].Equals(default)) { index = i; } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index a1de7fd4b..2a0a02d94 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -12,34 +15,38 @@ namespace SixLabors.ImageSharp.Tests.Quantization public void SinglePixelOpaque() { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - using (var image = new Image(config, 1, 1, Rgba32.Black)) - using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) - { - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + using var image = new Image(config, 1, 1, Color.Black); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(Rgba32.Black, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); - } + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelBufferSpan().Length); + + Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelBufferSpan()[0]); } [Fact] public void SinglePixelTransparent() { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - using (var image = new Image(config, 1, 1, default(Rgba32))) - using (IQuantizedFrame result = quantizer.CreateFrameQuantizer(config).QuantizeFrame(image.Frames[0])) - { - Assert.Equal(1, result.Palette.Length); - Assert.Equal(1, result.GetPixelSpan().Length); + using var image = new Image(config, 1, 1, default(Rgba32)); + ImageFrame frame = image.Frames.RootFrame; - Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelSpan()[0]); - } + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(1, result.Palette.Length); + Assert.Equal(1, result.GetPixelBufferSpan().Length); + + Assert.Equal(default, result.Palette.Span[0]); + Assert.Equal(0, result.GetPixelBufferSpan()[0]); } [Fact] @@ -60,63 +67,65 @@ namespace SixLabors.ImageSharp.Tests.Quantization [Fact] public void Palette256() { - using (var image = new Image(1, 256)) + using var image = new Image(1, 256); + + for (int i = 0; i < 256; i++) { - for (int i = 0; i < 256; i++) - { - byte r = (byte)((i % 4) * 85); - byte g = (byte)(((i / 4) % 4) * 85); - byte b = (byte)(((i / 16) % 4) * 85); - byte a = (byte)((i / 64) * 85); + byte r = (byte)((i % 4) * 85); + byte g = (byte)(((i / 4) % 4) * 85); + byte b = (byte)(((i / 16) % 4) * 85); + byte a = (byte)((i / 64) * 85); - image[0, i] = new Rgba32(r, g, b, a); - } + image[0, i] = new Rgba32(r, g, b, a); + } - Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); - using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) - { - Assert.Equal(256, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Configuration config = Configuration.Default; + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); - var actualImage = new Image(1, 256); + ImageFrame frame = image.Frames.RootFrame; - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; - for (int y = 0; y < actualImage.Height; y++) - { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); - int yy = y * actualImage.Width; + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); - for (int x = 0; x < actualImage.Width; x++) - { - int i = x + yy; - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; - } - } + Assert.Equal(256, result.Palette.Length); + Assert.Equal(256, result.GetPixelBufferSpan().Length); - Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); + var actualImage = new Image(1, 256); + + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < actualImage.Height; y++) + { + Span row = actualImage.GetPixelRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.GetPixelBufferSpan(); + int yy = y * actualImage.Width; + + for (int x = 0; x < actualImage.Width; x++) + { + int i = x + yy; + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[i])]; } } + + Assert.True(image.GetPixelSpan().SequenceEqual(actualImage.GetPixelSpan())); } [Theory] [WithFile(TestImages.Png.LowColorVariance, PixelTypes.Rgba32)] public void LowVariance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { // See https://github.com/SixLabors/ImageSharp/issues/866 using (Image image = provider.GetImage()) { Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); - using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) - { - Assert.Equal(48, result.Palette.Length); - } + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + ImageFrame frame = image.Frames.RootFrame; + + using IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config); + using IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds()); + + Assert.Equal(48, result.Palette.Length); } } @@ -139,20 +148,21 @@ namespace SixLabors.ImageSharp.Tests.Quantization } Configuration config = Configuration.Default; - var quantizer = new WuQuantizer(false); + var quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }); + ImageFrame frame = image.Frames.RootFrame; using (IFrameQuantizer frameQuantizer = quantizer.CreateFrameQuantizer(config)) - using (IQuantizedFrame result = frameQuantizer.QuantizeFrame(image.Frames[0])) + using (IndexedImageFrame result = frameQuantizer.QuantizeFrame(frame, frame.Bounds())) { Assert.Equal(4 * 8, result.Palette.Length); - Assert.Equal(256, result.GetPixelSpan().Length); + Assert.Equal(256, result.GetPixelBufferSpan().Length); ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = result.Palette.Length - 1; + int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelSpan(); + ReadOnlySpan quantizedPixelSpan = result.GetPixelBufferSpan(); int yy = y * actualImage.Width; for (int x = 0; x < actualImage.Width; x++) @@ -167,4 +177,4 @@ namespace SixLabors.ImageSharp.Tests.Quantization } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs index 771e33038..a4d5e7c13 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataArray.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataArray { - #region Byte - public static readonly byte[] UInt8 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; public static readonly object[][] UInt8TestData = @@ -14,14 +12,9 @@ namespace SixLabors.ImageSharp.Tests new object[] { UInt8, UInt8 } }; - #endregion - - #region UInt16 - public static readonly ushort[] UInt16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] UInt16_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_0, IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, @@ -31,22 +24,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt16_6, IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.UInt16_8, - IccTestDataPrimitives.UInt16_9 - ); + IccTestDataPrimitives.UInt16_9); public static readonly object[][] UInt16TestData = { new object[] { UInt16_Arr, UInt16_Val } }; - #endregion - - #region Int16 - public static readonly short[] Int16_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] Int16_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Int16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int16_0, IccTestDataPrimitives.Int16_1, IccTestDataPrimitives.Int16_2, @@ -56,22 +43,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Int16_6, IccTestDataPrimitives.Int16_7, IccTestDataPrimitives.Int16_8, - IccTestDataPrimitives.Int16_9 - ); + IccTestDataPrimitives.Int16_9); public static readonly object[][] Int16TestData = { new object[] { Int16_Arr, Int16_Val } }; - #endregion - - #region UInt32 - public static readonly uint[] UInt32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] UInt32_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt32_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, @@ -81,22 +62,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.UInt32_7, IccTestDataPrimitives.UInt32_8, - IccTestDataPrimitives.UInt32_9 - ); + IccTestDataPrimitives.UInt32_9); public static readonly object[][] UInt32TestData = { new object[] { UInt32_Arr, UInt32_Val } }; - #endregion - - #region Int32 - public static readonly int[] Int32_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] Int32_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Int32_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int32_0, IccTestDataPrimitives.Int32_1, IccTestDataPrimitives.Int32_2, @@ -106,22 +81,16 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Int32_6, IccTestDataPrimitives.Int32_7, IccTestDataPrimitives.Int32_8, - IccTestDataPrimitives.Int32_9 - ); + IccTestDataPrimitives.Int32_9); public static readonly object[][] Int32TestData = { new object[] { Int32_Arr, Int32_Val } }; - #endregion - - #region UInt64 - public static readonly ulong[] UInt64_Val = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - public static readonly byte[] UInt64_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt64_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt64_0, IccTestDataPrimitives.UInt64_1, IccTestDataPrimitives.UInt64_2, @@ -131,14 +100,11 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt64_6, IccTestDataPrimitives.UInt64_7, IccTestDataPrimitives.UInt64_8, - IccTestDataPrimitives.UInt64_9 - ); + IccTestDataPrimitives.UInt64_9); public static readonly object[][] UInt64TestData = { new object[] { UInt64_Arr, UInt64_Val } }; - - #endregion } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs index 334ee026d..837674e70 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataCurves.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -8,79 +8,64 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataCurves { - #region Response - +#pragma warning disable SA1118 // Parameter should not span multiple lines /// /// Channels: 3 /// - public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve - ( + public static readonly IccResponseCurve Response_ValGrad = new IccResponseCurve( IccCurveMeasurementEncodings.StatusA, - new Vector3[] + new[] { IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccTestDataNonPrimitives.XyzNumber_ValVar3, + IccTestDataNonPrimitives.XyzNumber_ValVar3 }, new IccResponseNumber[][] { new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val1, IccTestDataNonPrimitives.ResponseNumber_Val2 }, new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val3, IccTestDataNonPrimitives.ResponseNumber_Val4 }, new IccResponseNumber[] { IccTestDataNonPrimitives.ResponseNumber_Val5, IccTestDataNonPrimitives.ResponseNumber_Val6 }, - } - ); + }); +#pragma warning restore SA1118 // Parameter should not span multiple lines /// /// Channels: 3 /// - public static readonly byte[] Response_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Response_Grad = ArrayHelper.Concat( new byte[] { 0x53, 0x74, 0x61, 0x41 }, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UInt32_2, - IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, IccTestDataNonPrimitives.XyzNumber_Var3, - IccTestDataNonPrimitives.ResponseNumber_1, IccTestDataNonPrimitives.ResponseNumber_2, - IccTestDataNonPrimitives.ResponseNumber_3, IccTestDataNonPrimitives.ResponseNumber_4, - IccTestDataNonPrimitives.ResponseNumber_5, - IccTestDataNonPrimitives.ResponseNumber_6 - ); + IccTestDataNonPrimitives.ResponseNumber_6); public static readonly object[][] ResponseCurveTestData = { new object[] { Response_Grad, Response_ValGrad, 3 }, }; - #endregion - - #region Parametric - public static readonly IccParametricCurve Parametric_ValVar1 = new IccParametricCurve(1); public static readonly IccParametricCurve Parametric_ValVar2 = new IccParametricCurve(1, 2, 3); public static readonly IccParametricCurve Parametric_ValVar3 = new IccParametricCurve(1, 2, 3, 4); public static readonly IccParametricCurve Parametric_ValVar4 = new IccParametricCurve(1, 2, 3, 4, 5); public static readonly IccParametricCurve Parametric_ValVar5 = new IccParametricCurve(1, 2, 3, 4, 5, 6, 7); - public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x00, }, - IccTestDataPrimitives.Fix16_1 - ); + IccTestDataPrimitives.Fix16_1); - public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var2 = ArrayHelper.Concat( new byte[] { 0x00, 0x01, @@ -88,11 +73,9 @@ namespace SixLabors.ImageSharp.Tests }, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3 - ); + IccTestDataPrimitives.Fix16_3); - public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var3 = ArrayHelper.Concat( new byte[] { 0x00, 0x02, @@ -101,11 +84,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3, - IccTestDataPrimitives.Fix16_4 - ); + IccTestDataPrimitives.Fix16_4); - public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var4 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -115,11 +96,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_3, IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_5 - ); + IccTestDataPrimitives.Fix16_5); - public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat - ( + public static readonly byte[] Parametric_Var5 = ArrayHelper.Concat( new byte[] { 0x00, 0x04, @@ -131,8 +110,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Fix16_7 - ); + IccTestDataPrimitives.Fix16_7); public static readonly object[][] ParametricCurveTestData = { @@ -143,16 +121,12 @@ namespace SixLabors.ImageSharp.Tests new object[] { Parametric_Var5, Parametric_ValVar5 }, }; - #endregion - - #region Formula Segment - + // Formula Segment public static readonly IccFormulaCurveElement Formula_ValVar1 = new IccFormulaCurveElement(IccFormulaCurveType.Type1, 1, 2, 3, 4, 0, 0); public static readonly IccFormulaCurveElement Formula_ValVar2 = new IccFormulaCurveElement(IccFormulaCurveType.Type2, 1, 2, 3, 4, 5, 0); public static readonly IccFormulaCurveElement Formula_ValVar3 = new IccFormulaCurveElement(IccFormulaCurveType.Type3, 0, 2, 3, 4, 5, 6); - public static readonly byte[] Formula_Var1 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, @@ -161,11 +135,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4 - ); + IccTestDataPrimitives.Single_4); - public static readonly byte[] Formula_Var2 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var2 = ArrayHelper.Concat( new byte[] { 0x00, 0x01, @@ -175,11 +147,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_5 - ); + IccTestDataPrimitives.Single_5); - public static readonly byte[] Formula_Var3 = ArrayHelper.Concat - ( + public static readonly byte[] Formula_Var3 = ArrayHelper.Concat( new byte[] { 0x00, 0x02, @@ -189,8 +159,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, - IccTestDataPrimitives.Single_6 - ); + IccTestDataPrimitives.Single_6); public static readonly object[][] FormulaCurveSegmentTestData = { @@ -199,17 +168,12 @@ namespace SixLabors.ImageSharp.Tests new object[] { Formula_Var3, Formula_ValVar3 }, }; - #endregion - - #region Sampled Segment - + // Sampled Segment public static readonly IccSampledCurveElement Sampled_ValGrad1 = new IccSampledCurveElement(new float[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }); public static readonly IccSampledCurveElement Sampled_ValGrad2 = new IccSampledCurveElement(new float[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }); - public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat - ( + public static readonly byte[] Sampled_Grad1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, @@ -218,13 +182,10 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_6, IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, - IccTestDataPrimitives.Single_9 - ); + IccTestDataPrimitives.Single_9); - public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat - ( + public static readonly byte[] Sampled_Grad2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_9, - IccTestDataPrimitives.Single_9, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_7, @@ -233,8 +194,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_2, - IccTestDataPrimitives.Single_1 - ); + IccTestDataPrimitives.Single_1); public static readonly object[][] SampledCurveSegmentTestData = { @@ -242,65 +202,51 @@ namespace SixLabors.ImageSharp.Tests new object[] { Sampled_Grad2, Sampled_ValGrad2 }, }; - #endregion - - #region Segment - public static readonly IccCurveSegment Segment_ValFormula1 = Formula_ValVar1; public static readonly IccCurveSegment Segment_ValFormula2 = Formula_ValVar2; public static readonly IccCurveSegment Segment_ValFormula3 = Formula_ValVar3; public static readonly IccCurveSegment Segment_ValSampled1 = Sampled_ValGrad1; public static readonly IccCurveSegment Segment_ValSampled2 = Sampled_ValGrad2; - public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula1 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var1 - ); + Formula_Var1); - public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula2 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var2 - ); + Formula_Var2); - public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Formula3 = ArrayHelper.Concat( new byte[] { 0x70, 0x61, 0x72, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Formula_Var3 - ); + Formula_Var3); - public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Sampled1 = ArrayHelper.Concat( new byte[] { 0x73, 0x61, 0x6D, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Sampled_Grad1 - ); + Sampled_Grad1); - public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat - ( + public static readonly byte[] Segment_Sampled2 = ArrayHelper.Concat( new byte[] { 0x73, 0x61, 0x6D, 0x66, 0x00, 0x00, 0x00, 0x00, }, - Sampled_Grad2 - ); + Sampled_Grad2); public static readonly object[][] CurveSegmentTestData = { @@ -311,28 +257,19 @@ namespace SixLabors.ImageSharp.Tests new object[] { Segment_Sampled2, Segment_ValSampled2 }, }; - #endregion - - #region One Dimensional - - public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve - ( + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula1 = new IccOneDimensionalCurve( new float[] { 0, 1 }, - new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 } - ); - public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve - ( + new IccCurveSegment[] { Segment_ValFormula1, Segment_ValFormula2, Segment_ValFormula3 }); + + public static readonly IccOneDimensionalCurve OneDimensional_ValFormula2 = new IccOneDimensionalCurve( new float[] { 0, 1 }, - new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 } - ); - public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve - ( + new IccCurveSegment[] { Segment_ValFormula3, Segment_ValFormula2, Segment_ValFormula1 }); + + public static readonly IccOneDimensionalCurve OneDimensional_ValSampled = new IccOneDimensionalCurve( new float[] { 0, 1 }, - new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 } - ); + new IccCurveSegment[] { Segment_ValSampled1, Segment_ValSampled2, Segment_ValSampled1 }); - public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Formula1 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -342,11 +279,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Formula1, Segment_Formula2, - Segment_Formula3 - ); + Segment_Formula3); - public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Formula2 = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -356,11 +291,9 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Formula3, Segment_Formula2, - Segment_Formula1 - ); + Segment_Formula1); - public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat - ( + public static readonly byte[] OneDimensional_Sampled = ArrayHelper.Concat( new byte[] { 0x00, 0x03, @@ -370,8 +303,7 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.Single_1, Segment_Sampled1, Segment_Sampled2, - Segment_Sampled1 - ); + Segment_Sampled1); public static readonly object[][] OneDimensionalCurveTestData = { @@ -379,7 +311,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { OneDimensional_Formula2, OneDimensional_ValFormula2 }, new object[] { OneDimensional_Sampled, OneDimensional_ValSampled }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs index 5ef2156c7..31f368cec 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataLut.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -7,22 +7,28 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataLut { - #region LUT8 - public static readonly IccLut LUT8_ValGrad = CreateLUT8Val(); public static readonly byte[] LUT8_Grad = CreateLUT8(); private static IccLut CreateLUT8Val() { float[] result = new float[256]; - for (int i = 0; i < 256; i++) { result[i] = i / 255f; } + for (int i = 0; i < 256; i++) + { + result[i] = i / 255f; + } + return new IccLut(result); } private static byte[] CreateLUT8() { byte[] result = new byte[256]; - for (int i = 0; i < 256; i++) { result[i] = (byte)i; } + for (int i = 0; i < 256; i++) + { + result[i] = (byte)i; + } + return result; } @@ -31,10 +37,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { LUT8_Grad, LUT8_ValGrad }, }; - #endregion - - #region LUT16 - public static readonly IccLut LUT16_ValGrad = new IccLut(new float[] { 1f / ushort.MaxValue, @@ -50,8 +52,7 @@ namespace SixLabors.ImageSharp.Tests 1f }); - public static readonly byte[] LUT16_Grad = ArrayHelper.Concat - ( + public static readonly byte[] LUT16_Grad = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_3, @@ -62,20 +63,14 @@ namespace SixLabors.ImageSharp.Tests IccTestDataPrimitives.UInt16_8, IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.UInt16_32768, - IccTestDataPrimitives.UInt16_Max - ); + IccTestDataPrimitives.UInt16_Max); public static readonly object[][] Lut16TestData = { new object[] { LUT16_Grad, LUT16_ValGrad, 11 }, }; - #endregion - - #region CLUT8 - - public static readonly IccClut CLUT8_ValGrad = new IccClut - ( + public static readonly IccClut CLUT8_ValGrad = new IccClut( new float[][] { new float[] { 1f / byte.MaxValue, 2f / byte.MaxValue, 3f / byte.MaxValue }, @@ -90,8 +85,8 @@ namespace SixLabors.ImageSharp.Tests new float[] { 22f / byte.MaxValue, 23f / byte.MaxValue, 24f / byte.MaxValue }, new float[] { 25f / byte.MaxValue, 26f / byte.MaxValue, 27f / byte.MaxValue }, }, - new byte[] { 3, 3 }, IccClutDataType.UInt8 - ); + new byte[] { 3, 3 }, + IccClutDataType.UInt8); /// /// Input Channel Count: 2 @@ -118,12 +113,7 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT8_Grad, CLUT8_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUT16 - - public static readonly IccClut CLUT16_ValGrad = new IccClut - ( + public static readonly IccClut CLUT16_ValGrad = new IccClut( new float[][] { new float[] { 1f / ushort.MaxValue, 2f / ushort.MaxValue, 3f / ushort.MaxValue }, @@ -138,8 +128,8 @@ namespace SixLabors.ImageSharp.Tests new float[] { 22f / ushort.MaxValue, 23f / ushort.MaxValue, 24f / ushort.MaxValue }, new float[] { 25f / ushort.MaxValue, 26f / ushort.MaxValue, 27f / ushort.MaxValue }, }, - new byte[] { 3, 3 }, IccClutDataType.UInt16 - ); + new byte[] { 3, 3 }, + IccClutDataType.UInt16); /// /// Input Channel Count: 2 @@ -166,12 +156,7 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT16_Grad, CLUT16_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUTf32 - - public static readonly IccClut CLUTf32_ValGrad = new IccClut - ( + public static readonly IccClut CLUTf32_ValGrad = new IccClut( new float[][] { new float[] { 1f, 2f, 3f }, @@ -186,61 +171,65 @@ namespace SixLabors.ImageSharp.Tests new float[] { 4f, 5f, 6f }, new float[] { 7f, 8f, 9f }, }, - new byte[] { 3, 3 }, IccClutDataType.Float - ); + new byte[] { 3, 3 }, + IccClutDataType.Float); /// /// Input Channel Count: 2 /// Output Channel Count: 3 /// Grid-point Count: { 3, 3 } /// - public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat - ( - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, - - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9, - - IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_3, - IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_7, IccTestDataPrimitives.Single_8, IccTestDataPrimitives.Single_9 - ); + public static readonly byte[] CLUTf32_Grad = ArrayHelper.Concat( + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9, + IccTestDataPrimitives.Single_1, + IccTestDataPrimitives.Single_2, + IccTestDataPrimitives.Single_3, + IccTestDataPrimitives.Single_4, + IccTestDataPrimitives.Single_5, + IccTestDataPrimitives.Single_6, + IccTestDataPrimitives.Single_7, + IccTestDataPrimitives.Single_8, + IccTestDataPrimitives.Single_9); public static readonly object[][] ClutF32TestData = { new object[] { CLUTf32_Grad, CLUTf32_ValGrad, 2, 3, new byte[] { 3, 3 } }, }; - #endregion - - #region CLUT - public static readonly IccClut CLUT_Val8 = CLUT8_ValGrad; public static readonly IccClut CLUT_Val16 = CLUT16_ValGrad; public static readonly IccClut CLUT_Valf32 = CLUTf32_ValGrad; - public static readonly byte[] CLUT_8 = ArrayHelper.Concat - ( + public static readonly byte[] CLUT_8 = ArrayHelper.Concat( new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[4] { 0x01, 0x00, 0x00, 0x00 }, - CLUT8_Grad - ); + CLUT8_Grad); - public static readonly byte[] CLUT_16 = ArrayHelper.Concat - ( + public static readonly byte[] CLUT_16 = ArrayHelper.Concat( new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new byte[4] { 0x02, 0x00, 0x00, 0x00 }, - CLUT16_Grad - ); + CLUT16_Grad); - public static readonly byte[] CLUT_f32 = ArrayHelper.Concat - ( + public static readonly byte[] CLUT_f32 = ArrayHelper.Concat( new byte[16] { 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, - CLUTf32_Grad - ); + CLUTf32_Grad); public static readonly object[][] ClutTestData = { @@ -248,7 +237,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUT_16, CLUT_Val16, 2, 3, false }, new object[] { CLUT_f32, CLUT_Valf32, 2, 3, true }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs index c8ddc8f75..3bc787b34 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMatrix.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -9,8 +9,6 @@ namespace SixLabors.ImageSharp.Tests internal static class IccTestDataMatrix { - #region 2D - /// /// 3x3 Matrix /// @@ -20,6 +18,7 @@ namespace SixLabors.ImageSharp.Tests { 4, 5, 6 }, { 7, 8, 9 }, }; + /// /// 3x3 Matrix /// @@ -53,56 +52,44 @@ namespace SixLabors.ImageSharp.Tests /// /// 3x3 Matrix /// - public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_2D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_7, - IccTestDataPrimitives.Fix16_2, IccTestDataPrimitives.Fix16_5, IccTestDataPrimitives.Fix16_8, - IccTestDataPrimitives.Fix16_3, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Fix16_9 - ); + IccTestDataPrimitives.Fix16_9); /// /// 3x3 Matrix /// - public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_2D_Identity = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, - IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_0, - IccTestDataPrimitives.Fix16_0, IccTestDataPrimitives.Fix16_0, - IccTestDataPrimitives.Fix16_1 - ); + IccTestDataPrimitives.Fix16_1); /// /// 3x3 Matrix /// - public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Single_2D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_4, IccTestDataPrimitives.Single_7, - IccTestDataPrimitives.Single_2, IccTestDataPrimitives.Single_5, IccTestDataPrimitives.Single_8, - IccTestDataPrimitives.Single_3, IccTestDataPrimitives.Single_6, - IccTestDataPrimitives.Single_9 - ); + IccTestDataPrimitives.Single_9); public static readonly object[][] Matrix2D_FloatArrayTestData = { @@ -125,14 +112,11 @@ namespace SixLabors.ImageSharp.Tests new object[] { Single_2D_Grad, 3, 3, true, Single_Matrix4x4_ValGrad }, }; - #endregion - - #region 1D - /// /// 3x1 Matrix /// public static readonly float[] Single_1DArray_ValGrad = { 1, 4, 7 }; + /// /// 3x1 Matrix /// @@ -141,22 +125,18 @@ namespace SixLabors.ImageSharp.Tests /// /// 3x1 Matrix /// - public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Fix16_1D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_4, - IccTestDataPrimitives.Fix16_7 - ); + IccTestDataPrimitives.Fix16_7); /// /// 3x1 Matrix /// - public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat - ( + public static readonly byte[] Single_1D_Grad = ArrayHelper.Concat( IccTestDataPrimitives.Single_1, IccTestDataPrimitives.Single_4, - IccTestDataPrimitives.Single_7 - ); + IccTestDataPrimitives.Single_7); public static readonly object[][] Matrix1D_ArrayTestData = { @@ -169,7 +149,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { Fix16_1D_Grad, 3, false, Single_Vector3_ValGrad }, new object[] { Single_1D_Grad, 3, true, Single_Vector3_ValGrad }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs index 586e84680..d7f9dd877 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataMultiProcessElements.cs @@ -1,14 +1,12 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Tests { - internal static class IccTestDataMultiProcessElement + internal static class IccTestDataMultiProcessElements { - #region CurveSet - /// /// Input Channel Count: 3 /// Output Channel Count: 3 @@ -19,60 +17,48 @@ namespace SixLabors.ImageSharp.Tests IccTestDataCurves.OneDimensional_ValFormula2, IccTestDataCurves.OneDimensional_ValFormula1 }); + /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat - ( + public static readonly byte[] CurvePE_Grad = ArrayHelper.Concat( IccTestDataCurves.OneDimensional_Formula1, IccTestDataCurves.OneDimensional_Formula2, - IccTestDataCurves.OneDimensional_Formula1 - ); + IccTestDataCurves.OneDimensional_Formula1); public static readonly object[][] CurveSetTestData = { new object[] { CurvePE_Grad, CurvePE_ValGrad, 3, 3 }, }; - #endregion - - #region Matrix - /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement - ( + public static readonly IccMatrixProcessElement MatrixPE_ValGrad = new IccMatrixProcessElement( IccTestDataMatrix.Single_2DArray_ValGrad, - IccTestDataMatrix.Single_1DArray_ValGrad - ); + IccTestDataMatrix.Single_1DArray_ValGrad); + /// /// Input Channel Count: 3 /// Output Channel Count: 3 /// - public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat - ( + public static readonly byte[] MatrixPE_Grad = ArrayHelper.Concat( IccTestDataMatrix.Single_2D_Grad, - IccTestDataMatrix.Single_1D_Grad - ); + IccTestDataMatrix.Single_1D_Grad); public static readonly object[][] MatrixTestData = { new object[] { MatrixPE_Grad, MatrixPE_ValGrad, 3, 3 }, }; - - #endregion - - #region CLUT - /// /// Input Channel Count: 2 /// Output Channel Count: 3 /// public static readonly IccClutProcessElement CLUTPE_ValGrad = new IccClutProcessElement(IccTestDataLut.CLUT_Valf32); + /// /// Input Channel Count: 2 /// Output Channel Count: 3 @@ -84,48 +70,38 @@ namespace SixLabors.ImageSharp.Tests new object[] { CLUTPE_Grad, CLUTPE_ValGrad, 2, 3 }, }; - #endregion - - #region MultiProcessElement - public static readonly IccMultiProcessElement MPE_ValMatrix = MatrixPE_ValGrad; public static readonly IccMultiProcessElement MPE_ValCLUT = CLUTPE_ValGrad; public static readonly IccMultiProcessElement MPE_ValCurve = CurvePE_ValGrad; public static readonly IccMultiProcessElement MPE_ValbACS = new IccBAcsProcessElement(3, 3); public static readonly IccMultiProcessElement MPE_ValeACS = new IccEAcsProcessElement(3, 3); - public static readonly byte[] MPE_Matrix = ArrayHelper.Concat - ( + public static readonly byte[] MPE_Matrix = ArrayHelper.Concat( new byte[] { 0x6D, 0x61, 0x74, 0x66, 0x00, 0x03, 0x00, 0x03, }, - MatrixPE_Grad - ); + MatrixPE_Grad); - public static readonly byte[] MPE_CLUT = ArrayHelper.Concat - ( + public static readonly byte[] MPE_CLUT = ArrayHelper.Concat( new byte[] { 0x63, 0x6C, 0x75, 0x74, 0x00, 0x02, 0x00, 0x03, }, - CLUTPE_Grad - ); + CLUTPE_Grad); - public static readonly byte[] MPE_Curve = ArrayHelper.Concat - ( + public static readonly byte[] MPE_Curve = ArrayHelper.Concat( new byte[] { 0x6D, 0x66, 0x6C, 0x74, 0x00, 0x03, 0x00, 0x03, }, - CurvePE_Grad - ); + CurvePE_Grad); public static readonly byte[] MPE_bACS = { @@ -151,7 +127,5 @@ namespace SixLabors.ImageSharp.Tests new object[] { MPE_bACS, MPE_ValbACS }, new object[] { MPE_eACS, MPE_ValeACS }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs index 44af42347..91f81cb43 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataNonPrimitives.cs @@ -10,8 +10,6 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataNonPrimitives { - #region DateTime - public static readonly DateTime DateTime_ValMin = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc); public static readonly DateTime DateTime_ValMax = new DateTime(9999, 12, 31, 23, 59, 59, DateTimeKind.Utc); public static readonly DateTime DateTime_ValRand1 = new DateTime(1990, 11, 26, 3, 19, 47, DateTimeKind.Utc); @@ -63,10 +61,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { DateTime_Rand1, DateTime_ValRand1 }, }; - #endregion - - #region VersionNumber - public static readonly IccVersion VersionNumber_ValMin = new IccVersion(0, 0, 0); public static readonly IccVersion VersionNumber_Val211 = new IccVersion(2, 1, 1); public static readonly IccVersion VersionNumber_Val430 = new IccVersion(4, 3, 0); @@ -85,10 +79,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { VersionNumber_Max, VersionNumber_ValMax }, }; - #endregion - - #region XyzNumber - public static readonly Vector3 XyzNumber_ValMin = new Vector3(IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin, IccTestDataPrimitives.Fix16_ValMin); public static readonly Vector3 XyzNumber_Val0 = new Vector3(0, 0, 0); public static readonly Vector3 XyzNumber_Val1 = new Vector3(1, 1, 1); @@ -113,10 +103,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { XyzNumber_Max, XyzNumber_ValMax }, }; - #endregion - - #region ProfileId - public static readonly IccProfileId ProfileId_ValMin = new IccProfileId(0, 0, 0, 0); public static readonly IccProfileId ProfileId_ValRand = new IccProfileId(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2, IccTestDataPrimitives.UInt32_ValRand3, IccTestDataPrimitives.UInt32_ValRand4); public static readonly IccProfileId ProfileId_ValMax = new IccProfileId(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); @@ -132,10 +118,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { ProfileId_Max, ProfileId_ValMax }, }; - #endregion - - #region PositionNumber - public static readonly IccPositionNumber PositionNumber_ValMin = new IccPositionNumber(0, 0); public static readonly IccPositionNumber PositionNumber_ValRand = new IccPositionNumber(IccTestDataPrimitives.UInt32_ValRand1, IccTestDataPrimitives.UInt32_ValRand2); public static readonly IccPositionNumber PositionNumber_ValMax = new IccPositionNumber(uint.MaxValue, uint.MaxValue); @@ -151,10 +133,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { PositionNumber_Max, PositionNumber_ValMax }, }; - #endregion - - #region ResponseNumber - public static readonly IccResponseNumber ResponseNumber_ValMin = new IccResponseNumber(0, IccTestDataPrimitives.Fix16_ValMin); public static readonly IccResponseNumber ResponseNumber_Val1 = new IccResponseNumber(1, 1); public static readonly IccResponseNumber ResponseNumber_Val2 = new IccResponseNumber(2, 2); @@ -187,43 +165,48 @@ namespace SixLabors.ImageSharp.Tests new object[] { ResponseNumber_Max, ResponseNumber_ValMax }, }; - #endregion - - #region NamedColor - - public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor - ( + public static readonly IccNamedColor NamedColor_ValMin = new IccNamedColor( ArrayHelper.Fill('A', 31), new ushort[] { 0, 0, 0 }, - new ushort[] { 0, 0, 0 } - ); - public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor - ( + new ushort[] { 0, 0, 0 }); + + public static readonly IccNamedColor NamedColor_ValRand = new IccNamedColor( ArrayHelper.Fill('5', 31), new ushort[] { 10794, 10794, 10794 }, - new ushort[] { 17219, 17219, 17219, 17219, 17219 } - ); - public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor - ( + new ushort[] { 17219, 17219, 17219, 17219, 17219 }); + + public static readonly IccNamedColor NamedColor_ValMax = new IccNamedColor( ArrayHelper.Fill('4', 31), new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }, - new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue } - ); + new ushort[] { ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue }); public static readonly byte[] NamedColor_Min = CreateNamedColor(3, 0x41, 0x00, 0x00); public static readonly byte[] NamedColor_Rand = CreateNamedColor(5, 0x35, 42, 67); public static readonly byte[] NamedColor_Max = CreateNamedColor(4, 0x34, 0xFF, 0xFF); - private static byte[] CreateNamedColor(int devCoordCount, byte name, byte PCS, byte device) + private static byte[] CreateNamedColor(int devCoordCount, byte name, byte pCS, byte device) { - byte[] data = new byte[32 + 6 + devCoordCount * 2]; + byte[] data = new byte[32 + 6 + (devCoordCount * 2)]; for (int i = 0; i < data.Length; i++) { - if (i < 31) { data[i] = name; } // Name - else if (i == 31) { data[i] = 0x00; } // Name null terminator - else if (i < 32 + 6) { data[i] = PCS; } // PCS Coordinates - else { data[i] = device; } // Device Coordinates + if (i < 31) + { + data[i] = name; // Name + } + else if (i is 31) + { + data[i] = 0x00; // Name null terminator + } + else if (i < 32 + 6) + { + data[i] = pCS; // PCS Coordinates + } + else + { + data[i] = device; // Device Coordinates + } } + return data; } @@ -234,10 +217,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { NamedColor_Max, NamedColor_ValMax, 4u }, }; - #endregion - - #region ProfileDescription - private static readonly CultureInfo CultureEnUs = new CultureInfo("en-US"); private static readonly CultureInfo CultureDeAT = new CultureInfo("de-AT"); @@ -251,91 +230,76 @@ namespace SixLabors.ImageSharp.Tests }; private static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr1); - private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat - ( + private static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'d', (byte)'e', (byte)'A', (byte)'T' }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); - - public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry - ( - IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), - 1701729619, 2 - ); - public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat - ( + IccTestDataPrimitives.Unicode_Rand3); + + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand, + IccTestDataPrimitives.Unicode_ValRand1, + ArrayHelper.Fill('A', 66), + 1701729619, + 2); + + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 IccTestDataPrimitives.Ascii_Rand, new byte[] { 0x00 }, // Null terminator - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x07 }, // 7 IccTestDataPrimitives.Unicode_Rand2, new byte[] { 0x00, 0x00 }, // Null terminator - new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 } // Null terminator - ); + new byte[] { 0x00 }); // Null terminator - public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription - ( - 1, 2, + public static readonly IccProfileDescription ProfileDescription_ValRand1 = new IccProfileDescription( + 1, + 2, IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, IccProfileTag.ProfileDescription, MultiLocalizedUnicode_Val.Texts, - MultiLocalizedUnicode_Val.Texts - ); + MultiLocalizedUnicode_Val.Texts); - public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription - ( - 1, 2, + public static readonly IccProfileDescription ProfileDescription_ValRand2 = new IccProfileDescription( + 1, + 2, IccDeviceAttribute.ChromaBlackWhite | IccDeviceAttribute.ReflectivityMatte, IccProfileTag.ProfileDescription, new IccLocalizedString[] { LocalizedString_Rand1 }, - new IccLocalizedString[] { LocalizedString_Rand1 } - ); + new IccLocalizedString[] { LocalizedString_Rand1 }); - public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat - ( + public static readonly byte[] ProfileDescription_Rand1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, MultiLocalizedUnicode_Arr, new byte[] { 0x6D, 0x6C, 0x75, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, - MultiLocalizedUnicode_Arr - ); + MultiLocalizedUnicode_Arr); - public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ProfileDescription_Rand2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, new byte[] { 0, 0, 0, 0, 0, 0, 0, 10 }, new byte[] { 0x64, 0x65, 0x73, 0x63 }, - new byte[] { 0x64, 0x65, 0x73, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, TextDescription_Arr1, new byte[] { 0x64, 0x65, 0x73, 0x63 }, new byte[] { 0x00, 0x00, 0x00, 0x00 }, - TextDescription_Arr1 - ); + TextDescription_Arr1); public static readonly object[][] ProfileDescriptionReadTestData = { @@ -348,30 +312,22 @@ namespace SixLabors.ImageSharp.Tests new object[] { ProfileDescription_Rand1, ProfileDescription_ValRand1 }, }; - #endregion - - #region ColorantTableEntry - public static readonly IccColorantTableEntry ColorantTableEntry_ValRand1 = new IccColorantTableEntry(ArrayHelper.Fill('A', 31), 1, 2, 3); public static readonly IccColorantTableEntry ColorantTableEntry_ValRand2 = new IccColorantTableEntry(ArrayHelper.Fill('4', 31), 4, 5, 6); - public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat - ( + public static readonly byte[] ColorantTableEntry_Rand1 = ArrayHelper.Concat( ArrayHelper.Fill((byte)0x41, 31), new byte[1], // null terminator IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); - public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ColorantTableEntry_Rand2 = ArrayHelper.Concat( ArrayHelper.Fill((byte)0x34, 31), new byte[1], // null terminator IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_6 - ); + IccTestDataPrimitives.UInt16_6); public static readonly object[][] ColorantTableEntryTestData = { @@ -379,33 +335,23 @@ namespace SixLabors.ImageSharp.Tests new object[] { ColorantTableEntry_Rand2, ColorantTableEntry_ValRand2 }, }; - #endregion - - #region ScreeningChannel - public static readonly IccScreeningChannel ScreeningChannel_ValRand1 = new IccScreeningChannel(4, 6, IccScreeningSpotType.Cross); public static readonly IccScreeningChannel ScreeningChannel_ValRand2 = new IccScreeningChannel(8, 5, IccScreeningSpotType.Diamond); - public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat - ( + public static readonly byte[] ScreeningChannel_Rand1 = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_4, IccTestDataPrimitives.Fix16_6, - IccTestDataPrimitives.Int32_7 - ); + IccTestDataPrimitives.Int32_7); - public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat - ( + public static readonly byte[] ScreeningChannel_Rand2 = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_8, IccTestDataPrimitives.Fix16_5, - IccTestDataPrimitives.Int32_3 - ); + IccTestDataPrimitives.Int32_3); public static readonly object[][] ScreeningChannelTestData = { new object[] { ScreeningChannel_Rand1, ScreeningChannel_ValRand1 }, new object[] { ScreeningChannel_Rand2, ScreeningChannel_ValRand2 }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs index b24e3f24a..c7b856a60 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataPrimitives.cs @@ -1,12 +1,10 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataPrimitives { - #region UInt16 - public static readonly byte[] UInt16_0 = { 0x00, 0x00 }; public static readonly byte[] UInt16_1 = { 0x00, 0x01 }; public static readonly byte[] UInt16_2 = { 0x00, 0x02 }; @@ -20,10 +18,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt16_32768 = { 0x80, 0x00 }; public static readonly byte[] UInt16_Max = { 0xFF, 0xFF }; - #endregion - - #region Int16 - public static readonly byte[] Int16_Min = { 0x80, 0x00 }; public static readonly byte[] Int16_0 = { 0x00, 0x00 }; public static readonly byte[] Int16_1 = { 0x00, 0x01 }; @@ -37,10 +31,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Int16_9 = { 0x00, 0x09 }; public static readonly byte[] Int16_Max = { 0x7F, 0xFF }; - #endregion - - #region UInt32 - public static readonly byte[] UInt32_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] UInt32_1 = { 0x00, 0x00, 0x00, 0x01 }; public static readonly byte[] UInt32_2 = { 0x00, 0x00, 0x00, 0x02 }; @@ -53,7 +43,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt32_9 = { 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] UInt32_Max = { 0xFF, 0xFF, 0xFF, 0xFF }; - public static readonly uint UInt32_ValRand1 = 1749014123; public static readonly uint UInt32_ValRand2 = 3870560989; public static readonly uint UInt32_ValRand3 = 1050090334; @@ -64,10 +53,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt32_Rand3 = { 0x3E, 0x97, 0x1B, 0x5E }; public static readonly byte[] UInt32_Rand4 = { 0xD3, 0x9C, 0x8F, 0x4A }; - #endregion - - #region Int32 - public static readonly byte[] Int32_Min = { 0x80, 0x00, 0x00, 0x00 }; public static readonly byte[] Int32_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Int32_1 = { 0x00, 0x00, 0x00, 0x01 }; @@ -81,10 +66,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Int32_9 = { 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] Int32_Max = { 0x7F, 0xFF, 0xFF, 0xFF }; - #endregion - - #region UInt64 - public static readonly byte[] UInt64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] UInt64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; public static readonly byte[] UInt64_2 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; @@ -97,10 +78,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] UInt64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] UInt64_Max = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - #endregion - - #region Int64 - public static readonly byte[] Int64_Min = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Int64_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Int64_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; @@ -114,10 +91,6 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Int64_9 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09 }; public static readonly byte[] Int64_Max = { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - #endregion - - #region Single - public static readonly byte[] Single_Min = { 0xFF, 0x7F, 0xFF, 0xFF }; public static readonly byte[] Single_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Single_1 = { 0x3F, 0x80, 0x00, 0x00 }; @@ -131,21 +104,13 @@ namespace SixLabors.ImageSharp.Tests public static readonly byte[] Single_9 = { 0x41, 0x10, 0x00, 0x00 }; public static readonly byte[] Single_Max = { 0x7F, 0x7F, 0xFF, 0xFF }; - #endregion - - #region Double - public static readonly byte[] Double_Min = { 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; public static readonly byte[] Double_0 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Double_1 = { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] Double_Max = { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - #endregion - - #region Fix16 - public const float Fix16_ValMin = short.MinValue; - public const float Fix16_ValMax = short.MaxValue + 65535f / 65536f; + public const float Fix16_ValMax = short.MaxValue + (65535f / 65536f); public static readonly byte[] Fix16_Min = { 0x80, 0x00, 0x00, 0x00 }; public static readonly byte[] Fix16_0 = { 0x00, 0x00, 0x00, 0x00 }; @@ -168,12 +133,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { Fix16_Max, Fix16_ValMax }, }; - #endregion - - #region UFix16 - public const float UFix16_ValMin = 0; - public const float UFix16_ValMax = ushort.MaxValue + 65535f / 65536f; + public const float UFix16_ValMax = ushort.MaxValue + (65535f / 65536f); public static readonly byte[] UFix16_0 = { 0x00, 0x00, 0x00, 0x00 }; public static readonly byte[] UFix16_1 = { 0x00, 0x01, 0x00, 0x00 }; @@ -194,12 +155,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { UFix16_Max, UFix16_ValMax }, }; - #endregion - - #region U1Fix15 - public const float U1Fix15_ValMin = 0; - public const float U1Fix15_ValMax = 1f + 32767f / 32768f; + public const float U1Fix15_ValMax = 1f + (32767f / 32768f); public static readonly byte[] U1Fix15_0 = { 0x00, 0x00 }; public static readonly byte[] U1Fix15_1 = { 0x80, 0x00 }; @@ -212,12 +169,8 @@ namespace SixLabors.ImageSharp.Tests new object[] { U1Fix15_Max, U1Fix15_ValMax }, }; - #endregion - - #region UFix8 - public const float UFix8_ValMin = 0; - public const float UFix8_ValMax = byte.MaxValue + 255f / 256f; + public const float UFix8_ValMax = byte.MaxValue + (255f / 256f); public static readonly byte[] UFix8_0 = { 0x00, 0x00 }; public static readonly byte[] UFix8_1 = { 0x01, 0x00 }; @@ -238,10 +191,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { UFix8_Max, UFix8_ValMax }, }; - #endregion - - #region ASCII String - public const string Ascii_ValRand = "aBcdEf1234"; public const string Ascii_ValRand1 = "Ecf3a"; public const string Ascii_ValRand2 = "2Bd4c"; @@ -283,10 +232,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Ascii_RandLength4, 4, Ascii_ValRand, false }, }; - #endregion - - #region Unicode String - public const string Unicode_ValRand1 = ".6Abäñ$€β𐐷𤭢"; public const string Unicode_ValRand2 = ".6Abäñ"; public const string Unicode_ValRand3 = "$€β𐐷𤭢"; @@ -324,7 +269,5 @@ namespace SixLabors.ImageSharp.Tests 0xD8, 0x01, 0xDC, 0x37, // 𐐷 0xD8, 0x52, 0xDF, 0x62, // 𤭢 }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs index 49ff19046..671edcfae 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataProfiles.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -78,41 +78,46 @@ namespace SixLabors.ImageSharp.Tests 0x64, 0x63, 0x62, 0x61, // CreatorSignature }, profileId, +#pragma warning disable SA1118 // Parameter should not span multiple lines new byte[] - { + { // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Nr of tag table entries - (byte)(nrOfEntries >> 24), (byte)(nrOfEntries >> 16), (byte)(nrOfEntries >> 8), (byte)nrOfEntries + (byte)(nrOfEntries >> 24), + (byte)(nrOfEntries >> 16), + (byte)(nrOfEntries >> 8), + (byte)nrOfEntries }); +#pragma warning restore SA1118 // Parameter should not span multiple lines } - public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat(CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), + public static readonly byte[] Profile_Random_Array = ArrayHelper.Concat( + CreateHeaderRandomArray(168, 2, Profile_Random_Id_Array), +#pragma warning disable SA1118 // Parameter should not span multiple lines new byte[] { 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) 0x00, 0x00, 0x00, 0x9C, // tag offset (156) 0x00, 0x00, 0x00, 0x0C, // tag size (12) - 0x00, 0x00, 0x00, 0x00, // tag signature (Unknown) 0x00, 0x00, 0x00, 0x9C, // tag offset (156) 0x00, 0x00, 0x00, 0x0C, // tag size (12) }, +#pragma warning restore SA1118 // Parameter should not span multiple lines IccTestDataTagDataEntry.TagDataEntryHeader_UnknownArr, - IccTestDataTagDataEntry.Unknown_Arr - ); + IccTestDataTagDataEntry.Unknown_Arr); - public static readonly IccProfile Profile_Random_Val = new IccProfile(CreateHeaderRandomValue(168, - Profile_Random_Id_Value, - "acsp"), - new IccTagDataEntry[] - { - IccTestDataTagDataEntry.Unknown_Val, - IccTestDataTagDataEntry.Unknown_Val - }); + public static readonly IccProfile Profile_Random_Val = new IccProfile( + CreateHeaderRandomValue( + 168, + Profile_Random_Id_Value, + "acsp"), + new IccTagDataEntry[] { IccTestDataTagDataEntry.Unknown_Val, IccTestDataTagDataEntry.Unknown_Val }); public static readonly byte[] Header_CorruptDataColorSpace_Array = { @@ -132,8 +137,10 @@ namespace SixLabors.ImageSharp.Tests 0x00, 0x00, 0x00, 0x03, // RenderingIntent 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -159,8 +166,10 @@ namespace SixLabors.ImageSharp.Tests 0x00, 0x00, 0x00, 0x03, // RenderingIntent 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -186,8 +195,10 @@ namespace SixLabors.ImageSharp.Tests 0x33, 0x41, 0x30, 0x6B, // RenderingIntent 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, // PcsIlluminant 0x64, 0x63, 0x62, 0x61, // CreatorSignature + // Profile ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Padding 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -221,4 +232,4 @@ namespace SixLabors.ImageSharp.Tests new object[] { Header_Random_Array, true }, }; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs index b5da22443..bfe7d94b1 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/IccTestDataTagDataEntry.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Globalization; @@ -9,8 +9,6 @@ namespace SixLabors.ImageSharp.Tests { internal static class IccTestDataTagDataEntry { - #region TagDataEntry Header - public static readonly IccTypeSignature TagDataEntryHeader_UnknownVal = IccTypeSignature.Unknown; public static readonly byte[] TagDataEntryHeader_UnknownArr = { @@ -39,10 +37,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { TagDataEntryHeader_CurveArr, TagDataEntryHeader_CurveVal }, }; - #endregion - - #region UnknownTagDataEntry - public static readonly IccUnknownTagDataEntry Unknown_Val = new IccUnknownTagDataEntry(new byte[] { 0x00, 0x01, 0x02, 0x03 }); public static readonly byte[] Unknown_Arr = { 0x00, 0x01, 0x02, 0x03 }; @@ -51,63 +45,45 @@ namespace SixLabors.ImageSharp.Tests new object[] { Unknown_Arr, Unknown_Val, 12u }, }; - #endregion - - #region ChromaticityTagDataEntry - public static readonly IccChromaticityTagDataEntry Chromaticity_Val1 = new IccChromaticityTagDataEntry(IccColorantEncoding.ItuRBt709_2); - public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_Arr1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt16_1, - new byte[] { 0x00, 0x00, 0xA3, 0xD7 }, // 0.640 new byte[] { 0x00, 0x00, 0x54, 0x7B }, // 0.330 - new byte[] { 0x00, 0x00, 0x4C, 0xCD }, // 0.300 new byte[] { 0x00, 0x00, 0x99, 0x9A }, // 0.600 - new byte[] { 0x00, 0x00, 0x26, 0x66 }, // 0.150 - new byte[] { 0x00, 0x00, 0x0F, 0x5C } // 0.060 - ); + new byte[] { 0x00, 0x00, 0x0F, 0x5C }); // 0.060 - public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry - ( + public static readonly IccChromaticityTagDataEntry Chromaticity_Val2 = new IccChromaticityTagDataEntry( new double[][] { new double[] { 1, 2 }, new double[] { 3, 4 }, - } - ); - public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat - ( + }); + + public static readonly byte[] Chromaticity_Arr2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_0, - IccTestDataPrimitives.UFix16_1, IccTestDataPrimitives.UFix16_2, - IccTestDataPrimitives.UFix16_3, - IccTestDataPrimitives.UFix16_4 - ); + IccTestDataPrimitives.UFix16_4); /// /// : channel count must be 3 for any enum other than /// - public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_ArrInvalid1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.UInt16_1 - ); + IccTestDataPrimitives.UInt16_1); /// /// : invalid enum value /// - public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat - ( + public static readonly byte[] Chromaticity_ArrInvalid2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, - IccTestDataPrimitives.UInt16_9 - ); + IccTestDataPrimitives.UInt16_9); public static readonly object[][] ChromaticityTagDataEntryTestData = { @@ -115,10 +91,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Chromaticity_Arr2, Chromaticity_Val2 }, }; - #endregion - - #region ColorantOrderTagDataEntry - public static readonly IccColorantOrderTagDataEntry ColorantOrder_Val = new IccColorantOrderTagDataEntry(new byte[] { 0x00, 0x01, 0x02 }); public static readonly byte[] ColorantOrder_Arr = ArrayHelper.Concat(IccTestDataPrimitives.UInt32_3, new byte[] { 0x00, 0x01, 0x02 }); @@ -127,52 +99,37 @@ namespace SixLabors.ImageSharp.Tests new object[] { ColorantOrder_Arr, ColorantOrder_Val }, }; - #endregion - - #region ColorantTableTagDataEntry - - public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry - ( + public static readonly IccColorantTableTagDataEntry ColorantTable_Val = new IccColorantTableTagDataEntry( new IccColorantTableEntry[] { IccTestDataNonPrimitives.ColorantTableEntry_ValRand1, IccTestDataNonPrimitives.ColorantTableEntry_ValRand2 - } - ); - public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ColorantTable_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ColorantTableEntry_Rand1, - IccTestDataNonPrimitives.ColorantTableEntry_Rand2 - ); + IccTestDataNonPrimitives.ColorantTableEntry_Rand2); public static readonly object[][] ColorantTableTagDataEntryTestData = { new object[] { ColorantTable_Arr, ColorantTable_Val }, }; - #endregion - - #region CurveTagDataEntry - public static readonly IccCurveTagDataEntry Curve_Val_0 = new IccCurveTagDataEntry(); public static readonly byte[] Curve_Arr_0 = IccTestDataPrimitives.UInt32_0; public static readonly IccCurveTagDataEntry Curve_Val_1 = new IccCurveTagDataEntry(1f); - public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat - ( + public static readonly byte[] Curve_Arr_1 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, - IccTestDataPrimitives.UFix8_1 - ); + IccTestDataPrimitives.UFix8_1); public static readonly IccCurveTagDataEntry Curve_Val_2 = new IccCurveTagDataEntry(new float[] { 1 / 65535f, 2 / 65535f, 3 / 65535f }); - public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat - ( + public static readonly byte[] Curve_Arr_2 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_3, IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); public static readonly object[][] CurveTagDataEntryTestData = { @@ -181,26 +138,20 @@ namespace SixLabors.ImageSharp.Tests new object[] { Curve_Arr_2, Curve_Val_2 }, }; - #endregion - - #region DataTagDataEntry - - public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry - ( + public static readonly IccDataTagDataEntry Data_ValNoASCII = new IccDataTagDataEntry( new byte[] { 0x01, 0x02, 0x03, 0x04 }, - false - ); + false); + public static readonly byte[] Data_ArrNoASCII = { 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04 }; - public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry - ( + public static readonly IccDataTagDataEntry Data_ValASCII = new IccDataTagDataEntry( new byte[] { (byte)'A', (byte)'S', (byte)'C', (byte)'I', (byte)'I' }, - true - ); + true); + public static readonly byte[] Data_ArrASCII = { 0x00, 0x00, 0x00, 0x01, @@ -213,10 +164,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Data_ArrASCII, Data_ValASCII, 17u }, }; - #endregion - - #region DateTimeTagDataEntry - public static readonly IccDateTimeTagDataEntry DateTime_Val = new IccDateTimeTagDataEntry(IccTestDataNonPrimitives.DateTime_ValRand1); public static readonly byte[] DateTime_Arr = IccTestDataNonPrimitives.DateTime_Rand1; @@ -225,89 +172,60 @@ namespace SixLabors.ImageSharp.Tests new object[] { DateTime_Arr, DateTime_Val }, }; - #endregion - - #region Lut16TagDataEntry - - public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry - ( + public static readonly IccLut16TagDataEntry Lut16_Val = new IccLut16TagDataEntry( new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }, IccTestDataLut.CLUT16_ValGrad, - new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad } - ); - public static readonly byte[] Lut16_Arr = ArrayHelper.Concat - ( + new IccLut[] { IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad, IccTestDataLut.LUT16_ValGrad }); + + public static readonly byte[] Lut16_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x03, 0x00 }, IccTestDataMatrix.Fix16_2D_Identity, new byte[] { 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length, 0x00, (byte)IccTestDataLut.LUT16_ValGrad.Values.Length }, - IccTestDataLut.LUT16_Grad, IccTestDataLut.LUT16_Grad, - IccTestDataLut.CLUT16_Grad, - IccTestDataLut.LUT16_Grad, IccTestDataLut.LUT16_Grad, - IccTestDataLut.LUT16_Grad - ); + IccTestDataLut.LUT16_Grad); public static readonly object[][] Lut16TagDataEntryTestData = { new object[] { Lut16_Arr, Lut16_Val }, }; - #endregion - - #region Lut8TagDataEntry - - public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry - ( + public static readonly IccLut8TagDataEntry Lut8_Val = new IccLut8TagDataEntry( new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }, IccTestDataLut.CLUT8_ValGrad, - new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad } - ); - public static readonly byte[] Lut8_Arr = ArrayHelper.Concat - ( + new IccLut[] { IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad, IccTestDataLut.LUT8_ValGrad }); + + public static readonly byte[] Lut8_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x03, 0x00 }, IccTestDataMatrix.Fix16_2D_Identity, - IccTestDataLut.LUT8_Grad, IccTestDataLut.LUT8_Grad, - IccTestDataLut.CLUT8_Grad, - IccTestDataLut.LUT8_Grad, IccTestDataLut.LUT8_Grad, - IccTestDataLut.LUT8_Grad - ); + IccTestDataLut.LUT8_Grad); public static readonly object[][] Lut8TagDataEntryTestData = { new object[] { Lut8_Arr, Lut8_Val }, }; - #endregion - - #region LutAToBTagDataEntry - - private static readonly byte[] CurveFull_0 = ArrayHelper.Concat - ( + private static readonly byte[] CurveFull_0 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_0 - ); - private static readonly byte[] CurveFull_1 = ArrayHelper.Concat - ( + Curve_Arr_0); + + private static readonly byte[] CurveFull_1 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_1 - ); - private static readonly byte[] CurveFull_2 = ArrayHelper.Concat - ( + Curve_Arr_1); + + private static readonly byte[] CurveFull_2 = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, - Curve_Arr_2 - ); + Curve_Arr_2); - public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry - ( + public static readonly IccLutAToBTagDataEntry LutAToB_Val = new IccLutAToBTagDataEntry( new IccCurveTagDataEntry[] { Curve_Val_0, @@ -316,23 +234,13 @@ namespace SixLabors.ImageSharp.Tests }, IccTestDataMatrix.Single_2DArray_ValGrad, IccTestDataMatrix.Single_1DArray_ValGrad, - new IccCurveTagDataEntry[] - { - Curve_Val_1, - Curve_Val_2, - Curve_Val_0, - }, + new IccCurveTagDataEntry[] { Curve_Val_1, Curve_Val_2, Curve_Val_0 }, IccTestDataLut.CLUT_Val16, - new IccCurveTagDataEntry[] - { - Curve_Val_2, - Curve_Val_1, - } - ); - public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat - ( - new byte[] { 0x02, 0x03, 0x00, 0x00 }, + new IccCurveTagDataEntry[] { Curve_Val_2, Curve_Val_1 }); +#pragma warning disable SA1115 // Parameter should follow comma + public static readonly byte[] LutAToB_Arr = ArrayHelper.Concat( + new byte[] { 0x02, 0x03, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 new byte[] { 0x00, 0x00, 0x00, 0x50 }, // matrix: 80 new byte[] { 0x00, 0x00, 0x00, 0x80 }, // m: 128 @@ -365,21 +273,17 @@ namespace SixLabors.ImageSharp.Tests CurveFull_2, // 18 bytes new byte[] { 0x00, 0x00 }, // Padding CurveFull_1, // 14 bytes - new byte[] { 0x00, 0x00 } // Padding - ); + new byte[] { 0x00, 0x00 }); // Padding + +#pragma warning restore SA1115 // Parameter should follow comma public static readonly object[][] LutAToBTagDataEntryTestData = { new object[] { LutAToB_Arr, LutAToB_Val }, }; - #endregion - - #region LutBToATagDataEntry - - public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry - ( - new IccCurveTagDataEntry[] + public static readonly IccLutBToATagDataEntry LutBToA_Val = new IccLutBToATagDataEntry( + new[] { Curve_Val_0, Curve_Val_1, @@ -388,15 +292,10 @@ namespace SixLabors.ImageSharp.Tests null, null, IccTestDataLut.CLUT_Val16, - new IccCurveTagDataEntry[] - { - Curve_Val_2, - Curve_Val_1, - Curve_Val_0, - } - ); - public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat - ( + new[] { Curve_Val_2, Curve_Val_1, Curve_Val_0 }); + +#pragma warning disable SA1115 // Parameter should follow comma + public static readonly byte[] LutBToA_Arr = ArrayHelper.Concat( new byte[] { 0x02, 0x03, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x20 }, // b: 32 @@ -406,8 +305,8 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0x00, 0x00, 0x00, 0x88 }, // a: 136 // B - CurveFull_0, //12 bytes - CurveFull_1, //14 bytes + CurveFull_0, // 12 bytes + CurveFull_1, // 14 bytes new byte[] { 0x00, 0x00 }, // Padding // CLUT @@ -419,41 +318,34 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0x00, 0x00 }, // Padding CurveFull_1, // 14 bytes new byte[] { 0x00, 0x00 }, // Padding - CurveFull_0 // 12 bytes - ); + CurveFull_0); // 12 bytes + +#pragma warning restore SA1115 // Parameter should follow comma public static readonly object[][] LutBToATagDataEntryTestData = { new object[] { LutBToA_Arr, LutBToA_Val }, }; - #endregion - - #region MeasurementTagDataEntry + public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry( + IccStandardObserver.Cie1931Observer, + IccTestDataNonPrimitives.XyzNumber_ValVar1, + IccMeasurementGeometry.Degree0ToDOrDTo0, + 1f, + IccStandardIlluminant.D50); - public static readonly IccMeasurementTagDataEntry Measurement_Val = new IccMeasurementTagDataEntry - ( - IccStandardObserver.Cie1931Observer, IccTestDataNonPrimitives.XyzNumber_ValVar1, - IccMeasurementGeometry.Degree0ToDOrDTo0, 1f, IccStandardIlluminant.D50 - ); - public static readonly byte[] Measurement_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Measurement_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UFix16_1, - IccTestDataPrimitives.UInt32_1 - ); + IccTestDataPrimitives.UInt32_1); public static readonly object[][] MeasurementTagDataEntryTestData = { new object[] { Measurement_Arr, Measurement_Val }, }; - #endregion - - #region MultiLocalizedUnicodeTagDataEntry - private static readonly IccLocalizedString LocalizedString_Rand_enUS = CreateLocalizedString("en", "US", IccTestDataPrimitives.Unicode_ValRand2); private static readonly IccLocalizedString LocalizedString_Rand_deDE = CreateLocalizedString("de", "DE", IccTestDataPrimitives.Unicode_ValRand3); private static readonly IccLocalizedString LocalizedString_Rand2_deDE = CreateLocalizedString("de", "DE", IccTestDataPrimitives.Unicode_ValRand2); @@ -496,11 +388,13 @@ namespace SixLabors.ImageSharp.Tests LocalizedString_Rand_enUS, LocalizedString_Rand_deDE, }; + private static readonly IccLocalizedString[] LocalizedString_RandArr_en_Invariant = new IccLocalizedString[] { LocalizedString_Rand_en, LocalizedString_Rand_Invariant, }; + private static readonly IccLocalizedString[] LocalizedString_SameArr_enUS_deDE_esXL_xyXL = new IccLocalizedString[] { LocalizedString_Rand_enUS, @@ -510,82 +404,60 @@ namespace SixLabors.ImageSharp.Tests }; public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_enUS_deDE); - public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); + IccTestDataPrimitives.Unicode_Rand3); public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val2 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_RandArr_en_Invariant); - public static readonly byte[] MultiLocalizedUnicode_Arr2_Read = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr2_Read = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { 0x00, 0x00, 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); + IccTestDataPrimitives.Unicode_Rand3); - public static readonly byte[] MultiLocalizedUnicode_Arr2_Write = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr2_Write = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x28 }, // 40 - new byte[] { (byte)'x', (byte)'x', 0x00, 0x00 }, new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 new byte[] { 0x00, 0x00, 0x00, 0x34 }, // 52 - IccTestDataPrimitives.Unicode_Rand2, - IccTestDataPrimitives.Unicode_Rand3 - ); + IccTestDataPrimitives.Unicode_Rand3); public static readonly IccMultiLocalizedUnicodeTagDataEntry MultiLocalizedUnicode_Val3 = new IccMultiLocalizedUnicodeTagDataEntry(LocalizedString_SameArr_enUS_deDE_esXL_xyXL); - public static readonly byte[] MultiLocalizedUnicode_Arr3 = ArrayHelper.Concat - ( + public static readonly byte[] MultiLocalizedUnicode_Arr3 = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_4, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 - new byte[] { (byte)'e', (byte)'n', (byte)'U', (byte)'S' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - new byte[] { (byte)'d', (byte)'e', (byte)'D', (byte)'E' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - new byte[] { (byte)'e', (byte)'s', (byte)'X', (byte)'L' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - new byte[] { (byte)'x', (byte)'y', (byte)'X', (byte)'L' }, new byte[] { 0x00, 0x00, 0x00, 0x0C }, // 12 new byte[] { 0x00, 0x00, 0x00, 0x40 }, // 64 - - IccTestDataPrimitives.Unicode_Rand2 - ); + IccTestDataPrimitives.Unicode_Rand2); public static readonly object[][] MultiLocalizedUnicodeTagDataEntryTestData_Read = { @@ -601,55 +473,36 @@ namespace SixLabors.ImageSharp.Tests new object[] { MultiLocalizedUnicode_Arr3, MultiLocalizedUnicode_Val3 }, }; - #endregion - - #region MultiProcessElementsTagDataEntry - - public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry - ( + public static readonly IccMultiProcessElementsTagDataEntry MultiProcessElements_Val = new IccMultiProcessElementsTagDataEntry( new IccMultiProcessElement[] { - IccTestDataMultiProcessElement.MPE_ValCLUT, - IccTestDataMultiProcessElement.MPE_ValCLUT, - } - ); - public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat - ( + IccTestDataMultiProcessElements.MPE_ValCLUT, + IccTestDataMultiProcessElements.MPE_ValCLUT, + }); + + public static readonly byte[] MultiProcessElements_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt32_2, - new byte[] { 0x00, 0x00, 0x00, 0x20 }, // 32 new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - new byte[] { 0x00, 0x00, 0x00, 0xA4 }, // 164 new byte[] { 0x00, 0x00, 0x00, 0x84 }, // 132 - - IccTestDataMultiProcessElement.MPE_CLUT, - IccTestDataMultiProcessElement.MPE_CLUT - ); + IccTestDataMultiProcessElements.MPE_CLUT, + IccTestDataMultiProcessElements.MPE_CLUT); public static readonly object[][] MultiProcessElementsTagDataEntryTestData = { new object[] { MultiProcessElements_Arr, MultiProcessElements_Val }, }; - #endregion - - #region NamedColor2TagDataEntry - - public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry - ( + public static readonly IccNamedColor2TagDataEntry NamedColor2_Val = new IccNamedColor2TagDataEntry( 16909060, - ArrayHelper.Fill('A', 31), ArrayHelper.Fill('4', 31), - new IccNamedColor[] - { - IccTestDataNonPrimitives.NamedColor_ValMin, - IccTestDataNonPrimitives.NamedColor_ValMin - } - ); - public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat - ( + ArrayHelper.Fill('A', 31), + ArrayHelper.Fill('4', 31), + new IccNamedColor[] { IccTestDataNonPrimitives.NamedColor_ValMin, IccTestDataNonPrimitives.NamedColor_ValMin }); + + public static readonly byte[] NamedColor2_Arr = ArrayHelper.Concat( new byte[] { 0x01, 0x02, 0x03, 0x04 }, IccTestDataPrimitives.UInt32_2, IccTestDataPrimitives.UInt32_3, @@ -658,18 +511,13 @@ namespace SixLabors.ImageSharp.Tests ArrayHelper.Fill((byte)0x34, 31), new byte[] { 0x00 }, IccTestDataNonPrimitives.NamedColor_Min, - IccTestDataNonPrimitives.NamedColor_Min - ); + IccTestDataNonPrimitives.NamedColor_Min); public static readonly object[][] NamedColor2TagDataEntryTestData = { new object[] { NamedColor2_Arr, NamedColor2_Val }, }; - #endregion - - #region ParametricCurveTagDataEntry - public static readonly IccParametricCurveTagDataEntry ParametricCurve_Val = new IccParametricCurveTagDataEntry(IccTestDataCurves.Parametric_ValVar1); public static readonly byte[] ParametricCurve_Arr = IccTestDataCurves.Parametric_Var1; @@ -678,118 +526,81 @@ namespace SixLabors.ImageSharp.Tests new object[] { ParametricCurve_Arr, ParametricCurve_Val }, }; - #endregion - - #region ProfileSequenceDescTagDataEntry - - public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry - ( + public static readonly IccProfileSequenceDescTagDataEntry ProfileSequenceDesc_Val = new IccProfileSequenceDescTagDataEntry( new IccProfileDescription[] { IccTestDataNonPrimitives.ProfileDescription_ValRand1, IccTestDataNonPrimitives.ProfileDescription_ValRand1 - } - ); - public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ProfileSequenceDesc_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ProfileDescription_Rand1, - IccTestDataNonPrimitives.ProfileDescription_Rand1 - ); + IccTestDataNonPrimitives.ProfileDescription_Rand1); public static readonly object[][] ProfileSequenceDescTagDataEntryTestData = { new object[] { ProfileSequenceDesc_Arr, ProfileSequenceDesc_Val }, }; - #endregion - - #region ProfileSequenceIdentifierTagDataEntry - - public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry - ( + public static readonly IccProfileSequenceIdentifierTagDataEntry ProfileSequenceIdentifier_Val = new IccProfileSequenceIdentifierTagDataEntry( new IccProfileSequenceIdentifier[] { new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), new IccProfileSequenceIdentifier(IccTestDataNonPrimitives.ProfileId_ValRand, LocalizedString_RandArr_enUS_deDE), - } - ); - public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat - ( - IccTestDataPrimitives.UInt32_2, + }); + public static readonly byte[] ProfileSequenceIdentifier_Arr = ArrayHelper.Concat( + IccTestDataPrimitives.UInt32_2, new byte[] { 0x00, 0x00, 0x00, 0x1C }, // 28 new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 - new byte[] { 0x00, 0x00, 0x00, 0x70 }, // 112 new byte[] { 0x00, 0x00, 0x00, 0x54 }, // 84 - IccTestDataNonPrimitives.ProfileId_Rand, // 16 bytes TagDataEntryHeader_MultiLocalizedUnicodeArr, // 8 bytes MultiLocalizedUnicode_Arr, // 58 bytes new byte[] { 0x00, 0x00 }, // 2 bytes (padding) - IccTestDataNonPrimitives.ProfileId_Rand, TagDataEntryHeader_MultiLocalizedUnicodeArr, MultiLocalizedUnicode_Arr, - new byte[] { 0x00, 0x00 } - ); + new byte[] { 0x00, 0x00 }); public static readonly object[][] ProfileSequenceIdentifierTagDataEntryTestData = { new object[] { ProfileSequenceIdentifier_Arr, ProfileSequenceIdentifier_Val }, }; - #endregion - - #region ResponseCurveSet16TagDataEntry - - public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry - ( + public static readonly IccResponseCurveSet16TagDataEntry ResponseCurveSet16_Val = new IccResponseCurveSet16TagDataEntry( new IccResponseCurve[] { IccTestDataCurves.Response_ValGrad, IccTestDataCurves.Response_ValGrad, - } - ); - public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat - ( + }); + + public static readonly byte[] ResponseCurveSet16_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt16_2, - new byte[] { 0x00, 0x00, 0x00, 0x14 }, // 20 new byte[] { 0x00, 0x00, 0x00, 0x6C }, // 108 - IccTestDataCurves.Response_Grad, // 88 bytes - IccTestDataCurves.Response_Grad // 88 bytes - ); + IccTestDataCurves.Response_Grad); // 88 bytes public static readonly object[][] ResponseCurveSet16TagDataEntryTestData = { new object[] { ResponseCurveSet16_Arr, ResponseCurveSet16_Val }, }; - #endregion - - #region Fix16ArrayTagDataEntry - public static readonly IccFix16ArrayTagDataEntry Fix16Array_Val = new IccFix16ArrayTagDataEntry(new float[] { 1 / 256f, 2 / 256f, 3 / 256f }); - public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] Fix16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Fix16_1, IccTestDataPrimitives.Fix16_2, - IccTestDataPrimitives.Fix16_3 - ); + IccTestDataPrimitives.Fix16_3); public static readonly object[][] Fix16ArrayTagDataEntryTestData = { new object[] { Fix16Array_Arr, Fix16Array_Val, 20u }, }; - #endregion - - #region SignatureTagDataEntry - public static readonly IccSignatureTagDataEntry Signature_Val = new IccSignatureTagDataEntry("ABCD"); public static readonly byte[] Signature_Arr = { 0x41, 0x42, 0x43, 0x44, }; @@ -798,10 +609,6 @@ namespace SixLabors.ImageSharp.Tests new object[] { Signature_Arr, Signature_Val }, }; - #endregion - - #region TextTagDataEntry - public static readonly IccTextTagDataEntry Text_Val = new IccTextTagDataEntry("ABCD"); public static readonly byte[] Text_Arr = { 0x41, 0x42, 0x43, 0x44 }; @@ -810,78 +617,50 @@ namespace SixLabors.ImageSharp.Tests new object[] { Text_Arr, Text_Val, 12u }, }; - #endregion - - #region UFix16ArrayTagDataEntry - public static readonly IccUFix16ArrayTagDataEntry UFix16Array_Val = new IccUFix16ArrayTagDataEntry(new float[] { 1, 2, 3 }); - public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UFix16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UFix16_1, IccTestDataPrimitives.UFix16_2, - IccTestDataPrimitives.UFix16_3 - ); + IccTestDataPrimitives.UFix16_3); public static readonly object[][] UFix16ArrayTagDataEntryTestData = { new object[] { UFix16Array_Arr, UFix16Array_Val, 20u }, }; - #endregion - - #region UInt16ArrayTagDataEntry - public static readonly IccUInt16ArrayTagDataEntry UInt16Array_Val = new IccUInt16ArrayTagDataEntry(new ushort[] { 1, 2, 3 }); - public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt16Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt16_1, IccTestDataPrimitives.UInt16_2, - IccTestDataPrimitives.UInt16_3 - ); + IccTestDataPrimitives.UInt16_3); public static readonly object[][] UInt16ArrayTagDataEntryTestData = { new object[] { UInt16Array_Arr, UInt16Array_Val, 14u }, }; - #endregion - - #region UInt32ArrayTagDataEntry - public static readonly IccUInt32ArrayTagDataEntry UInt32Array_Val = new IccUInt32ArrayTagDataEntry(new uint[] { 1, 2, 3 }); - public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt32Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_1, IccTestDataPrimitives.UInt32_2, - IccTestDataPrimitives.UInt32_3 - ); + IccTestDataPrimitives.UInt32_3); public static readonly object[][] UInt32ArrayTagDataEntryTestData = { new object[] { UInt32Array_Arr, UInt32Array_Val, 20u }, }; - #endregion - - #region UInt64ArrayTagDataEntry - public static readonly IccUInt64ArrayTagDataEntry UInt64Array_Val = new IccUInt64ArrayTagDataEntry(new ulong[] { 1, 2, 3 }); - public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat - ( + public static readonly byte[] UInt64Array_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt64_1, IccTestDataPrimitives.UInt64_2, - IccTestDataPrimitives.UInt64_3 - ); + IccTestDataPrimitives.UInt64_3); public static readonly object[][] UInt64ArrayTagDataEntryTestData = { new object[] { UInt64Array_Arr, UInt64Array_Val, 32u }, }; - #endregion - - #region UInt8ArrayTagDataEntry - public static readonly IccUInt8ArrayTagDataEntry UInt8Array_Val = new IccUInt8ArrayTagDataEntry(new byte[] { 1, 2, 3 }); public static readonly byte[] UInt8Array_Arr = { 1, 2, 3 }; @@ -890,88 +669,66 @@ namespace SixLabors.ImageSharp.Tests new object[] { UInt8Array_Arr, UInt8Array_Val, 11u }, }; - #endregion - - #region ViewingConditionsTagDataEntry - - public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry - ( + public static readonly IccViewingConditionsTagDataEntry ViewingConditions_Val = new IccViewingConditionsTagDataEntry( IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, - IccStandardIlluminant.D50 - ); - public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat - ( + IccStandardIlluminant.D50); + + public static readonly byte[] ViewingConditions_Arr = ArrayHelper.Concat( IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataPrimitives.UInt32_1 - ); + IccTestDataPrimitives.UInt32_1); public static readonly object[][] ViewingConditionsTagDataEntryTestData = { new object[] { ViewingConditions_Arr, ViewingConditions_Val }, }; - #endregion - - #region XYZTagDataEntry - public static readonly IccXyzTagDataEntry XYZ_Val = new IccXyzTagDataEntry(new Vector3[] { IccTestDataNonPrimitives.XyzNumber_ValVar1, IccTestDataNonPrimitives.XyzNumber_ValVar2, IccTestDataNonPrimitives.XyzNumber_ValVar3, }); - public static readonly byte[] XYZ_Arr = ArrayHelper.Concat - ( + + public static readonly byte[] XYZ_Arr = ArrayHelper.Concat( IccTestDataNonPrimitives.XyzNumber_Var1, IccTestDataNonPrimitives.XyzNumber_Var2, - IccTestDataNonPrimitives.XyzNumber_Var3 - ); + IccTestDataNonPrimitives.XyzNumber_Var3); public static readonly object[][] XYZTagDataEntryTestData = { new object[] { XYZ_Arr, XYZ_Val, 44u }, }; - #endregion - - #region TextDescriptionTagDataEntry + public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry( + IccTestDataPrimitives.Ascii_ValRand, + IccTestDataPrimitives.Unicode_ValRand1, + ArrayHelper.Fill('A', 66), + 1701729619, + 2); - public static readonly IccTextDescriptionTagDataEntry TextDescription_Val1 = new IccTextDescriptionTagDataEntry - ( - IccTestDataPrimitives.Ascii_ValRand, IccTestDataPrimitives.Unicode_ValRand1, ArrayHelper.Fill('A', 66), - 1701729619, 2 - ); - public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat - ( - new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 + public static readonly byte[] TextDescription_Arr1 = ArrayHelper.Concat( + new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 IccTestDataPrimitives.Ascii_Rand, - new byte[] { 0x00 }, // Null terminator - - new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS - new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 + new byte[] { 0x00 }, // Null terminator + new byte[] { 0x65, 0x6E, 0x55, 0x53 }, // enUS + new byte[] { 0x00, 0x00, 0x00, 0x0E }, // 14 IccTestDataPrimitives.Unicode_Rand1, - new byte[] { 0x00, 0x00 }, // Null terminator - - new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 + new byte[] { 0x00, 0x00 }, // Null terminator + new byte[] { 0x00, 0x02, 0x43 }, // 2, 67 ArrayHelper.Fill((byte)0x41, 66), - new byte[] { 0x00 } // Null terminator - ); + new byte[] { 0x00 }); // Null terminator public static readonly IccTextDescriptionTagDataEntry TextDescription_Val2 = new IccTextDescriptionTagDataEntry(IccTestDataPrimitives.Ascii_ValRand, null, null, 0, 0); - public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat - ( + public static readonly byte[] TextDescription_Arr2 = ArrayHelper.Concat( new byte[] { 0x00, 0x00, 0x00, 0x0B }, // 11 IccTestDataPrimitives.Ascii_Rand, new byte[] { 0x00 }, // Null terminator - IccTestDataPrimitives.UInt32_0, IccTestDataPrimitives.UInt32_0, - new byte[] { 0x00, 0x00, 0x00 }, // 0, 0 - ArrayHelper.Fill((byte)0x00, 67) - ); + ArrayHelper.Fill((byte)0x00, 67)); public static readonly object[][] TextDescriptionTagDataEntryTestData = { @@ -979,19 +736,14 @@ namespace SixLabors.ImageSharp.Tests new object[] { TextDescription_Arr2, TextDescription_Val2 }, }; - #endregion - - #region CrdInfoTagDataEntry - public static readonly IccCrdInfoTagDataEntry CrdInfo_Val = new IccCrdInfoTagDataEntry( IccTestDataPrimitives.Ascii_ValRand4, IccTestDataPrimitives.Ascii_ValRand1, IccTestDataPrimitives.Ascii_ValRand2, IccTestDataPrimitives.Ascii_ValRand3, - IccTestDataPrimitives.Ascii_ValRand4 - ); - public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat - ( + IccTestDataPrimitives.Ascii_ValRand4); + + public static readonly byte[] CrdInfo_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.Ascii_Rand4, new byte[] { 0 }, @@ -1006,108 +758,74 @@ namespace SixLabors.ImageSharp.Tests new byte[] { 0 }, IccTestDataPrimitives.UInt32_6, IccTestDataPrimitives.Ascii_Rand4, - new byte[] { 0 } - ); + new byte[] { 0 }); public static readonly object[][] CrdInfoTagDataEntryTestData = { new object[] { CrdInfo_Arr, CrdInfo_Val }, }; - #endregion - - #region ScreeningTagDataEntry - public static readonly IccScreeningTagDataEntry Screening_Val = new IccScreeningTagDataEntry( IccScreeningFlag.DefaultScreens | IccScreeningFlag.UnitLinesPerCm, - new IccScreeningChannel[] - { - IccTestDataNonPrimitives.ScreeningChannel_ValRand1, - IccTestDataNonPrimitives.ScreeningChannel_ValRand2, - } - ); - public static readonly byte[] Screening_Arr = ArrayHelper.Concat - ( + new IccScreeningChannel[] { IccTestDataNonPrimitives.ScreeningChannel_ValRand1, IccTestDataNonPrimitives.ScreeningChannel_ValRand2 }); + + public static readonly byte[] Screening_Arr = ArrayHelper.Concat( IccTestDataPrimitives.Int32_1, IccTestDataPrimitives.UInt32_2, IccTestDataNonPrimitives.ScreeningChannel_Rand1, - IccTestDataNonPrimitives.ScreeningChannel_Rand2 - ); + IccTestDataNonPrimitives.ScreeningChannel_Rand2); public static readonly object[][] ScreeningTagDataEntryTestData = { new object[] { Screening_Arr, Screening_Val }, }; - #endregion - - #region UcrBgTagDataEntry - public static readonly IccUcrBgTagDataEntry UcrBg_Val = new IccUcrBgTagDataEntry( new ushort[] { 3, 4, 6 }, new ushort[] { 9, 7, 2, 5 }, - IccTestDataPrimitives.Ascii_ValRand - ); - public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat - ( + IccTestDataPrimitives.Ascii_ValRand); + + public static readonly byte[] UcrBg_Arr = ArrayHelper.Concat( IccTestDataPrimitives.UInt32_3, IccTestDataPrimitives.UInt16_3, IccTestDataPrimitives.UInt16_4, IccTestDataPrimitives.UInt16_6, - IccTestDataPrimitives.UInt32_4, IccTestDataPrimitives.UInt16_9, IccTestDataPrimitives.UInt16_7, IccTestDataPrimitives.UInt16_2, IccTestDataPrimitives.UInt16_5, - IccTestDataPrimitives.Ascii_Rand, - new byte[] { 0 } - ); + new byte[] { 0 }); public static readonly object[][] UcrBgTagDataEntryTestData = { new object[] { UcrBg_Arr, UcrBg_Val, 41 }, }; - #endregion - - #region TagDataEntry - public static readonly IccTagDataEntry TagDataEntry_CurveVal = Curve_Val_2; - public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat - ( + public static readonly byte[] TagDataEntry_CurveArr = ArrayHelper.Concat( TagDataEntryHeader_CurveArr, Curve_Arr_2, - new byte[] { 0x00, 0x00 } // padding - ); + new byte[] { 0x00, 0x00 }); // padding public static readonly IccTagDataEntry TagDataEntry_MultiLocalizedUnicodeVal = MultiLocalizedUnicode_Val; - public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat - ( + public static readonly byte[] TagDataEntry_MultiLocalizedUnicodeArr = ArrayHelper.Concat( TagDataEntryHeader_MultiLocalizedUnicodeArr, MultiLocalizedUnicode_Arr, - new byte[] { 0x00, 0x00 } // padding - ); + new byte[] { 0x00, 0x00 }); // padding - public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry - ( - IccProfileTag.Unknown, 0, - (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2 - ); + public static readonly IccTagTableEntry TagDataEntry_MultiLocalizedUnicodeTable = new IccTagTableEntry( + IccProfileTag.Unknown, + 0, + (uint)TagDataEntry_MultiLocalizedUnicodeArr.Length - 2); - public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry - ( - IccProfileTag.Unknown, 0, - (uint)TagDataEntry_CurveArr.Length - 2 - ); + public static readonly IccTagTableEntry TagDataEntry_CurveTable = new IccTagTableEntry(IccProfileTag.Unknown, 0, (uint)TagDataEntry_CurveArr.Length - 2); public static readonly object[][] TagDataEntryTestData = { new object[] { TagDataEntry_CurveArr, TagDataEntry_CurveVal }, new object[] { TagDataEntry_MultiLocalizedUnicodeArr, TagDataEntry_MultiLocalizedUnicodeVal }, }; - - #endregion } } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 821992090..bd185fa6b 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests /// The "Formats" directory, as lazy value /// // ReSharper disable once InconsistentNaming - private static readonly Lazy inputImagesDirectory = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); + private static readonly Lazy InputImagesDirectoryValue = new Lazy(() => TestEnvironment.InputImagesDirectoryFullPath); /// /// The image (lazy initialized value) @@ -52,17 +52,17 @@ namespace SixLabors.ImageSharp.Tests public byte[] Bytes => this.bytes ?? (this.bytes = File.ReadAllBytes(this.FullPath)); /// - /// The full path to file. + /// Gets the full path to file. /// public string FullPath { get; } /// - /// The file name. + /// Gets the file name. /// public string FileName => Path.GetFileName(this.FullPath); /// - /// The file name without extension. + /// Gets the file name without extension. /// public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(this.FullPath); @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the input image directory. /// - private static string InputImagesDirectory => inputImagesDirectory.Value; + private static string InputImagesDirectory => InputImagesDirectoryValue.Value; /// /// Gets the full qualified path to the input test file. diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFontUtilities.cs similarity index 91% rename from tests/ImageSharp.Tests/TestFont.cs rename to tests/ImageSharp.Tests/TestFontUtilities.cs index c01f50f20..e087516c6 100644 --- a/tests/ImageSharp.Tests/TestFont.cs +++ b/tests/ImageSharp.Tests/TestFontUtilities.cs @@ -40,11 +40,12 @@ namespace SixLabors.ImageSharp.Tests /// private static string GetFontsDirectory() { - List directories = new List { + List directories = new List + { "TestFonts/", // Here for code coverage tests. - "tests/ImageSharp.Tests/TestFonts/", // from travis/build script - "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 - "../../../../TestFonts/" + "tests/ImageSharp.Tests/TestFonts/", // from travis/build script + "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../TestFonts/" }; directories = directories.SelectMany(x => new[] diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 0f44b8e1c..783d6fa48 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -47,16 +47,16 @@ namespace SixLabors.ImageSharp.Tests { ms.Write(marker, 0, marker.Length); } + ms.Position = 0; return ms; } public void VerifySpecificDecodeCall(byte[] marker, Configuration config) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); - Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) @@ -69,7 +69,6 @@ namespace SixLabors.ImageSharp.Tests { DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); - Assert.True(discovered.Any(), "No calls to decode on this format with the provided options happened"); foreach (DecodeOperation d in discovered) @@ -79,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests } public Image Sample() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { lock (this.sampleImages) { @@ -116,6 +115,7 @@ namespace SixLabors.ImageSharp.Tests { return false; } + for (int i = 0; i < this.header.Length; i++) { if (header[i] != this.header[i]) @@ -123,6 +123,7 @@ namespace SixLabors.ImageSharp.Tests return false; } } + return true; } @@ -135,38 +136,37 @@ namespace SixLabors.ImageSharp.Tests public struct DecodeOperation { - public byte[] marker; - internal Configuration config; + public byte[] Marker; + internal Configuration Config; - public Type pixelType; + public Type PixelType; public bool IsMatch(byte[] testMarker, Configuration config, Type pixelType) { - - if (this.config != config || this.pixelType != pixelType) + if (this.Config != config || this.PixelType != pixelType) { return false; } - if (testMarker.Length != this.marker.Length) + if (testMarker.Length != this.Marker.Length) { return false; } - for (int i = 0; i < this.marker.Length; i++) + for (int i = 0; i < this.Marker.Length; i++) { - if (testMarker[i] != this.marker[i]) + if (testMarker[i] != this.Marker[i]) { return false; } } + return true; } } public class TestHeader : IImageFormatDetector { - private TestFormat testFormat; public int HeaderSize => this.testFormat.HeaderSize; @@ -174,7 +174,9 @@ namespace SixLabors.ImageSharp.Tests public IImageFormat DetectFormat(ReadOnlySpan header) { if (this.testFormat.IsSupportedFileFormat(header)) + { return this.testFormat; + } return null; } @@ -184,6 +186,7 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } } + public class TestDecoder : ImageSharp.Formats.IImageDecoder { private TestFormat testFormat; @@ -193,30 +196,30 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } - public IEnumerable MimeTypes => new[] { testFormat.MimeType }; - - public IEnumerable FileExtensions => testFormat.SupportedExtensions; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; - public int HeaderSize => testFormat.HeaderSize; + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public Image Decode(Configuration config, Stream stream) where TPixel : struct, IPixel + public int HeaderSize => this.testFormat.HeaderSize; + public Image Decode(Configuration config, Stream stream) + where TPixel : unmanaged, IPixel { var ms = new MemoryStream(); stream.CopyTo(ms); var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); this.testFormat.DecodeCalls.Add(new DecodeOperation { - marker = marker, - config = config, - pixelType = typeof(TPixel) + Marker = marker, + Config = config, + PixelType = typeof(TPixel) }); // TODO record this happened so we can verify it. return this.testFormat.Sample(); } - public bool IsSupportedFileFormat(Span header) => testFormat.IsSupportedFileFormat(header); + public bool IsSupportedFileFormat(Span header) => this.testFormat.IsSupportedFileFormat(header); public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } @@ -230,37 +233,85 @@ namespace SixLabors.ImageSharp.Tests this.testFormat = testFormat; } - public IEnumerable MimeTypes => new[] { testFormat.MimeType }; + public IEnumerable MimeTypes => new[] { this.testFormat.MimeType }; - public IEnumerable FileExtensions => testFormat.SupportedExtensions; + public IEnumerable FileExtensions => this.testFormat.SupportedExtensions; - public void Encode(Image image, Stream stream) where TPixel : struct, IPixel + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel { // TODO record this happened so we can verify it. } } - - struct TestPixelForAgnosticDecode : IPixel + public struct TestPixelForAgnosticDecode : IPixel { public PixelOperations CreatePixelOperations() => new PixelOperations(); - public void FromScaledVector4(Vector4 vector) { } + + public void FromScaledVector4(Vector4 vector) + { + } + public Vector4 ToScaledVector4() => default; - public void FromVector4(Vector4 vector) { } + + public void FromVector4(Vector4 vector) + { + } + public Vector4 ToVector4() => default; - public void FromArgb32(Argb32 source) { } - public void FromBgra5551(Bgra5551 source) { } - public void FromBgr24(Bgr24 source) { } - public void FromBgra32(Bgra32 source) { } - public void FromL8(L8 source) { } - public void FromL16(L16 source) { } - public void FromLa16(La16 source) { } - public void FromLa32(La32 source) { } - public void FromRgb24(Rgb24 source) { } - public void FromRgba32(Rgba32 source) { } - public void ToRgba32(ref Rgba32 dest) { } - public void FromRgb48(Rgb48 source) { } - public void FromRgba64(Rgba64 source) { } + + public void FromArgb32(Argb32 source) + { + } + + public void FromBgra5551(Bgra5551 source) + { + } + + public void FromBgr24(Bgr24 source) + { + } + + public void FromBgra32(Bgra32 source) + { + } + + public void FromL8(L8 source) + { + } + + public void FromL16(L16 source) + { + } + + public void FromLa16(La16 source) + { + } + + public void FromLa32(La32 source) + { + } + + public void FromRgb24(Rgb24 source) + { + } + + public void FromRgba32(Rgba32 source) + { + } + + public void ToRgba32(ref Rgba32 dest) + { + } + + public void FromRgb48(Rgb48 source) + { + } + + public void FromRgba64(Rgba64 source) + { + } + public bool Equals(TestPixelForAgnosticDecode other) => false; } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f5cdb29b6..2e58ac970 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System.Linq; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming // ReSharper disable MemberHidesStaticFromOuterClass namespace SixLabors.ImageSharp.Tests { @@ -56,6 +56,7 @@ namespace SixLabors.ImageSharp.Tests public const string LowColorVariance = "Png/low-variance.png"; public const string PngWithMetadata = "Png/PngWithMetaData.png"; public const string InvalidTextData = "Png/InvalidTextData.png"; + public const string David = "Png/david.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -89,6 +90,7 @@ namespace SixLabors.ImageSharp.Tests public const string Issue1014_4 = "Png/issues/Issue_1014_4.png"; public const string Issue1014_5 = "Png/issues/Issue_1014_5.png"; public const string Issue1014_6 = "Png/issues/Issue_1014_6.png"; + public const string Issue1127 = "Png/issues/Issue_1127.png"; public static class Bad { @@ -142,7 +144,7 @@ namespace SixLabors.ImageSharp.Tests public const string Floorplan = "Jpg/baseline/Floorplan.jpg"; public const string Calliphora = "Jpg/baseline/Calliphora.jpg"; public const string Ycck = "Jpg/baseline/ycck.jpg"; - public const string Turtle = "Jpg/baseline/turtle.jpg"; + public const string Turtle420 = "Jpg/baseline/turtle.jpg"; public const string GammaDalaiLamaGray = "Jpg/baseline/gamma_dalai_lama_gray.jpg"; public const string Hiyamugi = "Jpg/baseline/Hiyamugi.jpg"; public const string Snake = "Jpg/baseline/Snake.jpg"; @@ -161,7 +163,7 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] All = { Cmyk, Ycck, Exif, Floorplan, - Calliphora, Turtle, GammaDalaiLamaGray, + Calliphora, Turtle420, GammaDalaiLamaGray, Hiyamugi, Jpeg400, Jpeg420Exif, Jpeg444, Ratio1x1, Testorig12bit, YcckSubsample1222 }; @@ -310,8 +312,8 @@ namespace SixLabors.ImageSharp.Tests public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; - public static readonly string[] BitFields - = { + public static readonly string[] BitFields = + { Rgb32bfdef, Rgb32bf, Rgb16565, @@ -320,32 +322,32 @@ namespace SixLabors.ImageSharp.Tests Issue735, }; - public static readonly string[] Miscellaneous - = { + public static readonly string[] Miscellaneous = + { Car, F, NegHeight }; - public static readonly string[] Benchmark - = { - Car, - F, - NegHeight, - CoreHeader, - V5Header, - RLE4, - RLE8, - RLE8Inverted, - Bit1, - Bit1Pal1, - Bit4, - Bit8, - Bit8Inverted, - Bit16, - Bit16Inverted, - Bit32Rgb - }; + public static readonly string[] Benchmark = + { + Car, + F, + NegHeight, + CoreHeader, + V5Header, + RLE4, + RLE8, + RLE8Inverted, + Bit1, + Bit1Pal1, + Bit4, + Bit8, + Bit8Inverted, + Bit16, + Bit16Inverted, + Bit32Rgb + }; } public static class Gif @@ -353,6 +355,7 @@ namespace SixLabors.ImageSharp.Tests public const string Rings = "Gif/rings.gif"; public const string Giphy = "Gif/giphy.gif"; public const string Cheers = "Gif/cheers.gif"; + public const string Receipt = "Gif/receipt.gif"; public const string Trans = "Gif/trans.gif"; public const string Kumin = "Gif/kumin.gif"; public const string Leo = "Gif/leo.gif"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 2ed4d9382..eff29555c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -15,20 +15,20 @@ namespace SixLabors.ImageSharp.Tests IEqualityComparer, IEqualityComparer { - private readonly float Epsilon; + private readonly float epsilon; /// /// Initializes a new instance of the class. /// /// The comparison error difference epsilon to use. - public ApproximateFloatComparer(float epsilon = 1F) => this.Epsilon = epsilon; + public ApproximateFloatComparer(float epsilon = 1F) => this.epsilon = epsilon; /// public bool Equals(float x, float y) { float d = x - y; - return d >= -this.Epsilon && d <= this.Epsilon; + return d >= -this.epsilon && d <= this.epsilon; } /// @@ -60,4 +60,4 @@ namespace SixLabors.ImageSharp.Tests /// public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs index fdb694dcc..eceecb2c8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ArrayHelper.cs @@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp.Tests arrays[i].CopyTo(result, offset); offset += arrays[i].Length; } + return result; } @@ -39,6 +40,7 @@ namespace SixLabors.ImageSharp.Tests { result[i] = value; } + return result; } @@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests /// The filled string public static string Fill(char value, int length) { - return "".PadRight(length, value); + return string.Empty.PadRight(length, value); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs index b2967058c..3287311bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/GroupOutputAttribute.cs @@ -1,4 +1,7 @@ -namespace SixLabors.ImageSharp.Tests +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests { using System; @@ -14,4 +17,4 @@ public string Subfolder { get; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index f03d68307..85b178c73 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -18,12 +18,16 @@ namespace SixLabors.ImageSharp.Tests protected readonly PixelTypes PixelTypes; + static ImageDataAttributeBase() + { + // ImageDataAttributes are used in almost all tests, thus a good place to enforce the execution of + // TestEnvironment static constructor before anything else is done. + TestEnvironment.EnsureSharedInitializersDone(); + } + /// /// Initializes a new instance of the class. /// - /// - /// - /// protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) { this.PixelTypes = pixelTypes; @@ -32,12 +36,12 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Gets the member name + /// Gets the member name. /// public string MemberName { get; } /// - /// Gets the member type + /// Gets or sets the member type. /// public Type MemberType { get; set; } @@ -84,8 +88,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns a value indicating whether the first parameter of the method is a test provider. /// - /// - /// + /// True, if the first parameter is a test provider. private bool FirstIsProvider(MethodInfo testMethod) { TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); @@ -102,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests { foreach (object[] row in memberData) { - var actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; + object[] actualFactoryMethodArgs = new object[originalFactoryMethodArgs.Length + 2]; Array.Copy(originalFactoryMethodArgs, actualFactoryMethodArgs, originalFactoryMethodArgs.Length); actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; @@ -110,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) .Invoke(null, actualFactoryMethodArgs); - var result = new object[this.AdditionalParameters.Length + 1 + row.Length]; + object[] result = new object[this.AdditionalParameters.Length + 1 + row.Length]; result[0] = factory; Array.Copy(row, 0, result, 1, row.Length); Array.Copy(this.AdditionalParameters, 0, result, 1 + row.Length, this.AdditionalParameters.Length); @@ -153,6 +156,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the field accessor for the given type. /// + /// The field accessor. protected Func GetFieldAccessor(Type type, string memberName) { FieldInfo fieldInfo = null; @@ -160,11 +164,15 @@ namespace SixLabors.ImageSharp.Tests { fieldInfo = reflectionType.GetRuntimeField(memberName); if (fieldInfo != null) + { break; + } } - if (fieldInfo == null || !fieldInfo.IsStatic) + if (fieldInfo is null || !fieldInfo.IsStatic) + { return null; + } return () => fieldInfo.GetValue(null); } @@ -172,6 +180,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Gets the property accessor for the given type. /// + /// The property accessor. protected Func GetPropertyAccessor(Type type, string memberName) { PropertyInfo propInfo = null; @@ -184,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests } } - if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic) + if (propInfo?.GetMethod is null || !propInfo.GetMethod.IsStatic) { return null; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs similarity index 99% rename from tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs rename to tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs index 796cba855..051bfecdc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImagesAttribute.cs @@ -42,10 +42,11 @@ namespace SixLabors.ImageSharp.Tests } public int Width { get; } + public int Height { get; } protected override string GetFactoryMethodName(MethodInfo testMethod) => "Blank"; protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs index 5aed3b364..60c8c8cef 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs @@ -9,8 +9,8 @@ namespace SixLabors.ImageSharp.Tests { /// /// Triggers passing instances which return the image produced by the given test class member method - /// instances will be passed for each the pixel format defined by the pixelTypes parameter - /// The parameter of the factory method must be a instance + /// instances will be passed for each the pixel format defined by the pixelTypes parameter. + /// The parameter of the factory method must be a instance. /// public class WithMemberFactoryAttribute : ImageDataAttributeBase { @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Tests /// /// Triggers passing instances which return the image produced by the given test class member method - /// instances will be passed for each the pixel format defined by the pixelTypes parameter + /// instances will be passed for each the pixel format defined by the pixelTypes parameter. /// - /// The name of the static test class which returns the image - /// The requested pixel types - /// Additional theory parameter values + /// The name of the static test class which returns the image. + /// The requested pixel types. + /// Additional theory parameter values. public WithMemberFactoryAttribute(string memberMethodName, PixelTypes pixelTypes, params object[] additionalParameters) : base(null, pixelTypes, additionalParameters) { @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - return new object[] { testMethod.DeclaringType.FullName, this.memberMethodName}; + return new object[] { testMethod.DeclaringType.FullName, this.memberMethodName }; } protected override string GetFactoryMethodName(MethodInfo testMethod) => "Lambda"; diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs index 190e80fba..5c67b5d13 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -141,22 +141,22 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Red + /// Gets the red component. /// public byte R { get; } /// - /// Green + /// Gets the green component. /// public byte G { get; } /// - /// Blue + /// Gets the blue component. /// public byte B { get; } /// - /// Alpha + /// Gets the alpha component. /// public byte A { get; } @@ -165,4 +165,4 @@ namespace SixLabors.ImageSharp.Tests protected override string GetFactoryMethodName(MethodInfo testMethod) => "Solid"; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs similarity index 89% rename from tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs rename to tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs index 7c659c64f..0f00f1d86 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImagesAttribute.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests public class WithTestPatternImagesAttribute : ImageDataAttributeBase { /// - /// Triggers passing an that produces a test pattern image of size width * height + /// Initializes a new instance of the class. /// /// The required width /// The required height @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests } /// - /// Triggers passing an that produces a test pattern image of size width * height + /// Initializes a new instance of the class. /// /// The member data to apply to theories /// The required width @@ -53,4 +53,4 @@ namespace SixLabors.ImageSharp.Tests protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs index 48469db43..d7c54f35f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs @@ -30,6 +30,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities { writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); } + writer.Flush(); byte[] data = ms.ToArray(); return System.Convert.ToBase64String(data); @@ -42,7 +43,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities using var ms = new MemoryStream(data); using var reader = new StreamReader(ms); var type = Type.GetType(reader.ReadLine()); - for (string s = reader.ReadLine(); s != null ; s = reader.ReadLine()) + for (string s = reader.ReadLine(); s != null; s = reader.ReadLine()) { string[] kv = s.Split(Separator); this.map[kv[0]] = kv[1]; @@ -58,12 +59,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities return serializer.DumpToString(serializable.GetType()); } - public static T Deserialize(string dump) where T : IXunitSerializable + public static T Deserialize(string dump) + where T : IXunitSerializable { var serializer = new BasicSerializer(); Type type = serializer.LoadDump(dump); - var result = (T) Activator.CreateInstance(type); + var result = (T)Activator.CreateInstance(type); result.Deserialize(serializer); return result; } @@ -75,6 +77,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities { return; } + type ??= value.GetType(); this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index b8b7ca025..9b7ebe34a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; @@ -21,7 +24,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison int width = actual.Width; // TODO: Comparing through Rgba64 may not be robust enough because of the existence of super high precision pixel types. - var aBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width]; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index d000f7093..626b698e1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using System.Linq; @@ -21,6 +24,16 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); + // TODO: We should add OSX. + sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux"); + sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is CI : {0}", TestEnvironment.RunsOnCI); + sb.Append(Environment.NewLine); + + sb.AppendFormat("Test Environment is .NET Core : {0}", !TestEnvironment.IsFramework); + sb.Append(Environment.NewLine); + int i = 0; foreach (ImageSimilarityReport r in reports) { @@ -29,7 +42,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.Append(Environment.NewLine); i++; } + return sb.ToString(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs index c44b89568..df1c1837b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDimensionsMismatchException.cs @@ -13,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } public Size ExpectedSize { get; } + public Size ActualSize { get; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs index bbdb6b581..d84f1c358 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImagesSimilarityException.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { using System; @@ -9,4 +12,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs index 95b3eb024..76c018f06 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs @@ -16,6 +16,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// Returns an instance of . /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. /// + /// A ImageComparer instance. public static ImageComparer Tolerant( float imageThreshold = TolerantImageComparer.DefaultImageThreshold, int perPixelManhattanThreshold = 0) @@ -26,13 +27,15 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison /// /// Returns Tolerant(imageThresholdInPercents/100) /// + /// A ImageComparer instance. public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0) => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold); public abstract ImageSimilarityReport CompareImagesOrFrames( ImageFrame expected, ImageFrame actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel; + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel; } public static class ImageComparerExtensions @@ -41,7 +44,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame); } @@ -50,7 +54,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { var result = new List>(); @@ -58,6 +63,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { throw new Exception("Frame count does not match!"); } + for (int i = 0; i < expected.Frames.Count; i++) { ImageSimilarityReport report = comparer.CompareImagesOrFrames(expected.Frames[i], actual.Frames[i]); @@ -74,7 +80,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison this ImageComparer comparer, Image expected, Image actual) - where TPixelA : struct, IPixel where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { if (expected.Size() != actual.Size()) { @@ -98,8 +105,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison Image expected, Image actual, Rectangle ignoredRegion) - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { if (expected.Size() != actual.Size()) { @@ -137,4 +144,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index f53407976..2faeacf68 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -63,6 +66,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendLine(); sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); } + int max = Math.Min(5, this.Differences.Length); for (int i = 0; i < max; i++) @@ -73,17 +77,19 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison sb.AppendFormat(";{0}", Environment.NewLine); } } + if (this.Differences.Length >= 5) { sb.Append("..."); } + return sb.ToString(); } } public class ImageSimilarityReport : ImageSimilarityReport - where TPixelA : struct, IPixel - where TPixelB : struct, IPixel + where TPixelA : unmanaged, IPixel + where TPixelB : unmanaged, IPixel { public ImageSimilarityReport( ImageFrame expectedImage, @@ -101,4 +107,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison public new ImageFrame ActualImage => (ImageFrame)base.ActualImage; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs index 30380218c..6452bc581 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs @@ -1,4 +1,7 @@ -using SixLabors.ImageSharp.PixelFormats; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { @@ -19,7 +22,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } public PixelDifference(Point position, Rgba64 expected, Rgba64 actual) - : this(position, + : this( + position, actual.R - expected.R, actual.G - expected.G, actual.B - expected.B, @@ -30,11 +34,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison public Point Position { get; } public int RedDifference { get; } + public int GreenDifference { get; } + public int BlueDifference { get; } + public int AlphaDifference { get; } public override string ToString() => $"[Δ({this.RedDifference},{this.GreenDifference},{this.BlueDifference},{this.AlphaDifference}) @ ({this.Position.X},{this.Position.Y})]"; } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 751c8d46b..36da98453 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -65,7 +68,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison int width = actual.Width; // TODO: Comparing through Rgba64 may not robust enough because of the existence of super high precision pixel types. - var aBuffer = new Rgba64[width]; var bBuffer = new Rgba64[width]; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 63f07eed1..1025ed9a1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -5,21 +5,32 @@ using System; using System.Numerics; using SixLabors.ImageSharp.Advanced; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider + public abstract partial class TestImageProvider : IXunitSerializable { + public virtual TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + throw new NotSupportedException("GetExpectedBasicTestPatternPixelAt(x,y) only works with BasicTestPattern"); + } + private class BasicTestPatternProvider : BlankProvider { + private static readonly TPixel TopLeftColor = Color.Red.ToPixel(); + private static readonly TPixel TopRightColor = Color.Green.ToPixel(); + private static readonly TPixel BottomLeftColor = Color.Blue.ToPixel(); + + // Transparent purple: + private static readonly TPixel BottomRightColor = GetBottomRightColor(); + public BasicTestPatternProvider(int width, int height) : base(width, height) { } - /// - /// This parameterless constructor is needed for xUnit deserialization - /// + // This parameterless constructor is needed for xUnit deserialization public BasicTestPatternProvider() { } @@ -30,14 +41,6 @@ namespace SixLabors.ImageSharp.Tests { var result = new Image(this.Configuration, this.Width, this.Height); - TPixel topLeftColor = Color.Red.ToPixel(); - TPixel topRightColor = Color.Green.ToPixel(); - TPixel bottomLeftColor = Color.Blue.ToPixel(); - - // Transparent purple: - TPixel bottomRightColor = default; - bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); - int midY = this.Height / 2; int midX = this.Width / 2; @@ -45,20 +48,42 @@ namespace SixLabors.ImageSharp.Tests { Span row = result.GetPixelRowSpan(y); - row.Slice(0, midX).Fill(topLeftColor); - row.Slice(midX, this.Width - midX).Fill(topRightColor); + row.Slice(0, midX).Fill(TopLeftColor); + row.Slice(midX, this.Width - midX).Fill(TopRightColor); } for (int y = midY; y < this.Height; y++) { Span row = result.GetPixelRowSpan(y); - row.Slice(0, midX).Fill(bottomLeftColor); - row.Slice(midX, this.Width - midX).Fill(bottomRightColor); + row.Slice(0, midX).Fill(BottomLeftColor); + row.Slice(midX, this.Width - midX).Fill(BottomRightColor); } return result; } + + public override TPixel GetExpectedBasicTestPatternPixelAt(int x, int y) + { + int midY = this.Height / 2; + int midX = this.Width / 2; + + if (y < midY) + { + return x < midX ? TopLeftColor : TopRightColor; + } + else + { + return x < midX ? BottomLeftColor : BottomRightColor; + } + } + + private static TPixel GetBottomRightColor() + { + TPixel bottomRightColor = default; + bottomRightColor.FromVector4(new Vector4(1f, 0f, 1f, 0.5f)); + return bottomRightColor; + } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index 0860af1a4..b8ad5c506 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.PixelFormats; @@ -7,8 +7,8 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class BlankProvider : TestImageProvider, IXunitSerializable { @@ -35,7 +35,6 @@ namespace SixLabors.ImageSharp.Tests public override Image GetImage() => new Image(this.Configuration, this.Width, this.Height); - public override void Deserialize(IXunitSerializationInfo info) { this.Width = info.GetValue("width"); @@ -51,4 +50,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 8c5b88b28..48d7b80fb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -13,8 +13,8 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class FileProvider : TestImageProvider, IXunitSerializable { @@ -22,14 +22,18 @@ namespace SixLabors.ImageSharp.Tests // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : IEquatable { - private Tuple commonValues; + private readonly Tuple commonValues; - private Dictionary decoderParameters; + private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) + public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) { Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple(pixelType, filePath, customType); + this.commonValues = new Tuple( + pixelType, + filePath, + customType, + allocatorBufferCapacity); this.decoderParameters = GetDecoderParameters(customDecoder); } @@ -48,8 +52,10 @@ namespace SixLabors.ImageSharp.Tests object value = p.GetValue(customDecoder); data[key] = value; } + type = type.GetTypeInfo().BaseType; } + return data; } @@ -81,11 +87,13 @@ namespace SixLabors.ImageSharp.Tests { return false; } + if (!object.Equals(kv.Value, otherVal)) { return false; } } + return true; } @@ -116,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests public static bool operator !=(Key left, Key right) => !Equals(left, right); } - private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> Cache = new ConcurrentDictionary>(); // Needed for deserialization! // ReSharper disable once UnusedMember.Local @@ -148,9 +156,10 @@ namespace SixLabors.ImageSharp.Tests return this.LoadImage(decoder); } - var key = new Key(this.PixelType, this.FilePath, decoder); + int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); + var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); - Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder)); + Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); return cachedImage.Clone(this.Configuration); } @@ -181,4 +190,4 @@ namespace SixLabors.ImageSharp.Tests return fileProvider?.FilePath; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs new file mode 100644 index 000000000..199cc4316 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/ITestImageProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Tests +{ + public interface ITestImageProvider + { + PixelTypes PixelType { get; } + + ImagingTestCaseUtility Utility { get; } + + string SourceFileOrDescription { get; } + + Configuration Configuration { get; set; } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs index 077dc622f..45cf57064 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs @@ -13,8 +13,8 @@ namespace SixLabors.ImageSharp.Tests /// Provides instances for parametric unit tests. /// /// The pixel format of the image - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class MemberMethodProvider : TestImageProvider { @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests Type imgType = typeof(Image<>).MakeGenericType(pixelType); Type funcType = typeof(Func<>).MakeGenericType(imgType); MethodInfo genericMethod = m.MakeGenericMethod(pixelType); - return (Func>) genericMethod.CreateDelegate(funcType); + return (Func>)genericMethod.CreateDelegate(funcType); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 7da9707ef..131647301 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -1,20 +1,19 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. - using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - /// /// Provides instances for parametric unit tests. /// /// The pixel format of the image - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { private class SolidProvider : BlankProvider { @@ -55,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests Image image = base.GetImage(); Color color = new Rgba32(this.r, this.g, this.b, this.a); - image.GetPixelSpan().Fill(color.ToPixel()); + image.GetRootFramePixelBuffer().FastMemoryGroup.Fill(color.ToPixel()); return image; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 8b165a8be..c652b32af 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -13,41 +13,35 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public interface ITestImageProvider - { - PixelTypes PixelType { get; } - ImagingTestCaseUtility Utility { get; } - string SourceFileOrDescription { get; } - - Configuration Configuration { get; set; } - } - /// /// Provides instances for parametric unit tests. /// - /// The pixel format of the image + /// The pixel format of the image. public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); - public virtual string SourceFileOrDescription => ""; + public virtual string SourceFileOrDescription => string.Empty; public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); /// - /// Utility instance to provide information about the test image & manage input/output + /// Gets the utility instance to provide information about the test image & manage input/output. /// public ImagingTestCaseUtility Utility { get; private set; } public string TypeName { get; private set; } + public string MethodName { get; private set; } + public string OutputSubfolderName { get; private set; } - public static TestImageProvider BasicTestPattern(int width, - int height, - MethodInfo testMethod = null, - PixelTypes pixelTypeOverride = PixelTypes.Undefined) + public static TestImageProvider BasicTestPattern( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) => new BasicTestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); public static TestImageProvider TestPattern( @@ -95,6 +89,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns an instance to the test case with the necessary traits. /// + /// A test image. public abstract Image GetImage(); public virtual Image GetImage(IImageDecoder decoder) @@ -105,6 +100,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns an instance to the test case with the necessary traits. /// + /// A test image. public Image GetImage(Action operationsToApply) { Image img = this.GetImage(); @@ -140,6 +136,7 @@ namespace SixLabors.ImageSharp.Tests { this.PixelType = pixelTypeOverride; } + this.TypeName = typeName; this.MethodName = methodName; this.OutputSubfolderName = outputSubfolderName; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 8965458cd..47b647329 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -7,28 +7,31 @@ using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests { - public abstract partial class TestImageProvider - where TPixel : struct, IPixel + public abstract partial class TestImageProvider : IXunitSerializable + where TPixel : unmanaged, IPixel { /// /// A test image provider that produces test patterns. /// private class TestPatternProvider : BlankProvider { - static readonly Dictionary> TestImages = new Dictionary>(); + private static readonly Dictionary> TestImages = new Dictionary>(); - private static TPixel[] BlackWhitePixels = new[] { + private static readonly TPixel[] BlackWhitePixels = + { Color.Black.ToPixel(), Color.White.ToPixel() - }; + }; - private static TPixel[] PinkBluePixels = new[] { + private static readonly TPixel[] PinkBluePixels = + { Color.HotPink.ToPixel(), Color.Blue.ToPixel() - }; + }; public TestPatternProvider(int width, int height) : base(width, height) @@ -50,10 +53,11 @@ namespace SixLabors.ImageSharp.Tests { if (!TestImages.ContainsKey(this.SourceFileOrDescription)) { - Image image = new Image(this.Width, this.Height); + var image = new Image(this.Width, this.Height); DrawTestPattern(image); TestImages.Add(this.SourceFileOrDescription, image); } + return TestImages[this.SourceFileOrDescription].Clone(this.Configuration); } } @@ -61,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. /// - /// + /// The image to rdaw on. private static void DrawTestPattern(Image image) { // first lets split the image into 4 quadrants @@ -75,7 +79,6 @@ namespace SixLabors.ImageSharp.Tests /// /// Fills the top right quadrant with alternating solid vertical bars. /// - /// private static void VerticalBars(Buffer2D pixels) { // topLeft @@ -99,6 +102,7 @@ namespace SixLabors.ImageSharp.Tests p++; p = p % PinkBluePixels.Length; } + pixels[x, y] = PinkBluePixels[p]; } } @@ -107,7 +111,6 @@ namespace SixLabors.ImageSharp.Tests /// /// fills the top left quadrant with a black and white checker board. /// - /// private static void BlackWhiteChecker(Buffer2D pixels) { // topLeft @@ -120,21 +123,24 @@ namespace SixLabors.ImageSharp.Tests int p = 0; for (int y = top; y < bottom; y++) { - if (y % stride == 0) + if (y % stride is 0) { p++; p = p % BlackWhitePixels.Length; } + int pstart = p; for (int x = left; x < right; x++) { - if (x % stride == 0) + if (x % stride is 0) { p++; p = p % BlackWhitePixels.Length; } + pixels[x, y] = BlackWhitePixels[p]; } + p = pstart; } } @@ -142,7 +148,6 @@ namespace SixLabors.ImageSharp.Tests /// /// Fills the bottom left quadrant with 3 horizontal bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). /// - /// private static void TransparentGradients(Buffer2D pixels) { // topLeft @@ -152,11 +157,11 @@ namespace SixLabors.ImageSharp.Tests int bottom = pixels.Height; int height = (int)Math.Ceiling(pixels.Height / 6f); - Vector4 red = Rgba32.Red.ToVector4(); // use real color so we can see har it translates in the test pattern - Vector4 green = Rgba32.Green.ToVector4(); // use real color so we can see har it translates in the test pattern - Vector4 blue = Rgba32.Blue.ToVector4(); // use real color so we can see har it translates in the test pattern + var red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + var blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - TPixel c = default(TPixel); + var c = default(TPixel); for (int x = left; x < right; x++) { @@ -168,12 +173,14 @@ namespace SixLabors.ImageSharp.Tests { pixels[x, y] = c; } + topBand = topBand + height; c.FromVector4(green); for (int y = topBand; y < topBand + height; y++) { pixels[x, y] = c; } + topBand = topBand + height; c.FromVector4(blue); for (int y = topBand; y < bottom; y++) @@ -187,7 +194,6 @@ namespace SixLabors.ImageSharp.Tests /// Fills the bottom right quadrant with all the colors producible by converting iterating over a uint and unpacking it. /// A better algorithm could be used but it works /// - /// private static void Rainbow(Buffer2D pixels) { int left = pixels.Width / 2; @@ -205,8 +211,9 @@ namespace SixLabors.ImageSharp.Tests for (int y = top; y < bottom; y++) { t.PackedValue += stepsPerPixel; - Vector4 v = t.ToVector4(); - //v.W = (x - left) / (float)left; + var v = t.ToVector4(); + + // v.W = (x - left) / (float)left; c.FromVector4(v); pixels[x, y] = c; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 92cc9f636..e08dff525 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -19,25 +19,26 @@ namespace SixLabors.ImageSharp.Tests public class ImagingTestCaseUtility { /// - /// Name of the TPixel in the owner + /// Gets or sets the name of the TPixel in the owner /// public string PixelTypeName { get; set; } = string.Empty; /// - /// The name of the file which is provided by + /// Gets or sets the name of the file which is provided by /// Or a short string describing the image in the case of a non-file based image provider. /// public string SourceFileOrDescription { get; set; } = string.Empty; /// - /// By default this is the name of the test class, but it's possible to change it + /// Gets or sets the test group name. + /// By default this is the name of the test class, but it's possible to change it. /// public string TestGroupName { get; set; } = string.Empty; public string OutputSubfolderName { get; set; } = string.Empty; /// - /// The name of the test case (by default) + /// Gets or sets the name of the test case (by default). /// public string TestName { get; set; } = string.Empty; @@ -54,7 +55,7 @@ namespace SixLabors.ImageSharp.Tests string fn = appendSourceFileOrDescription ? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription) - : ""; + : string.Empty; if (string.IsNullOrWhiteSpace(extension)) { @@ -65,6 +66,7 @@ namespace SixLabors.ImageSharp.Tests { extension = ".bmp"; } + extension = extension.ToLower(); if (extension[0] != '.') @@ -77,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests fn = '_' + fn; } - string pixName = ""; + string pixName = string.Empty; if (appendPixelTypeToFileName) { @@ -137,8 +139,7 @@ namespace SixLabors.ImageSharp.Tests detailsString = string.Join( "_", properties.ToDictionary(x => x.Name, x => x.GetValue(testOutputDetails)) - .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}")) - ); + .Select(x => TestUtils.AsInvariantString($"{x.Key}-{x.Value}"))); } } @@ -152,12 +153,13 @@ namespace SixLabors.ImageSharp.Tests /// /// Encodes image by the format matching the required extension, than saves it to the recommended output file. /// - /// The image instance - /// The requested extension - /// Optional encoder - /// A value indicating whether to append the pixel type to the test output file name + /// The image instance. + /// The requested extension. + /// Optional encoder. + /// Additional information to append to the test output file name. + /// A value indicating whether to append the pixel type to the test output file name. /// A boolean indicating whether to append to the test output file name. - /// Additional information to append to the test output file name + /// The path to the saved image file. public string SaveTestOutputFile( Image image, string extension = null, @@ -189,7 +191,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) { - string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); + string baseDir = this.GetTestOutputFileName(string.Empty, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription); if (!Directory.Exists(baseDir)) { @@ -209,7 +211,7 @@ namespace SixLabors.ImageSharp.Tests IImageEncoder encoder = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { encoder = encoder ?? TestEnvironment.GetReferenceEncoder($"foo.{extension}"); @@ -241,8 +243,7 @@ namespace SixLabors.ImageSharp.Tests bool appendSourceFileOrDescription) { return TestEnvironment.GetReferenceOutputFileName( - this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription) - ); + this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)); } public string[] GetReferenceOutputFileNamesMultiFrame( @@ -275,10 +276,10 @@ namespace SixLabors.ImageSharp.Tests } public static void ModifyPixel(Image img, int x, int y, byte perChannelChange) - where TPixel : struct, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); + where TPixel : unmanaged, IPixel => ModifyPixel(img.Frames.RootFrame, x, y, perChannelChange); public static void ModifyPixel(ImageFrame img, int x, int y, byte perChannelChange) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { TPixel pixel = img[x, y]; Rgba64 rgbaPixel = default; @@ -325,4 +326,4 @@ namespace SixLabors.ImageSharp.Tests img[x, y] = pixel; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 61bd7cd9f..b01ece7ba 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Value indicating whether printing is enabled. /// - protected bool EnablePrinting = true; + protected bool enablePrinting = true; /// /// Measures and prints the execution time of an , executed multiple times. @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests /// The name of the operation to print to the output public void Measure(int times, Action action, [CallerMemberName] string operationName = null) { - if (this.EnablePrinting) + if (this.enablePrinting) { this.Output?.WriteLine($"{operationName} X {times} ..."); } @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests } sw.Stop(); - if (this.EnablePrinting) + if (this.enablePrinting) { this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); } diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index eb8860eb6..9d3427a06 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -12,6 +12,7 @@ namespace SixLabors.ImageSharp.Tests [Flags] public enum PixelTypes { +#pragma warning disable SA1602 // Enumeration items should be documented Undefined = 0, A8 = 1 << 0, @@ -72,5 +73,7 @@ namespace SixLabors.ImageSharp.Tests // "All" is handled as a separate, individual case instead of using bitwise OR All = 30 + +#pragma warning restore SA1602 // Enumeration items should be documented } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index e81714ddc..4708d70b0 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -3,12 +3,14 @@ using System; using System.IO; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ImageMagick; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs @@ -17,47 +19,66 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { public static MagickReferenceDecoder Instance { get; } = new MagickReferenceDecoder(); + private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba32Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 4); + } + } + + private static void FromRgba64Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) + where TPixel : unmanaged, IPixel + { + foreach (Memory m in destinationGroup) + { + Span destBuffer = m.Span; + PixelOperations.Instance.FromRgba64Bytes( + configuration, + rgbaBytes, + destBuffer, + destBuffer.Length); + rgbaBytes = rgbaBytes.Slice(destBuffer.Length * 8); + } + } + public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - using (var magickImage = new MagickImage(stream)) + using var magickImage = new MagickImage(stream); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + MemoryGroup resultPixels = result.GetRootFramePixelBuffer().FastMemoryGroup; + + using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) { - var result = new Image(configuration, magickImage.Width, magickImage.Height); - Span resultPixels = result.GetPixelSpan(); + if (magickImage.Depth == 8) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - using (IPixelCollection pixels = magickImage.GetPixelsUnsafe()) + FromRgba32Bytes(configuration, data, resultPixels); + } + else if (magickImage.Depth == 16) { - if (magickImage.Depth == 8) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels, - resultPixels.Length); - } - else if (magickImage.Depth == 16) - { - ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); - Span bytes = MemoryMarshal.Cast(data.AsSpan()); - - PixelOperations.Instance.FromRgba64Bytes( - configuration, - bytes, - resultPixels, - resultPixels.Length); - } - else - { - throw new InvalidOperationException(); - } + ushort[] data = pixels.ToShortArray(PixelMapping.RGBA); + Span bytes = MemoryMarshal.Cast(data.AsSpan()); + FromRgba64Bytes(configuration, bytes, resultPixels); + } + else + { + throw new InvalidOperationException(); } - - return result; } + + return result; } - + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 1547edeeb..eb6f5e8c5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs /// The input bitmap. /// Thrown if the image pixel format is not of type internal static unsafe Image From32bppArgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int w = bmp.Width; int h = bmp.Height; @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs /// The input bitmap. /// Thrown if the image pixel format is not of type internal static unsafe Image From24bppRgbSystemDrawingBitmap(Bitmap bmp) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { int w = bmp.Width; int h = bmp.Height; @@ -128,11 +128,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { bmp.UnlockBits(data); } + return image; } internal static unsafe Bitmap To32bppArgbSystemDrawingBitmap(Image image) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Configuration configuration = image.GetConfiguration(); int w = image.Width; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 2de3c03aa..254112339 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static SystemDrawingReferenceDecoder Instance { get; } = new SystemDrawingReferenceDecoder(); public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var sourceBitmap = new System.Drawing.Bitmap(stream)) { @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata()); } } - + public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs index 46dae17a1..563fe2cb3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceEncoder.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs public static SystemDrawingReferenceEncoder Bmp { get; } = new SystemDrawingReferenceEncoder(ImageFormat.Bmp); public void Encode(Image image, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (System.Drawing.Bitmap sdBitmap = SystemDrawingBridge.To32bppArgbSystemDrawingBitmap(image)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index e09b27c71..d49c34efd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -55,8 +55,7 @@ namespace SixLabors.ImageSharp.Tests var cfg = new Configuration( new JpegConfigurationModule(), new GifConfigurationModule(), - new TgaConfigurationModule() - ); + new TgaConfigurationModule()); // Magick codecs should work on all platforms IImageEncoder pngEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Png : new PngEncoder(); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 1bc4f47c7..4152d3bc6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -34,12 +34,18 @@ namespace SixLabors.ImageSharp.Tests private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + static TestEnvironment() + { + PrepareRemoteExecutor(); + } + /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// internal static string NetCoreVersion => NetCoreVersionLazy.Value; // ReSharper disable once InconsistentNaming + /// /// Gets a value indicating whether test execution runs on CI. /// @@ -110,6 +116,13 @@ namespace SixLabors.ImageSharp.Tests internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + /// + /// A dummy operation to enforce the execution of the static constructor. + /// + internal static void EnsureSharedInitializersDone() + { + } + /// /// Creates the image output directory. /// @@ -140,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests /// When running in 32 bits, enforces 32 bit execution of Microsoft.DotNet.RemoteExecutor.exe /// with the help of CorFlags.exe found in Windows SDK. /// - internal static void PrepareRemoteExecutor() + private static void PrepareRemoteExecutor() { if (!IsFramework) { @@ -152,12 +165,11 @@ namespace SixLabors.ImageSharp.Tests if (File.Exists(remoteExecutorConfigPath)) { - // already prepared + // Already initialized return; } string testProjectConfigPath = TestAssemblyFile.FullName + ".config"; - File.Copy(testProjectConfigPath, remoteExecutorConfigPath); if (Is64BitProcess) @@ -174,14 +186,26 @@ namespace SixLabors.ImageSharp.Tests /// private static void EnsureRemoteExecutorIs32Bit() { - string windowsSdksDir = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), - "Microsoft SDKs", "Windows"); + string windowsSdksDir = Path.Combine( + Environment.GetEnvironmentVariable("PROGRAMFILES(x86)"), + "Microsoft SDKs", + "Windows"); FileInfo corFlagsFile = Find(new DirectoryInfo(windowsSdksDir), "CorFlags.exe"); string remoteExecutorPath = Path.Combine(TestAssemblyFile.DirectoryName, "Microsoft.DotNet.RemoteExecutor.exe"); - string args = $"{remoteExecutorPath} /32Bit+ /Force"; + string remoteExecutorTmpPath = $"{remoteExecutorPath}._tmp"; + + if (File.Exists(remoteExecutorTmpPath)) + { + // Already initialized + return; + } + + File.Copy(remoteExecutorPath, remoteExecutorTmpPath); + + string args = $"{remoteExecutorTmpPath} /32Bit+ /Force"; var si = new ProcessStartInfo() { @@ -203,6 +227,9 @@ namespace SixLabors.ImageSharp.Tests $@"Failed to run {si.FileName} {si.Arguments}:\n STDOUT: {standardOutput}\n STDERR: {standardError}"); } + File.Delete(remoteExecutorPath); + File.Copy(remoteExecutorTmpPath, remoteExecutorPath); + static FileInfo Find(DirectoryInfo root, string name) { FileInfo fi = root.EnumerateFiles(name).FirstOrDefault(); @@ -234,8 +261,11 @@ namespace SixLabors.ImageSharp.Tests string[] assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + { return assemblyPath[netCoreAppIndex + 1]; - return ""; + } + + return string.Empty; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index dcbc4f0c3..8ecb2ee6c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -5,9 +5,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Numerics; - +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Advanced.ParallelUtils; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -24,7 +23,6 @@ namespace SixLabors.ImageSharp.Tests /// /// TODO: Consider adding this private processor to the library /// - /// public static void MakeOpaque(this IImageProcessingContext ctx) => ctx.ApplyProcessor(new MakeOpaqueProcessor()); @@ -49,13 +47,14 @@ namespace SixLabors.ImageSharp.Tests /// /// Saves the image only when not running in the CI server. /// - /// The image - /// The image provider + /// The image. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. - /// The extension + /// The extension. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. /// Custom encoder to use. + /// The input image. public static Image DebugSave( this Image image, ITestImageProvider provider, @@ -125,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (TestEnvironment.RunsOnCI) { @@ -149,7 +148,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareToReferenceOutput( provider, @@ -164,15 +163,15 @@ namespace SixLabors.ImageSharp.Tests /// 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 . /// - /// The pixel format - /// The image - /// The image provider + /// The pixel format. + /// The image which should be compared to the reference image. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. - /// + /// The image. public static Image CompareToReferenceOutput( this Image image, ITestImageProvider provider, @@ -181,7 +180,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return CompareToReferenceOutput( image, @@ -202,7 +201,7 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareToReferenceOutput( comparer, @@ -217,17 +216,17 @@ namespace SixLabors.ImageSharp.Tests /// 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 . /// - /// The pixel format - /// The image - /// The to use - /// The image provider + /// The pixel format. + /// The image which should be compared to the reference output. + /// The to use. + /// The image provider. /// Details to be concatenated to the test output file, describing the parameters of the test. /// The extension /// A boolean indicating whether we should debug save + compare against a grayscale image, smaller in size. /// A boolean indicating whether to append the pixel type to the output file name. /// A boolean indicating whether to append to the test output file name. /// A custom decoder. - /// + /// The image. public static Image CompareToReferenceOutput( this Image image, ImageComparer comparer, @@ -238,7 +237,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true, IImageDecoder decoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = GetReferenceOutputImage( provider, @@ -263,7 +262,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return image.CompareFirstFrameToReferenceOutput( comparer, @@ -284,7 +283,7 @@ namespace SixLabors.ImageSharp.Tests bool grayscale = false, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (var firstFrameOnlyImage = new Image(image.Width, image.Height)) using (Image referenceImage = GetReferenceOutputImage( @@ -311,7 +310,7 @@ namespace SixLabors.ImageSharp.Tests string extension = "png", bool grayscale = false, bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = GetReferenceOutputImageMultiFrame( provider, @@ -326,13 +325,14 @@ namespace SixLabors.ImageSharp.Tests return image; } - public static Image GetReferenceOutputImage(this ITestImageProvider provider, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true, - bool appendSourceFileOrDescription = true, - IImageDecoder decoder = null) - where TPixel : struct, IPixel + public static Image GetReferenceOutputImage( + this ITestImageProvider provider, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true, + bool appendSourceFileOrDescription = true, + IImageDecoder decoder = null) + where TPixel : unmanaged, IPixel { string referenceOutputFile = provider.Utility.GetReferenceOutputFileName( extension, @@ -342,20 +342,21 @@ namespace SixLabors.ImageSharp.Tests if (!File.Exists(referenceOutputFile)) { - throw new System.IO.FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); + throw new FileNotFoundException("Reference output file missing: " + referenceOutputFile, referenceOutputFile); } - decoder = decoder ?? TestEnvironment.GetReferenceDecoder(referenceOutputFile); + decoder ??= TestEnvironment.GetReferenceDecoder(referenceOutputFile); return Image.Load(referenceOutputFile, decoder); } - public static Image GetReferenceOutputImageMultiFrame(this ITestImageProvider provider, - int frameCount, - object testOutputDetails = null, - string extension = "png", - bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + public static Image GetReferenceOutputImageMultiFrame( + this ITestImageProvider provider, + int frameCount, + object testOutputDetails = null, + string extension = "png", + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel { string[] frameFiles = provider.Utility.GetReferenceOutputFileNamesMultiFrame( frameCount, @@ -388,7 +389,7 @@ namespace SixLabors.ImageSharp.Tests fi.Dispose(); } - // remove the initial empty frame: + // Remove the initial empty frame: result.Frames.RemoveFrame(0); return result; } @@ -400,7 +401,7 @@ namespace SixLabors.ImageSharp.Tests object testOutputDetails = null, string extension = "png", bool appendPixelTypeToFileName = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image referenceImage = provider.GetReferenceOutputImage( testOutputDetails, @@ -414,7 +415,7 @@ namespace SixLabors.ImageSharp.Tests public static Image ComparePixelBufferTo( this Image image, Span expectedPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span actualPixels = image.GetPixelSpan(); @@ -440,8 +441,10 @@ namespace SixLabors.ImageSharp.Tests /// /// All pixels in all frames should be exactly equal to 'expectedPixel'. /// + /// The pixel type of the image. + /// The image. public static Image ComparePixelBufferTo(this Image image, TPixel expectedPixel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (ImageFrame imageFrame in image.Frames) { @@ -454,8 +457,10 @@ namespace SixLabors.ImageSharp.Tests /// /// All pixels in all frames should be exactly equal to 'expectedPixelColor.ToPixel()'. /// + /// The pixel type of the image. + /// The image. public static Image ComparePixelBufferTo(this Image image, Color expectedPixelColor) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { foreach (ImageFrame imageFrame in image.Frames) { @@ -468,8 +473,10 @@ namespace SixLabors.ImageSharp.Tests /// /// All pixels in the frame should be exactly equal to 'expectedPixel'. /// + /// The pixel type of the image. + /// The image. public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span actualPixels = imageFrame.GetPixelSpan(); @@ -484,7 +491,7 @@ namespace SixLabors.ImageSharp.Tests public static ImageFrame ComparePixelBufferTo( this ImageFrame image, Span expectedPixels) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Span actual = image.GetPixelSpan(); @@ -502,7 +509,7 @@ namespace SixLabors.ImageSharp.Tests this Image image, ITestImageProvider provider, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { return CompareToOriginal(image, provider, ImageComparer.Tolerant(), referenceDecoder); } @@ -512,7 +519,7 @@ namespace SixLabors.ImageSharp.Tests ITestImageProvider provider, ImageComparer comparer, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestImageProvider.GetFilePathOrNull(provider); if (path == null) @@ -545,7 +552,7 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -557,7 +564,8 @@ namespace SixLabors.ImageSharp.Tests appendPixelTypeToFileName: appendPixelTypeToFileName, appendSourceFileOrDescription: appendSourceFileOrDescription); - image.CompareToReferenceOutput(comparer, + image.CompareToReferenceOutput( + comparer, provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName, @@ -577,7 +585,7 @@ namespace SixLabors.ImageSharp.Tests FormattableString testOutputDetails, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation( ImageComparer.Tolerant(), @@ -599,7 +607,7 @@ namespace SixLabors.ImageSharp.Tests Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation( comparer, @@ -620,7 +628,7 @@ namespace SixLabors.ImageSharp.Tests Action> operation, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { provider.VerifyOperation(operation, $"", appendPixelTypeToFileName, appendSourceFileOrDescription); } @@ -639,7 +647,7 @@ namespace SixLabors.ImageSharp.Tests bool appendPixelTypeToFileName = true, string referenceImageExtension = null, IImageDecoder referenceDecoder = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string actualOutputFile = provider.Utility.SaveTestOutputFile( image, @@ -648,22 +656,30 @@ namespace SixLabors.ImageSharp.Tests testOutputDetails, appendPixelTypeToFileName); - referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(actualOutputFile); + referenceDecoder ??= TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var actualImage = Image.Load(actualOutputFile, referenceDecoder)) + using (var encodedImage = Image.Load(actualOutputFile, referenceDecoder)) { ImageComparer comparer = customComparer ?? ImageComparer.Exact; - comparer.VerifySimilarity(actualImage, image); + comparer.VerifySimilarity(encodedImage, image); } } + internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( + this TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; + return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); + } + internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) { var image = new Image(buffer.Width, buffer.Height); Span pixels = image.Frames.RootFrame.GetPixelSpan(); - Span bufferSpan = buffer.GetSpan(); + Span bufferSpan = buffer.GetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) { @@ -678,40 +694,86 @@ namespace SixLabors.ImageSharp.Tests private class MakeOpaqueProcessor : IImageProcessor { public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel => new MakeOpaqueProcessor(configuration, source, sourceRectangle); } private class MakeOpaqueProcessor : ImageProcessor - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public MakeOpaqueProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) : base(configuration, source, sourceRectangle) { - } protected override void OnFrameApply(ImageFrame source) { Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - ParallelHelper.IterateRowsWithTempBuffer(sourceRectangle, configuration, - (rows, temp) => + + var operation = new RowOperation(configuration, sourceRectangle, source); + + ParallelRowIterator.IterateRowIntervals( + configuration, + sourceRectangle, + in operation); + } + + private readonly struct RowOperation : IRowIntervalOperation + { + private readonly Configuration configuration; + private readonly Rectangle bounds; + private readonly ImageFrame source; + + public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + { + this.configuration = configuration; + this.bounds = bounds; + this.source = source; + } + + public void Invoke(in RowInterval rows, Span span) + { + for (int y = rows.Min; y < rows.Max; y++) + { + Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + for (int i = 0; i < span.Length; i++) { - Span tempSpan = temp.Span; - for (int y = rows.Min; y < rows.Max; y++) - { - Span rowSpan = source.GetPixelRowSpan(y).Slice(sourceRectangle.Left, sourceRectangle.Width); - PixelOperations.Instance.ToVector4(configuration, rowSpan, tempSpan, PixelConversionModifiers.Scale); - for (int i = 0; i < tempSpan.Length; i++) - { - ref Vector4 v = ref tempSpan[i]; - v.W = 1F; - } - PixelOperations.Instance.FromVector4Destructive(configuration, tempSpan, rowSpan, PixelConversionModifiers.Scale); - } - }); + ref Vector4 v = ref span[i]; + v.W = 1F; + } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + } + } } } } + + internal class AllocatorBufferCapacityConfigurator + { + private readonly ArrayPoolMemoryAllocator allocator; + private readonly int pixelSizeInBytes; + + public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + { + this.allocator = allocator; + this.pixelSizeInBytes = pixelSizeInBytes; + } + + public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; + + public void InPixels(int totalPixels) => this.InBytes(totalPixels * this.pixelSizeInBytes); + + /// + /// Set the maximum buffer capacity to bytesSqrt^2 bytes. + /// + public void InBytesSqrt(int bytesSqrt) => this.InBytes(bytesSqrt * bytesSqrt); + + /// + /// Set the maximum buffer capacity to pixelsSqrt^2 x sizeof(TPixel) bytes. + /// + public void InPixelsSqrt(int pixelsSqrt) => this.InPixels(pixelsSqrt * pixelsSqrt); + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index fcda2eaa1..dd928cb75 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using System.Buffers; using System.Collections.Generic; @@ -10,7 +13,8 @@ namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { - private List allocationLog = new List(); + private readonly List allocationLog = new List(); + private readonly List returnLog = new List(); public TestMemoryAllocator(byte dirtyValue = 42) { @@ -18,29 +22,35 @@ namespace SixLabors.ImageSharp.Tests.Memory } /// - /// The value to initialize the result buffer with, with non-clean options () + /// Gets the value to initialize the result buffer with, with non-clean options () /// public byte DirtyValue { get; } - public IList AllocationLog => this.allocationLog; + public int BufferCapacityInBytes { get; set; } = int.MaxValue; + + public IReadOnlyList AllocationLog => this.allocationLog; + + public IReadOnlyList ReturnLog => this.returnLog; + + protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); - return new BasicArrayBuffer(array, length); + return new BasicArrayBuffer(array, length, this); } public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) { byte[] array = this.AllocateArray(length, options); - return new ManagedByteBuffer(array); + return new ManagedByteBuffer(array, this); } private T[] AllocateArray(int length, AllocationOptions options) where T : struct { - this.allocationLog.Add(AllocationRequest.Create(options, length)); var array = new T[length + 42]; + this.allocationLog.Add(AllocationRequest.Create(options, length, array)); if (options == AllocationOptions.None) { @@ -51,34 +61,54 @@ namespace SixLabors.ImageSharp.Tests.Memory return array; } + private void Return(BasicArrayBuffer buffer) + where T : struct + { + this.returnLog.Add(new ReturnRequest(buffer.Array.GetHashCode())); + } + public struct AllocationRequest { - private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes) + private AllocationRequest(Type elementType, AllocationOptions allocationOptions, int length, int lengthInBytes, int hashCodeOfBuffer) { this.ElementType = elementType; this.AllocationOptions = allocationOptions; this.Length = length; this.LengthInBytes = lengthInBytes; + this.HashCodeOfBuffer = hashCodeOfBuffer; if (elementType == typeof(Vector4)) { - } } - public static AllocationRequest Create(AllocationOptions allocationOptions, int length) + public static AllocationRequest Create(AllocationOptions allocationOptions, int length, T[] buffer) { Type type = typeof(T); int elementSize = Marshal.SizeOf(type); - return new AllocationRequest(type, allocationOptions, length, length * elementSize); + return new AllocationRequest(type, allocationOptions, length, length * elementSize, buffer.GetHashCode()); } public Type ElementType { get; } + public AllocationOptions AllocationOptions { get; } + public int Length { get; } + public int LengthInBytes { get; } + + public int HashCodeOfBuffer { get; } } + public struct ReturnRequest + { + public ReturnRequest(int hashCodeOfBuffer) + { + this.HashCodeOfBuffer = hashCodeOfBuffer; + } + + public int HashCodeOfBuffer { get; } + } /// /// Wraps an array as an instance. @@ -86,36 +116,29 @@ namespace SixLabors.ImageSharp.Tests.Memory private class BasicArrayBuffer : MemoryManager where T : struct { + private readonly TestMemoryAllocator allocator; private GCHandle pinHandle; - /// - /// Initializes a new instance of the class - /// - /// The array - /// The length of the buffer - public BasicArrayBuffer(T[] array, int length) + public BasicArrayBuffer(T[] array, int length, TestMemoryAllocator allocator) { + this.allocator = allocator; DebugGuard.MustBeLessThanOrEqualTo(length, array.Length, nameof(length)); this.Array = array; this.Length = length; } - /// - /// Initializes a new instance of the class - /// - /// The array - public BasicArrayBuffer(T[] array) - : this(array, array.Length) + public BasicArrayBuffer(T[] array, TestMemoryAllocator allocator) + : this(array, array.Length, allocator) { } /// - /// Gets the array + /// Gets the array. /// public T[] Array { get; } /// - /// Gets the length + /// Gets the length. /// public int Length { get; } @@ -141,15 +164,19 @@ namespace SixLabors.ImageSharp.Tests.Memory /// protected override void Dispose(bool disposing) { + if (disposing) + { + this.allocator.Return(this); + } } } private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer { - public ManagedByteBuffer(byte[] array) - : base(array) + public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) + : base(array, allocator) { } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs index 9274e5727..3fd5f6e37 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryManager.cs @@ -1,9 +1,12 @@ -using System; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System; using System.Buffers; namespace SixLabors.ImageSharp.Tests { - class TestMemoryManager : MemoryManager + public class TestMemoryManager : MemoryManager where T : struct { public TestMemoryManager(T[] pixelArray) @@ -43,4 +46,4 @@ namespace SixLabors.ImageSharp.Tests this.PixelArray = null; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index f3be50e9a..ba146b9e4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -10,7 +10,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.TestUtilities { public class TestPixel : IXunitSerializable - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { public TestPixel() { @@ -25,20 +25,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities } public float Red { get; set; } + public float Green { get; set; } + public float Blue { get; set; } + public float Alpha { get; set; } public TPixel AsPixel() { - TPixel pix = default(TPixel); + var pix = default(TPixel); pix.FromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); return pix; } internal Span AsSpan() { - return new Span(new[] { AsPixel() }); + return new Span(new[] { this.AsPixel() }); } public void Deserialize(IXunitSerializationInfo info) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 9ac8054ea..3a1fbd195 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -6,10 +6,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -52,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (a.Width != b.Width || a.Height != b.Height) { @@ -104,8 +106,7 @@ namespace SixLabors.ImageSharp.Tests /// /// Returns the enumerations for the given type. /// - /// - /// + /// The pixel type. public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) @@ -128,6 +129,7 @@ namespace SixLabors.ImageSharp.Tests result[pt] = pt.GetClrType(); } } + return result; } @@ -147,9 +149,31 @@ namespace SixLabors.ImageSharp.Tests } internal static TPixel GetPixelOfNamedColor(string colorName) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => GetColorByName(colorName).ToPixel(); + internal static void RunBufferCapacityLimitProcessorTest( + this TestImageProvider provider, + int bufferCapacityInPixelRows, + Action process, + object testOutputDetails = null, + ImageComparer comparer = null) + where TPixel : unmanaged, IPixel + { + comparer??= ImageComparer.Exact; + using Image expected = provider.GetImage(); + int width = expected.Width; + expected.Mutate(process); + + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); + provider.Configuration.MemoryAllocator = allocator; + allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); + + using Image actual = provider.GetImage(); + actual.Mutate(process); + comparer.VerifySimilarity(expected, actual); + } + /// /// Utility for testing image processor extension methods: /// 1. Run a processor defined by 'process' @@ -160,8 +184,8 @@ namespace SixLabors.ImageSharp.Tests /// The image processing method to test. (As a delegate) /// The value to append to the test output. /// The custom image comparer to use - /// - /// + /// If true, the pixel type will by appended to the output file. + /// A boolean indicating whether to append to the test output file name. internal static void RunValidatingProcessorTest( this TestImageProvider provider, Action process, @@ -169,7 +193,7 @@ namespace SixLabors.ImageSharp.Tests ImageComparer comparer = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -205,7 +229,7 @@ namespace SixLabors.ImageSharp.Tests ImageComparer comparer = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -215,9 +239,7 @@ namespace SixLabors.ImageSharp.Tests using (Image image = provider.GetImage()) { FormattableString testOutputDetails = $""; - image.Mutate( - ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); } - ); + image.Mutate(ctx => { testOutputDetails = processAndGetTestOutputDetails(ctx); }); image.DebugSave( provider, @@ -246,7 +268,7 @@ namespace SixLabors.ImageSharp.Tests string useReferenceOutputFrom = null, bool appendPixelTypeToFileName = true, bool appendSourceFileOrDescription = true) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -296,8 +318,9 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider, Action process, object testOutputDetails = null, - ImageComparer comparer = null) - where TPixel : struct, IPixel + ImageComparer comparer = null, + bool appendPixelTypeToFileName = true) + where TPixel : unmanaged, IPixel { if (comparer == null) { @@ -308,8 +331,8 @@ namespace SixLabors.ImageSharp.Tests { var bounds = new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2); image.Mutate(x => process(x, bounds)); - image.DebugSave(provider, testOutputDetails); - image.CompareToReferenceOutput(comparer, provider, testOutputDetails); + image.DebugSave(provider, testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); + image.CompareToReferenceOutput(comparer, provider, testOutputDetails: testOutputDetails, appendPixelTypeToFileName: appendPixelTypeToFileName); } } @@ -320,7 +343,7 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider, Action process, object testOutputDetails = null) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -343,6 +366,18 @@ namespace SixLabors.ImageSharp.Tests return (IResampler)property.GetValue(null); } + public static IDither GetDither(string name) + { + PropertyInfo property = typeof(KnownDitherings).GetTypeInfo().GetProperty(name); + + if (property is null) + { + throw new Exception($"No dither named '{name}"); + } + + return (IDither)property.GetValue(null); + } + public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) { return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) @@ -351,4 +386,4 @@ namespace SixLabors.ImageSharp.Tests .ToArray(); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs index 990258e0c..8677184e3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.Numerics; @@ -21,8 +21,11 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities } public float X { get; set; } + public float Y { get; set; } + public float Z { get; set; } + public float W { get; set; } public static implicit operator Vector4(TestVector4 d) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs index 71ab5b262..5039f0154 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -6,48 +9,50 @@ namespace SixLabors.ImageSharp.Tests { public class BasicSerializerTests { - class BaseObj : IXunitSerializable + internal class BaseObj : IXunitSerializable { public double Length { get; set; } + public string Name { get; set; } + public int Lives { get; set; } public virtual void Deserialize(IXunitSerializationInfo info) { - info.AddValue(nameof(Length), Length); - info.AddValue(nameof(Name), Name); - info.AddValue(nameof(this.Lives), Lives); + info.AddValue(nameof(this.Length), this.Length); + info.AddValue(nameof(this.Name), this.Name); + info.AddValue(nameof(this.Lives), this.Lives); } public virtual void Serialize(IXunitSerializationInfo info) { - this.Length = info.GetValue(nameof(Length)); - this.Name = info.GetValue(nameof(Name)); - this.Lives = info.GetValue(nameof(Lives)); + this.Length = info.GetValue(nameof(this.Length)); + this.Name = info.GetValue(nameof(this.Name)); + this.Lives = info.GetValue(nameof(this.Lives)); } } - class DerivedObj : BaseObj + internal class DerivedObj : BaseObj { public double Strength { get; set; } public override void Deserialize(IXunitSerializationInfo info) { - this.Strength = info.GetValue(nameof(Strength)); + this.Strength = info.GetValue(nameof(this.Strength)); base.Deserialize(info); } public override void Serialize(IXunitSerializationInfo info) { base.Serialize(info); - info.AddValue(nameof(Strength), Strength); + info.AddValue(nameof(this.Strength), this.Strength); } } [Fact] public void SerializeDeserialize_ShouldPreserveValues() { - var obj = new DerivedObj() {Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8}; + var obj = new DerivedObj() { Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8 }; string str = BasicSerializer.Serialize(obj); BaseObj mirrorBase = BasicSerializer.Deserialize(str); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs index 061d42b0a..f411e9b08 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/GroupOutputTests.cs @@ -1,4 +1,7 @@ -using System.IO; +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; using SixLabors.ImageSharp.PixelFormats; @@ -12,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void OutputSubfolderName_ValueIsTakeFromGroupOutputAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.Equal("Foo", provider.Utility.OutputSubfolderName); } @@ -20,10 +23,10 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void GetTestOutputDir_ShouldDefineSubfolder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string expected = $"{Path.DirectorySeparatorChar}Foo{Path.DirectorySeparatorChar}"; Assert.Contains(expected, provider.Utility.GetTestOutputDir()); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 97c87d486..b977ca022 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System.Collections.Generic; using System.Linq; @@ -28,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests TestImageProvider provider, float imageThreshold, int pixelThreshold) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -43,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(110, 110, PixelTypes.Rgba32)] public void TolerantImageComparer_ApprovesSimilarityBelowTolerance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -60,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void TolerantImageComparer_DoesNotApproveSimilarityAboveTolerance(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -83,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba64)] public void TolerantImageComparer_TestPerPixelThreshold(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -103,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 99, 100)] [WithTestPatternImages(100, 100, PixelTypes.Rgba32, 100, 99)] public void VerifySimilarity_ThrowsOnSizeMismatch(TestImageProvider provider, int w, int h) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -120,11 +123,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void VerifySimilarity_WhenAnImageFrameIsDifferent_Reports(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -140,11 +142,10 @@ namespace SixLabors.ImageSharp.Tests } } - [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ExactComparer_ApprovesExactEquality(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -158,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ExactComparer_DoesNotTolerateAnyPixelDifference(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -178,4 +179,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index 3b4097820..6083b1fae 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -1,9 +1,9 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using Xunit; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests { using SixLabors.ImageSharp.PixelFormats; @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Splash)] [WithBlankImages(1, 1, PixelTypesToTest32, TestImages.Png.Indexed)] public void MagickDecode_8BitDepthImage_IsEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(testImage); @@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.Rgb48BppTrans)] [WithBlankImages(1, 1, PixelTypesToTest48, TestImages.Png.L16Bit)] public void MagickDecode_16BitDepthImage_IsApproximatelyEquivalentTo_SystemDrawingResult(TestImageProvider dummyProvider, string testImage) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(testImage); @@ -84,4 +84,4 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index b0d3b8c7e..9f9ea44f9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkMagickPngDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(PngBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Png"); } @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkSystemDrawingPngDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(PngBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Png"); } @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkMagickBmpDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new MagickReferenceDecoder(), "Magick Decode Bmp"); } @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory(Skip = SkipBenchmarks)] [WithFile(TestImages.Png.Kaboom, PixelTypes.Rgba32)] public void BenchmarkSystemDrawingBmpDecoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.BenchmarkDecoderImpl(BmpBenchmarkFiles, new SystemDrawingReferenceDecoder(), "System.Drawing Decode Bmp"); } @@ -81,7 +81,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests private void BenchmarkDecoderImpl(IEnumerable testFiles, IImageDecoder decoder, string info, int times = DefaultExecutionCount) { var measure = new MeasureFixture(this.Output); - measure.Measure(times, + measure.Measure( + times, () => { foreach (string testFile in testFiles) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs index 4a02d280e..4c0d5758f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void To32bppArgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void From32bppArgbSystemDrawingBitmap(TestImageProvider dummyProvider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests } private static string SavePng(TestImageProvider provider, PngColorType pngColorType) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image sourceImage = provider.GetImage()) { @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void From32bppArgbSystemDrawingBitmap2(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (TestEnvironment.IsLinux) { @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgb24)] public void From24bppRgbSystemDrawingBitmap(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = SavePng(provider, PngColorType.Rgb); @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32 | PixelTypes.Bgra32)] public void OpenWithReferenceDecoder(TestImageProvider dummyProvider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { string path = TestFile.GetInputFileFullPath(TestImages.Png.Splash); using (var image = Image.Load(path, SystemDrawingReferenceDecoder.Instance)) @@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void SaveWithReferenceEncoder(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 30a47062b..160b1fe40 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; + using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; @@ -12,8 +13,8 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using Xunit.Abstractions; -// ReSharper disable InconsistentNaming +// ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests { public class TestEnvironmentTests @@ -66,7 +67,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Windows(string fileName, Type expectedEncoderType) { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); @@ -79,7 +83,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Windows(string fileName, Type expectedDecoderType) { - if (TestEnvironment.IsLinux) return; + if (TestEnvironment.IsLinux) + { + return; + } IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); @@ -92,7 +99,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifEncoder))] public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Type expectedEncoderType) { - if (!TestEnvironment.IsLinux) return; + if (!TestEnvironment.IsLinux) + { + return; + } IImageEncoder encoder = TestEnvironment.GetReferenceEncoder(fileName); Assert.IsType(expectedEncoderType, encoder); @@ -105,7 +115,10 @@ namespace SixLabors.ImageSharp.Tests [InlineData("lol/Baz.gif", typeof(GifDecoder))] public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType) { - if (!TestEnvironment.IsLinux) return; + if (!TestEnvironment.IsLinux) + { + return; + } IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); Assert.IsType(expectedDecoderType, decoder); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs index 6a1582828..30f7f1f16 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageExtensionsTests.cs @@ -1,3 +1,6 @@ +// Copyright (c) Six Labors and contributors. +// Licensed under the Apache License, Version 2.0. + using System; using Moq; @@ -15,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceOutputMatches_ShouldNotThrow( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -27,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceOutputDoesNotMatch_Throws( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -39,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_DoNotAppendPixelType( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -51,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 10, 0, 0, 255, PixelTypes.Rgba32)] public void CompareToReferenceOutput_WhenReferenceFileMissing_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -62,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void CompareToOriginal_WhenSimilar(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -76,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] public void CompareToOriginal_WhenDifferent_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -92,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(10, 10, PixelTypes.Rgba32)] public void CompareToOriginal_WhenInputIsNotFromFile_Throws(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -103,4 +106,4 @@ namespace SixLabors.ImageSharp.Tests } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 416f4034f..498f3edca 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -14,7 +14,6 @@ using Xunit; using Xunit.Abstractions; // ReSharper disable InconsistentNaming - namespace SixLabors.ImageSharp.Tests { public class TestImageProviderTests @@ -40,17 +39,16 @@ namespace SixLabors.ImageSharp.Tests /// /// Need to us to create instance of when pixelType is StandardImageClass /// - /// - /// - /// + /// The pixel type of the image. + /// A test image. public static Image CreateTestImage() - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => new Image(3, 3); [Theory] [MemberData(nameof(BasicData))] public void Blank_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); @@ -60,7 +58,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [MemberData(nameof(FileData))] public void File_MemberData(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); @@ -74,7 +72,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -104,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); @@ -132,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual( TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) { @@ -165,7 +163,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32)] public void NoOutputSubfolderIsPresentByDefault(TestImageProvider provider) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => Assert.Empty(provider.Utility.OutputSubfolderName); [Theory] @@ -173,13 +171,13 @@ namespace SixLabors.ImageSharp.Tests [WithBlankImages(1, 1, PixelTypes.A8, PixelTypes.A8)] [WithBlankImages(1, 1, PixelTypes.Argb32, PixelTypes.Argb32)] public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) - where TPixel : struct, IPixel => + where TPixel : unmanaged, IPixel => Assert.Equal(expected, provider.PixelType); [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void SaveTestOutputFileMultiFrame(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage()) { @@ -199,7 +197,7 @@ namespace SixLabors.ImageSharp.Tests [WithBasicTestPatternImages(49, 17, PixelTypes.Rgba32)] [WithBasicTestPatternImages(20, 10, PixelTypes.Rgba32)] public void Use_WithBasicTestPatternImages(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -212,7 +210,7 @@ namespace SixLabors.ImageSharp.Tests public void Use_WithBlankImagesAttribute_WithAllPixelTypes( TestImageProvider provider, string message) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); @@ -224,7 +222,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithBlankImages(42, 666, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.HalfSingle, "hello")] public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); @@ -237,7 +235,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.All, 123)] [WithFile(TestImages.Bmp.F, PixelTypes.All, 123)] public void Use_WithFileAttribute(TestImageProvider provider, int yo) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image img = provider.GetImage()) @@ -254,7 +252,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFile(TestImages.Jpeg.Baseline.Testorig420, PixelTypes.Rgba32)] public void Use_WithFileAttribute_CustomConfig(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { EnsureCustomConfigurationIsApplied(provider); } @@ -262,7 +260,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithFileCollection(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); using (Image image = provider.GetImage()) @@ -274,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.All)] public void Use_WithMemberFactoryAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); Assert.Equal(3, img.Width); @@ -287,7 +285,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithSolidFilledImages(10, 20, 255, 100, 50, 200, PixelTypes.Rgba32 | PixelTypes.Argb32)] public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image img = provider.GetImage(); Assert.Equal(10, img.Width); @@ -312,7 +310,7 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(49, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (Image img = provider.GetImage()) { @@ -323,13 +321,13 @@ namespace SixLabors.ImageSharp.Tests [Theory] [WithTestPatternImages(20, 20, PixelTypes.Rgba32)] public void Use_WithTestPatternImages_CustomConfiguration(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { EnsureCustomConfigurationIsApplied(provider); } private static void EnsureCustomConfigurationIsApplied(TestImageProvider provider) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { using (provider.GetImage()) { @@ -348,8 +346,7 @@ namespace SixLabors.ImageSharp.Tests private class TestDecoder : IImageDecoder { // Couldn't make xUnit happy without this hackery: - - private static readonly ConcurrentDictionary invocationCounts = + private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); private static readonly object Monitor = new object(); @@ -365,18 +362,18 @@ namespace SixLabors.ImageSharp.Tests } public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - invocationCounts[this.callerName]++; + InvocationCounts[this.callerName]++; return new Image(42, 42); } - internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal void InitCaller(string name) { this.callerName = name; - invocationCounts[name] = 0; + InvocationCounts[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); @@ -384,7 +381,7 @@ namespace SixLabors.ImageSharp.Tests private class TestDecoderWithParameters : IImageDecoder { - private static readonly ConcurrentDictionary invocationCounts = + private static readonly ConcurrentDictionary InvocationCounts = new ConcurrentDictionary(); private static readonly object Monitor = new object(); @@ -404,18 +401,18 @@ namespace SixLabors.ImageSharp.Tests } public Image Decode(Configuration configuration, Stream stream) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { - invocationCounts[this.callerName]++; + InvocationCounts[this.callerName]++; return new Image(42, 42); } - internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + internal static int GetInvocationCount(string callerName) => InvocationCounts[callerName]; internal void InitCaller(string name) { this.callerName = name; - invocationCounts[name] = 0; + InvocationCounts[name] = 0; } public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 64b217a02..821370b7a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests private ITestOutputHelper Output { get; } public static Image CreateTestImage() - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { var image = new Image(10, 10); @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, true)] [WithFile(TestImages.Bmp.Car, PixelTypes.Rgba32, false)] public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image a = provider.GetImage(); Image b = provider.GetImage(x => x.OilPaint(3, 2)); @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, true)] [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32 | PixelTypes.Bgr565, false)] public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Image a = provider.GetImage(); Image b = provider.GetImage(); @@ -93,7 +93,6 @@ namespace SixLabors.ImageSharp.Tests IEnumerable> pixelTypesExp) { Assert.Contains(new KeyValuePair(pt, typeof(T)), pixelTypesExp); - } [Fact] diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs index 9612882b3..ba82eb1ac 100644 --- a/tests/ImageSharp.Tests/VectorAssert.cs +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests public static class VectorAssert { public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) - where TPixel : struct, IPixel + where TPixel : unmanaged, IPixel { Equal(expected.ToVector4(), actual.ToVector4(), precision); } @@ -48,25 +48,23 @@ namespace SixLabors.ImageSharp.Tests public bool Equals(Vector2 x, Vector2 y) { - return Equals(x.X, y.X) && - Equals(x.Y, y.Y); - + return this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y); } + public bool Equals(Vector3 x, Vector3 y) { - return Equals(x.X, y.X) && - Equals(x.Y, y.Y) && - Equals(x.Z, y.Z); - + return this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y) && + this.Equals(x.Z, y.Z); } public bool Equals(Vector4 x, Vector4 y) { - return Equals(x.W, y.W) && - Equals(x.X, y.X) && - Equals(x.Y, y.Y) && - Equals(x.Z, y.Z); - + return this.Equals(x.W, y.W) && + this.Equals(x.X, y.X) && + this.Equals(x.Y, y.Y) && + this.Equals(x.Z, y.Z); } public bool Equals(float x, float y) @@ -78,10 +76,12 @@ namespace SixLabors.ImageSharp.Tests { return obj.GetHashCode(); } + public int GetHashCode(Vector3 obj) { return obj.GetHashCode(); } + public int GetHashCode(Vector2 obj) { return obj.GetHashCode(); diff --git a/tests/Images/External b/tests/Images/External index fbba5e2a7..d80955193 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit fbba5e2a78aa479c0752dc0fd91ec25b4948704a +Subproject commit d809551931858cd3873bad49d2fe915fddff7a26 diff --git a/tests/Images/Input/Gif/receipt.gif b/tests/Images/Input/Gif/receipt.gif new file mode 100644 index 000000000..9018b67b1 Binary files /dev/null and b/tests/Images/Input/Gif/receipt.gif differ diff --git a/tests/Images/Input/Png/bike-small.png b/tests/Images/Input/Png/bike-small.png new file mode 100644 index 000000000..8597e68e9 Binary files /dev/null and b/tests/Images/Input/Png/bike-small.png differ diff --git a/tests/Images/Input/Png/david.png b/tests/Images/Input/Png/david.png new file mode 100644 index 000000000..6cfa88480 Binary files /dev/null and b/tests/Images/Input/Png/david.png differ diff --git a/tests/Images/Input/Png/issues/Issue_1127.png b/tests/Images/Input/Png/issues/Issue_1127.png new file mode 100644 index 000000000..52914a32b Binary files /dev/null and b/tests/Images/Input/Png/issues/Issue_1127.png differ diff --git a/tests/Images/Input/Tga/targa.png b/tests/Images/Input/Tga/targa.png deleted file mode 100644 index c18cf7e23..000000000 Binary files a/tests/Images/Input/Tga/targa.png and /dev/null differ